@oj-module/cli 0.2.50 → 0.2.54

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 (130) hide show
  1. package/devkit/SKILL.md +2 -2
  2. package/devkit/manual.md +32 -25
  3. package/devkit/wecom-internal-app.md +111 -4
  4. package/package.json +2 -2
  5. package/shell-dist/assets/{index-FYt9CjFd.js → index-e_Mp2mb5.js} +5 -1
  6. package/shell-dist/assets/runtime.js +29 -5
  7. package/shell-dist/index.html +9 -12
  8. package/shell-dist/versions.json +3 -3
  9. package/src/__tests__/templates-tenant-scope.test.ts +3 -2
  10. package/src/build.ts +19 -2
  11. package/src/config.ts +9 -0
  12. package/src/contract/__tests__/client-path.test.ts +30 -0
  13. package/src/contract/__tests__/emit-client.test.ts +18 -0
  14. package/src/contract/__tests__/handler-scan.test.ts +23 -0
  15. package/src/contract/check.ts +5 -5
  16. package/src/contract/client-path.ts +30 -0
  17. package/src/contract/emit-client.ts +18 -4
  18. package/src/contract/handler-scan.ts +40 -3
  19. package/src/contract/run.ts +5 -3
  20. package/src/init.ts +19 -1
  21. package/src/shared-deps.ts +27 -9
  22. package/src/site-base.ts +1 -1
  23. package/templates/README.md +40 -44
  24. package/templates/init.include.yaml +15 -14
  25. package/templates/openapi.json +3422 -1955
  26. package/templates/src/_platform/api/_shared/service/domain/users.ts +40 -4
  27. package/templates/src/_platform/api/contacts/api.ts +29 -1
  28. package/templates/src/_platform/api/contacts/detail/api.ts +25 -1
  29. package/templates/src/_platform/api/solutions/api.ts +60 -1
  30. package/templates/src/_platform/web/__client/api.schemas.ts +99 -35
  31. package/templates/src/_platform/web/__client/api.ts +103 -59
  32. package/templates/src/app/web/entry.ts +77 -0
  33. package/templates/src/{home/web → app/web/home}/__client/api.ts +5 -2
  34. package/templates/src/app/web/home/routes.ts +41 -0
  35. package/templates/src/{login/web → app/web/login}/pages/login.tsx +54 -24
  36. package/templates/src/app/web/login/routes.ts +71 -0
  37. package/templates/src/{notification/web → app/web/notification}/__client/api.schemas.ts +14 -0
  38. package/templates/src/{notification/web → app/web/notification}/__client/api.ts +67 -20
  39. package/templates/src/app/web/notification/routes.ts +70 -0
  40. package/templates/src/{personal-center/web → app/web/personal-center}/__client/api.ts +5 -2
  41. package/templates/src/app/web/personal-center/routes.ts +49 -0
  42. package/templates/src/{system/web → app/web/system}/__client/api.schemas.ts +28 -22
  43. package/templates/src/{system/web → app/web/system}/__client/api.ts +103 -58
  44. package/templates/src/{system/web → app/web/system}/locales/en-US.json +14 -0
  45. package/templates/src/{system/web → app/web/system}/locales/zh-CN.json +14 -0
  46. package/templates/src/{system/web → app/web/system}/pages/users/index.tsx +5 -2
  47. package/templates/src/{system/web → app/web/system}/pages/users/roles.tsx +14 -3
  48. package/templates/src/{system/web → app/web/system}/pages/wecom/index.tsx +139 -1
  49. package/templates/src/app/web/system/routes.ts +97 -0
  50. package/templates/src/auth/api/_shared/wecom/admin.ts +4 -0
  51. package/templates/src/auth/api/_shared/wecom/client.ts +9 -2
  52. package/templates/src/auth/api/_shared/wecom/events.ts +3 -0
  53. package/templates/src/auth/api/_shared/wecom/tenant-admin.ts +194 -0
  54. package/templates/src/auth/api/api.ts +59 -0
  55. package/templates/src/auth/api/ldap-login/api.ts +37 -1
  56. package/templates/src/auth/api/wecom/admin/tenant-admin/api.ts +41 -0
  57. package/templates/src/auth/api/wecom/admin/tenant-admin/password/api.ts +66 -0
  58. package/templates/src/auth/api/wecom/api.ts +23 -0
  59. package/templates/src/auth/web/__client/api.schemas.ts +133 -13
  60. package/templates/src/auth/web/__client/api.ts +129 -31
  61. package/templates/src/notification/api/api.ts +17 -0
  62. package/templates/src/notification/api/del/api.ts +2 -0
  63. package/templates/src/system/api/roles/api.ts +3 -0
  64. package/templates/src/system/api/roles/bindings/api.ts +3 -0
  65. package/templates/src/system/api/settings/api.ts +6 -3
  66. package/templates/src/system/api/users/forbidden/api.ts +3 -0
  67. package/templates/src/system/api/users/password/api.ts +3 -0
  68. package/templates/src/system/api/users/roles/api.ts +3 -0
  69. package/templates/src/web/api/get-async-routes/api.ts +16 -1
  70. package/templates/src/web/api/hello/api.ts +13 -1
  71. package/templates/src/web/api/user-info/api.ts +21 -1
  72. package/templates/src/web/web/__client/api.schemas.ts +28 -0
  73. package/templates/src/web/web/__client/api.ts +33 -9
  74. package/templates/tsconfig.paths.json +0 -1
  75. package/templates/web.config.ts +22 -17
  76. package/vendor/host-versions.json +3 -3
  77. package/shell-dist/assets/echarts-charts.js +0 -58571
  78. package/shell-dist/assets/echarts-features.js +0 -24057
  79. package/shell-dist/assets/echarts-for-react.js +0 -704
  80. package/shell-dist/assets/echarts.js +0 -82916
  81. package/shell-dist/assets/editor.js +0 -161457
  82. package/shell-dist/assets/vditor-index-css.js +0 -7
  83. package/shell-dist/assets/vditor.js +0 -15223
  84. package/templates/src/demo/api/manifest.yaml +0 -5
  85. package/templates/src/demo/api/migrations/0001__create_todos.sql +0 -9
  86. package/templates/src/demo/api/migrations/0002__tenant.mysql.sql +0 -2
  87. package/templates/src/demo/api/migrations/0002__tenant.sql +0 -2
  88. package/templates/src/demo/api/schema.yaml +0 -12
  89. package/templates/src/demo/api/seed.sql +0 -5
  90. package/templates/src/demo/api/todos/api.ts +0 -59
  91. package/templates/src/demo/web/__client/api.schemas.ts +0 -20
  92. package/templates/src/demo/web/__client/api.ts +0 -89
  93. package/templates/src/demo/web/entry.ts +0 -42
  94. package/templates/src/demo/web/locales/en-US.json +0 -10
  95. package/templates/src/demo/web/locales/zh-CN.json +0 -10
  96. package/templates/src/demo/web/pages/index.tsx +0 -45
  97. package/templates/src/home/web/entry.ts +0 -49
  98. package/templates/src/login/web/entry.ts +0 -79
  99. package/templates/src/notification/web/entry.ts +0 -84
  100. package/templates/src/personal-center/web/entry.ts +0 -60
  101. package/templates/src/system/web/entry.ts +0 -108
  102. /package/templates/src/{home/web → app/web/home}/__client/api.schemas.ts +0 -0
  103. /package/templates/src/{home/web → app/web/home}/locales/en-US.json +0 -0
  104. /package/templates/src/{home/web → app/web/home}/locales/zh-CN.json +0 -0
  105. /package/templates/src/{home/web → app/web/home}/pages/components/bar-chart.tsx +0 -0
  106. /package/templates/src/{home/web → app/web/home}/pages/components/card-list.tsx +0 -0
  107. /package/templates/src/{home/web → app/web/home}/pages/components/line-chart.tsx +0 -0
  108. /package/templates/src/{home/web → app/web/home}/pages/components/pie-chart.tsx +0 -0
  109. /package/templates/src/{home/web → app/web/home}/pages/index.tsx +0 -0
  110. /package/templates/src/{login/web → app/web/login}/auth-provider.ts +0 -0
  111. /package/templates/src/{login/web → app/web/login}/locales/en-US.json +0 -0
  112. /package/templates/src/{login/web → app/web/login}/locales/zh-CN.json +0 -0
  113. /package/templates/src/{login/web → app/web/login}/pages/change-password.tsx +0 -0
  114. /package/templates/src/{login/web → app/web/login}/wecom.ts +0 -0
  115. /package/templates/src/{notification/web → app/web/notification}/locales/en-US.json +0 -0
  116. /package/templates/src/{notification/web → app/web/notification}/locales/zh-CN.json +0 -0
  117. /package/templates/src/{notification/web → app/web/notification}/pages/center/index.tsx +0 -0
  118. /package/templates/src/{personal-center/web → app/web/personal-center}/__client/api.schemas.ts +0 -0
  119. /package/templates/src/{personal-center/web → app/web/personal-center}/locales/en-US.json +0 -0
  120. /package/templates/src/{personal-center/web → app/web/personal-center}/locales/zh-CN.json +0 -0
  121. /package/templates/src/{personal-center/web → app/web/personal-center}/pages/my-profile/index.tsx +0 -0
  122. /package/templates/src/{system/web → app/web/system}/pages/departments/index.tsx +0 -0
  123. /package/templates/src/{system/web → app/web/system}/pages/departments/sync-bar.tsx +0 -0
  124. /package/templates/src/{system/web → app/web/system}/pages/departments/tree.ts +0 -0
  125. /package/templates/src/{system/web → app/web/system}/pages/errors.ts +0 -0
  126. /package/templates/src/{system/web → app/web/system}/pages/forbidden.tsx +0 -0
  127. /package/templates/src/{system/web → app/web/system}/pages/login-methods/index.tsx +0 -0
  128. /package/templates/src/{system/web → app/web/system}/pages/oplog/index.tsx +0 -0
  129. /package/templates/src/{system/web → app/web/system}/pages/roles/index.tsx +0 -0
  130. /package/templates/src/{system/web → app/web/system}/pages/settings/index.tsx +0 -0
@@ -33,7 +33,7 @@ export function create_platformClient(ctx: ModuleContext) {
33
33
  }
34
34
  ctx.register.apiPrefix(API_PREFIX);
35
35
  bindRequest(ctx.utils.request);
36
- return { platformContactsGet, platformContactsDetailGet, platformMarketInstallPost, platformMarketInstallCancelPost, platformMarketTaskGet, platformMarketEnablePost, platformMarketMetaGet, platformMarketTrustGet, platformMarketTrustPost, platformMarketTrustKeyPost, platformMarketTrustKeyDelete, platformMarketIndexGet, platformMarketUninstallImpactGet, platformMarketUninstallPost, platformMarketHistoryGet, platformSolutionsRoutesGet, platformSolutionsVerifyPost, platformSolutionsVerifyLastGet, platformOrgTreeGet, platformOrgDepartmentsGet, platformOrgMembersGet, platformOrgUserGet, platformOrgPickerGet, platformOrgResolvePost, platformOrgIsMemberGet };
36
+ return { platformContactsGet, platformContactsDetailGet, platformMarketInstallPost, platformMarketInstallCancelPost, platformMarketTaskGet, platformMarketEnablePost, platformMarketMetaGet, platformMarketTrustGet, platformMarketTrustPost, platformMarketTrustKeyPost, platformMarketTrustKeyDelete, platformMarketIndexGet, platformMarketUninstallImpactGet, platformMarketUninstallPost, platformMarketHistoryGet, platformOrgDepartmentsGet, platformOrgIsMemberGet, platformOrgMembersGet, platformOrgPickerGet, platformOrgResolvePost, platformOrgTreeGet, platformOrgUserGet, platformSolutionsRoutesGet, platformSolutionsVerifyPost, platformSolutionsVerifyLastGet };
37
37
  }
38
38
 
39
39
  /**
@@ -64,14 +64,23 @@ async function toApiError(e: unknown): Promise<unknown> {
64
64
  return e;
65
65
  }
66
66
 
67
- export async function platformContactsGet(): Promise<unknown> {
67
+ export type PlatformContactsGetQuery = z.input<(typeof schemas)["platformContactsGet"]["query"]>;
68
+ export type PlatformContactsGetData = z.infer<(typeof schemas)["platformContactsGet"]["data"]>;
69
+
70
+ export async function platformContactsGet(query: PlatformContactsGetQuery): Promise<PlatformContactsGetData> {
68
71
  const client = ensureReq();
69
72
  try {
70
- const env = await client.get(`_platform/contacts`).json<OjEnvelope<unknown>>();
73
+ const env = await client.get(`_platform/contacts`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<PlatformContactsGetData>>();
71
74
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
72
75
  if (typeof env.code === "number" && env.code !== 0)
73
76
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
74
- const data = env.data as unknown;
77
+ const data = env.data as PlatformContactsGetData;
78
+ if (import.meta.env.DEV) {
79
+ const { schemas } = await import("./api.schemas");
80
+ const r = schemas["platformContactsGet"].data.safeParse(data);
81
+ if (!r.success)
82
+ throw new ContractApiError(-1, `[契约违例] platformContactsGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
83
+ }
75
84
  return data;
76
85
  }
77
86
  catch (e) {
@@ -79,14 +88,23 @@ export async function platformContactsGet(): Promise<unknown> {
79
88
  }
80
89
  }
81
90
 
82
- export async function platformContactsDetailGet(): Promise<unknown> {
91
+ export type PlatformContactsDetailGetQuery = z.input<(typeof schemas)["platformContactsDetailGet"]["query"]>;
92
+ export type PlatformContactsDetailGetData = z.infer<(typeof schemas)["platformContactsDetailGet"]["data"]>;
93
+
94
+ export async function platformContactsDetailGet(query: PlatformContactsDetailGetQuery): Promise<PlatformContactsDetailGetData> {
83
95
  const client = ensureReq();
84
96
  try {
85
- const env = await client.get(`_platform/contacts/detail`).json<OjEnvelope<unknown>>();
97
+ const env = await client.get(`_platform/contacts/detail`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<PlatformContactsDetailGetData>>();
86
98
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
87
99
  if (typeof env.code === "number" && env.code !== 0)
88
100
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
89
- const data = env.data as unknown;
101
+ const data = env.data as PlatformContactsDetailGetData;
102
+ if (import.meta.env.DEV) {
103
+ const { schemas } = await import("./api.schemas");
104
+ const r = schemas["platformContactsDetailGet"].data.safeParse(data);
105
+ if (!r.success)
106
+ throw new ContractApiError(-1, `[契约违例] platformContactsDetailGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
107
+ }
90
108
  return data;
91
109
  }
92
110
  catch (e) {
@@ -402,14 +420,23 @@ export async function platformMarketHistoryGet(): Promise<PlatformMarketHistoryG
402
420
  }
403
421
  }
404
422
 
405
- export async function platformSolutionsRoutesGet(): Promise<unknown> {
423
+ export type PlatformOrgDepartmentsGetQuery = z.input<(typeof schemas)["platformOrgDepartmentsGet"]["query"]>;
424
+ export type PlatformOrgDepartmentsGetData = z.infer<(typeof schemas)["platformOrgDepartmentsGet"]["data"]>;
425
+
426
+ export async function platformOrgDepartmentsGet(query: PlatformOrgDepartmentsGetQuery): Promise<PlatformOrgDepartmentsGetData> {
406
427
  const client = ensureReq();
407
428
  try {
408
- const env = await client.get(`_platform/solutions/routes`).json<OjEnvelope<unknown>>();
429
+ const env = await client.get(`_platform/org/departments`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<PlatformOrgDepartmentsGetData>>();
409
430
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
410
431
  if (typeof env.code === "number" && env.code !== 0)
411
432
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
412
- const data = env.data as unknown;
433
+ const data = env.data as PlatformOrgDepartmentsGetData;
434
+ if (import.meta.env.DEV) {
435
+ const { schemas } = await import("./api.schemas");
436
+ const r = schemas["platformOrgDepartmentsGet"].data.safeParse(data);
437
+ if (!r.success)
438
+ throw new ContractApiError(-1, `[契约违例] platformOrgDepartmentsGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
439
+ }
413
440
  return data;
414
441
  }
415
442
  catch (e) {
@@ -417,14 +444,23 @@ export async function platformSolutionsRoutesGet(): Promise<unknown> {
417
444
  }
418
445
  }
419
446
 
420
- export async function platformSolutionsVerifyPost(): Promise<unknown> {
447
+ export type PlatformOrgIsMemberGetQuery = z.input<(typeof schemas)["platformOrgIsMemberGet"]["query"]>;
448
+ export type PlatformOrgIsMemberGetData = z.infer<(typeof schemas)["platformOrgIsMemberGet"]["data"]>;
449
+
450
+ export async function platformOrgIsMemberGet(query: PlatformOrgIsMemberGetQuery): Promise<PlatformOrgIsMemberGetData> {
421
451
  const client = ensureReq();
422
452
  try {
423
- const env = await client.post(`_platform/solutions/verify`).json<OjEnvelope<unknown>>();
453
+ const env = await client.get(`_platform/org/is-member`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<PlatformOrgIsMemberGetData>>();
424
454
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
425
455
  if (typeof env.code === "number" && env.code !== 0)
426
456
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
427
- const data = env.data as unknown;
457
+ const data = env.data as PlatformOrgIsMemberGetData;
458
+ if (import.meta.env.DEV) {
459
+ const { schemas } = await import("./api.schemas");
460
+ const r = schemas["platformOrgIsMemberGet"].data.safeParse(data);
461
+ if (!r.success)
462
+ throw new ContractApiError(-1, `[契约违例] platformOrgIsMemberGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
463
+ }
428
464
  return data;
429
465
  }
430
466
  catch (e) {
@@ -432,14 +468,23 @@ export async function platformSolutionsVerifyPost(): Promise<unknown> {
432
468
  }
433
469
  }
434
470
 
435
- export async function platformSolutionsVerifyLastGet(): Promise<unknown> {
471
+ export type PlatformOrgMembersGetQuery = z.input<(typeof schemas)["platformOrgMembersGet"]["query"]>;
472
+ export type PlatformOrgMembersGetData = z.infer<(typeof schemas)["platformOrgMembersGet"]["data"]>;
473
+
474
+ export async function platformOrgMembersGet(query: PlatformOrgMembersGetQuery): Promise<PlatformOrgMembersGetData> {
436
475
  const client = ensureReq();
437
476
  try {
438
- const env = await client.get(`_platform/solutions/verify/last`).json<OjEnvelope<unknown>>();
477
+ const env = await client.get(`_platform/org/members`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<PlatformOrgMembersGetData>>();
439
478
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
440
479
  if (typeof env.code === "number" && env.code !== 0)
441
480
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
442
- const data = env.data as unknown;
481
+ const data = env.data as PlatformOrgMembersGetData;
482
+ if (import.meta.env.DEV) {
483
+ const { schemas } = await import("./api.schemas");
484
+ const r = schemas["platformOrgMembersGet"].data.safeParse(data);
485
+ if (!r.success)
486
+ throw new ContractApiError(-1, `[契约违例] platformOrgMembersGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
487
+ }
443
488
  return data;
444
489
  }
445
490
  catch (e) {
@@ -447,22 +492,22 @@ export async function platformSolutionsVerifyLastGet(): Promise<unknown> {
447
492
  }
448
493
  }
449
494
 
450
- export type PlatformOrgTreeGetQuery = z.input<(typeof schemas)["platformOrgTreeGet"]["query"]>;
451
- export type PlatformOrgTreeGetData = z.infer<(typeof schemas)["platformOrgTreeGet"]["data"]>;
495
+ export type PlatformOrgPickerGetQuery = z.input<(typeof schemas)["platformOrgPickerGet"]["query"]>;
496
+ export type PlatformOrgPickerGetData = z.infer<(typeof schemas)["platformOrgPickerGet"]["data"]>;
452
497
 
453
- export async function platformOrgTreeGet(query: PlatformOrgTreeGetQuery): Promise<PlatformOrgTreeGetData> {
498
+ export async function platformOrgPickerGet(query: PlatformOrgPickerGetQuery): Promise<PlatformOrgPickerGetData> {
454
499
  const client = ensureReq();
455
500
  try {
456
- const env = await client.get(`_platform/org/tree`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<PlatformOrgTreeGetData>>();
501
+ const env = await client.get(`_platform/org/picker`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<PlatformOrgPickerGetData>>();
457
502
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
458
503
  if (typeof env.code === "number" && env.code !== 0)
459
504
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
460
- const data = env.data as PlatformOrgTreeGetData;
505
+ const data = env.data as PlatformOrgPickerGetData;
461
506
  if (import.meta.env.DEV) {
462
507
  const { schemas } = await import("./api.schemas");
463
- const r = schemas["platformOrgTreeGet"].data.safeParse(data);
508
+ const r = schemas["platformOrgPickerGet"].data.safeParse(data);
464
509
  if (!r.success)
465
- throw new ContractApiError(-1, `[契约违例] platformOrgTreeGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
510
+ throw new ContractApiError(-1, `[契约违例] platformOrgPickerGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
466
511
  }
467
512
  return data;
468
513
  }
@@ -471,22 +516,23 @@ export async function platformOrgTreeGet(query: PlatformOrgTreeGetQuery): Promis
471
516
  }
472
517
  }
473
518
 
474
- export type PlatformOrgDepartmentsGetQuery = z.input<(typeof schemas)["platformOrgDepartmentsGet"]["query"]>;
475
- export type PlatformOrgDepartmentsGetData = z.infer<(typeof schemas)["platformOrgDepartmentsGet"]["data"]>;
519
+ export type PlatformOrgResolvePostQuery = z.input<(typeof schemas)["platformOrgResolvePost"]["query"]>;
520
+ export type PlatformOrgResolvePostBody = z.input<(typeof schemas)["platformOrgResolvePost"]["body"]>;
521
+ export type PlatformOrgResolvePostData = z.infer<(typeof schemas)["platformOrgResolvePost"]["data"]>;
476
522
 
477
- export async function platformOrgDepartmentsGet(query: PlatformOrgDepartmentsGetQuery): Promise<PlatformOrgDepartmentsGetData> {
523
+ export async function platformOrgResolvePost(input: { query: PlatformOrgResolvePostQuery, body: PlatformOrgResolvePostBody }): Promise<PlatformOrgResolvePostData> {
478
524
  const client = ensureReq();
479
525
  try {
480
- const env = await client.get(`_platform/org/departments`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<PlatformOrgDepartmentsGetData>>();
526
+ const env = await client.post(`_platform/org/resolve`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<PlatformOrgResolvePostData>>();
481
527
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
482
528
  if (typeof env.code === "number" && env.code !== 0)
483
529
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
484
- const data = env.data as PlatformOrgDepartmentsGetData;
530
+ const data = env.data as PlatformOrgResolvePostData;
485
531
  if (import.meta.env.DEV) {
486
532
  const { schemas } = await import("./api.schemas");
487
- const r = schemas["platformOrgDepartmentsGet"].data.safeParse(data);
533
+ const r = schemas["platformOrgResolvePost"].data.safeParse(data);
488
534
  if (!r.success)
489
- throw new ContractApiError(-1, `[契约违例] platformOrgDepartmentsGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
535
+ throw new ContractApiError(-1, `[契约违例] platformOrgResolvePost 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
490
536
  }
491
537
  return data;
492
538
  }
@@ -495,22 +541,22 @@ export async function platformOrgDepartmentsGet(query: PlatformOrgDepartmentsGet
495
541
  }
496
542
  }
497
543
 
498
- export type PlatformOrgMembersGetQuery = z.input<(typeof schemas)["platformOrgMembersGet"]["query"]>;
499
- export type PlatformOrgMembersGetData = z.infer<(typeof schemas)["platformOrgMembersGet"]["data"]>;
544
+ export type PlatformOrgTreeGetQuery = z.input<(typeof schemas)["platformOrgTreeGet"]["query"]>;
545
+ export type PlatformOrgTreeGetData = z.infer<(typeof schemas)["platformOrgTreeGet"]["data"]>;
500
546
 
501
- export async function platformOrgMembersGet(query: PlatformOrgMembersGetQuery): Promise<PlatformOrgMembersGetData> {
547
+ export async function platformOrgTreeGet(query: PlatformOrgTreeGetQuery): Promise<PlatformOrgTreeGetData> {
502
548
  const client = ensureReq();
503
549
  try {
504
- const env = await client.get(`_platform/org/members`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<PlatformOrgMembersGetData>>();
550
+ const env = await client.get(`_platform/org/tree`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<PlatformOrgTreeGetData>>();
505
551
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
506
552
  if (typeof env.code === "number" && env.code !== 0)
507
553
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
508
- const data = env.data as PlatformOrgMembersGetData;
554
+ const data = env.data as PlatformOrgTreeGetData;
509
555
  if (import.meta.env.DEV) {
510
556
  const { schemas } = await import("./api.schemas");
511
- const r = schemas["platformOrgMembersGet"].data.safeParse(data);
557
+ const r = schemas["platformOrgTreeGet"].data.safeParse(data);
512
558
  if (!r.success)
513
- throw new ContractApiError(-1, `[契约违例] platformOrgMembersGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
559
+ throw new ContractApiError(-1, `[契约违例] platformOrgTreeGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
514
560
  }
515
561
  return data;
516
562
  }
@@ -543,22 +589,21 @@ export async function platformOrgUserGet(query: PlatformOrgUserGetQuery): Promis
543
589
  }
544
590
  }
545
591
 
546
- export type PlatformOrgPickerGetQuery = z.input<(typeof schemas)["platformOrgPickerGet"]["query"]>;
547
- export type PlatformOrgPickerGetData = z.infer<(typeof schemas)["platformOrgPickerGet"]["data"]>;
592
+ export type PlatformSolutionsRoutesGetData = z.infer<(typeof schemas)["platformSolutionsRoutesGet"]["data"]>;
548
593
 
549
- export async function platformOrgPickerGet(query: PlatformOrgPickerGetQuery): Promise<PlatformOrgPickerGetData> {
594
+ export async function platformSolutionsRoutesGet(): Promise<PlatformSolutionsRoutesGetData> {
550
595
  const client = ensureReq();
551
596
  try {
552
- const env = await client.get(`_platform/org/picker`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<PlatformOrgPickerGetData>>();
597
+ const env = await client.get(`_platform/solutions/routes`).json<OjEnvelope<PlatformSolutionsRoutesGetData>>();
553
598
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
554
599
  if (typeof env.code === "number" && env.code !== 0)
555
600
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
556
- const data = env.data as PlatformOrgPickerGetData;
601
+ const data = env.data as PlatformSolutionsRoutesGetData;
557
602
  if (import.meta.env.DEV) {
558
603
  const { schemas } = await import("./api.schemas");
559
- const r = schemas["platformOrgPickerGet"].data.safeParse(data);
604
+ const r = schemas["platformSolutionsRoutesGet"].data.safeParse(data);
560
605
  if (!r.success)
561
- throw new ContractApiError(-1, `[契约违例] platformOrgPickerGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
606
+ throw new ContractApiError(-1, `[契约违例] platformSolutionsRoutesGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
562
607
  }
563
608
  return data;
564
609
  }
@@ -567,23 +612,22 @@ export async function platformOrgPickerGet(query: PlatformOrgPickerGetQuery): Pr
567
612
  }
568
613
  }
569
614
 
570
- export type PlatformOrgResolvePostQuery = z.input<(typeof schemas)["platformOrgResolvePost"]["query"]>;
571
- export type PlatformOrgResolvePostBody = z.input<(typeof schemas)["platformOrgResolvePost"]["body"]>;
572
- export type PlatformOrgResolvePostData = z.infer<(typeof schemas)["platformOrgResolvePost"]["data"]>;
615
+ export type PlatformSolutionsVerifyPostQuery = z.input<(typeof schemas)["platformSolutionsVerifyPost"]["query"]>;
616
+ export type PlatformSolutionsVerifyPostData = z.infer<(typeof schemas)["platformSolutionsVerifyPost"]["data"]>;
573
617
 
574
- export async function platformOrgResolvePost(input: { query: PlatformOrgResolvePostQuery, body: PlatformOrgResolvePostBody }): Promise<PlatformOrgResolvePostData> {
618
+ export async function platformSolutionsVerifyPost(query: PlatformSolutionsVerifyPostQuery): Promise<PlatformSolutionsVerifyPostData> {
575
619
  const client = ensureReq();
576
620
  try {
577
- const env = await client.post(`_platform/org/resolve`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<PlatformOrgResolvePostData>>();
621
+ const env = await client.post(`_platform/solutions/verify`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<PlatformSolutionsVerifyPostData>>();
578
622
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
579
623
  if (typeof env.code === "number" && env.code !== 0)
580
624
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
581
- const data = env.data as PlatformOrgResolvePostData;
625
+ const data = env.data as PlatformSolutionsVerifyPostData;
582
626
  if (import.meta.env.DEV) {
583
627
  const { schemas } = await import("./api.schemas");
584
- const r = schemas["platformOrgResolvePost"].data.safeParse(data);
628
+ const r = schemas["platformSolutionsVerifyPost"].data.safeParse(data);
585
629
  if (!r.success)
586
- throw new ContractApiError(-1, `[契约违例] platformOrgResolvePost 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
630
+ throw new ContractApiError(-1, `[契约违例] platformSolutionsVerifyPost 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
587
631
  }
588
632
  return data;
589
633
  }
@@ -592,22 +636,22 @@ export async function platformOrgResolvePost(input: { query: PlatformOrgResolveP
592
636
  }
593
637
  }
594
638
 
595
- export type PlatformOrgIsMemberGetQuery = z.input<(typeof schemas)["platformOrgIsMemberGet"]["query"]>;
596
- export type PlatformOrgIsMemberGetData = z.infer<(typeof schemas)["platformOrgIsMemberGet"]["data"]>;
639
+ export type PlatformSolutionsVerifyLastGetQuery = z.input<(typeof schemas)["platformSolutionsVerifyLastGet"]["query"]>;
640
+ export type PlatformSolutionsVerifyLastGetData = z.infer<(typeof schemas)["platformSolutionsVerifyLastGet"]["data"]>;
597
641
 
598
- export async function platformOrgIsMemberGet(query: PlatformOrgIsMemberGetQuery): Promise<PlatformOrgIsMemberGetData> {
642
+ export async function platformSolutionsVerifyLastGet(query: PlatformSolutionsVerifyLastGetQuery): Promise<PlatformSolutionsVerifyLastGetData> {
599
643
  const client = ensureReq();
600
644
  try {
601
- const env = await client.get(`_platform/org/is-member`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<PlatformOrgIsMemberGetData>>();
645
+ const env = await client.get(`_platform/solutions/verify/last`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<PlatformSolutionsVerifyLastGetData>>();
602
646
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
603
647
  if (typeof env.code === "number" && env.code !== 0)
604
648
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
605
- const data = env.data as PlatformOrgIsMemberGetData;
649
+ const data = env.data as PlatformSolutionsVerifyLastGetData;
606
650
  if (import.meta.env.DEV) {
607
651
  const { schemas } = await import("./api.schemas");
608
- const r = schemas["platformOrgIsMemberGet"].data.safeParse(data);
652
+ const r = schemas["platformSolutionsVerifyLastGet"].data.safeParse(data);
609
653
  if (!r.success)
610
- throw new ContractApiError(-1, `[契约违例] platformOrgIsMemberGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
654
+ throw new ContractApiError(-1, `[契约违例] platformSolutionsVerifyLastGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
611
655
  }
612
656
  return data;
613
657
  }
@@ -0,0 +1,77 @@
1
+ import type { ModuleContext, ModuleI18n } from "@oj-module/runtime";
2
+ import { defineModule } from "@oj-module/runtime";
3
+
4
+ import { routes as homeRoutes } from "./home/routes";
5
+ import { onInit as loginOnInit, routes as loginRoutes } from "./login/routes";
6
+ import { onInit as notificationOnInit, routes as notificationRoutes } from "./notification/routes";
7
+ import { onInit as personalCenterOnInit, routes as personalCenterRoutes } from "./personal-center/routes";
8
+ import { onInit as systemOnInit, routes as systemRoutes } from "./system/routes";
9
+
10
+ /**
11
+ * 应用模块(多根聚合):一个 entry 承载六个业务根,各根独立成一级菜单。
12
+ *
13
+ * 为什么聚合:这六个根(首页/登录/个人中心/通知/系统管理)是脚手架的框架内置页面,
14
+ * 同版本发布、同生命周期、缺一不可,拆成六个模块只会把工程目录、web.config 登记、
15
+ * modules.json 与首屏模块入口请求都放大六倍。聚合后仍是「一目录一模块」,但模块内
16
+ * 按根分目录(`<root>/routes.ts` + `pages/` + `__client/`),页面相对导入不变。
17
+ *
18
+ * 结构约定(多根模块):
19
+ * - 顶层路由首段去重 > 1 → runtime 判为多根(`isMultiRoot`),各根按 entry 里的
20
+ * **绝对路径**原样注册,不被 `routePrefix` 改写——URL 与菜单 key 都不变;
21
+ * - 菜单顺序仍由各根 `handle.order` 决定(≥0 主区升序、<0 底部固定区),与聚合前
22
+ * 逐项一致;改顺序只改 order,不要依赖 routes 数组下标(缺省 order 会按数组下标兜底);
23
+ * - 门禁挂根路由:`/system` 的 `handle.roles` 等价于原先的模块级 requiredRoles。
24
+ *
25
+ * i18n 用 `i18nNamespaces` 沿用各根**原命名空间**(home / login / personal-center /
26
+ * notification / system),因此 `t("system:menu.users")`、`useTranslation("system")`
27
+ * 与菜单标题 `<ns>:menu.<key>` 全部零改动。
28
+ */
29
+ const i18nNamespaces: Record<string, ModuleI18n> = {
30
+ "home": {
31
+ "zh-CN": () => import("./home/locales/zh-CN.json"),
32
+ "en-US": () => import("./home/locales/en-US.json"),
33
+ },
34
+ "login": {
35
+ "zh-CN": () => import("./login/locales/zh-CN.json"),
36
+ "en-US": () => import("./login/locales/en-US.json"),
37
+ },
38
+ "personal-center": {
39
+ "zh-CN": () => import("./personal-center/locales/zh-CN.json"),
40
+ "en-US": () => import("./personal-center/locales/en-US.json"),
41
+ },
42
+ "notification": {
43
+ "zh-CN": () => import("./notification/locales/zh-CN.json"),
44
+ "en-US": () => import("./notification/locales/en-US.json"),
45
+ },
46
+ "system": {
47
+ "zh-CN": () => import("./system/locales/zh-CN.json"),
48
+ "en-US": () => import("./system/locales/en-US.json"),
49
+ },
50
+ };
51
+
52
+ export default defineModule({
53
+ name: "app",
54
+ description: "应用模块:框架内置页面(首页/登录/个人中心/通知/系统管理),多根聚合",
55
+ version: "0.1.0",
56
+ peerRuntime: ">=0.0.0",
57
+ routes: [
58
+ ...homeRoutes,
59
+ ...loginRoutes,
60
+ ...personalCenterRoutes,
61
+ ...notificationRoutes,
62
+ ...systemRoutes,
63
+ ],
64
+ i18nNamespaces,
65
+ lifecycle: {
66
+ /**
67
+ * 各根的 onInit 按原模块加载顺序串行——login 必须最先(认证 provider 要在任何
68
+ * 登录动作前就位),其余是前缀登记/API provider 注入,顺序无相互依赖。
69
+ */
70
+ async onInit(ctx: ModuleContext) {
71
+ await loginOnInit(ctx);
72
+ await personalCenterOnInit(ctx);
73
+ await notificationOnInit(ctx);
74
+ await systemOnInit(ctx);
75
+ },
76
+ },
77
+ });
@@ -17,6 +17,9 @@ export const API_PREFIX = "/home";
17
17
  /** 认领身份前缀(契约 apiPrefix,字面等于模块目录名):跨模块认领护栏对比基准 */
18
18
  export const MODULE_PREFIX = "/home";
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,8 +31,8 @@ export function createHomeClient(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);
@@ -0,0 +1,41 @@
1
+ import type { AppRouteRecordRaw } from "@oj-module/runtime";
2
+
3
+ import { HomeOutlined } from "@ant-design/icons";
4
+
5
+ import { createElement, lazy } from "react";
6
+
7
+ const Home = lazy(() => import("./pages/index"));
8
+
9
+ /**
10
+ * 首页根 `/home`。
11
+ *
12
+ * 为什么必须有:shell 预构建产物把 `VITE_BASE_HOME_PATH` 定为 `/home`
13
+ * (登录成功回跳、logo 点击、tabbar 首页、403/404 回首页都指向它)。
14
+ * 工程缺 `/home` 路由时这些跳转会落到错误边界。
15
+ *
16
+ * 结构对齐 playground-oj/web/src/home;页面用统计卡片 + 折线/柱/饼图
17
+ * 演示宿主共享依赖矩阵(echarts / echarts-for-react / react-countup / dayjs /
18
+ * antd / @ant-design/icons / react-i18next,均由宿主 importmap 提供单例)。
19
+ * 图表数据为静态演示值——真实业务走模块契约 + `ojm api` 生成的 client。
20
+ */
21
+ export const routes: AppRouteRecordRaw[] = [
22
+ {
23
+ path: "/home",
24
+ handle: {
25
+ layout: "container",
26
+ order: 1,
27
+ title: "home:menu.home",
28
+ icon: createElement(HomeOutlined),
29
+ },
30
+ children: [
31
+ {
32
+ index: true,
33
+ Component: Home,
34
+ handle: {
35
+ title: "home:menu.home",
36
+ icon: createElement(HomeOutlined),
37
+ },
38
+ },
39
+ ],
40
+ },
41
+ ];
@@ -54,12 +54,19 @@ export default function LoginPage() {
54
54
  const login = useAuthStore(s => s.login);
55
55
  const [error, setError] = useState<string>();
56
56
  const [loading, setLoading] = useState(false);
57
+ const [known, setKnown] = useState(false);
58
+ const inWecom = typeof navigator !== "undefined" && /wxwork/i.test(navigator.userAgent);
57
59
  const [methods, setMethods] = useState<LoginMethod[]>(["local"]);
58
60
  const [active, setActive] = useState<LoginMethod>("local");
59
61
  // 企业代码入口默认收起:单租户部署(绝大多数)保持今天的登录页外观,
60
62
  // 只有服务商/客户带 `?tenant=` 或手工展开时才需要填。
61
- // 例外:持久化租户不是部署默认值时必须展开——租户头是**隐式**跟着请求走的
63
+ // 例外一:持久化租户不是部署默认值时必须展开——租户头是**隐式**跟着请求走的
62
64
  // (runtime 每请求注入),藏起来等于让上一次的租户无声地把这次登录带走。
65
+ // 例外二:**开了企业微信登录就默认展开**(见下方 effect)——企微租户的键是 corp_id,
66
+ // 一旦有企业接入,「登录哪个租户」就是真问题:平台管理员在 default,而各企业的
67
+ // 管理员在各自的 corp 租户里,收起等于让运维找不到切租户的入口。
68
+ // 判据用「wecom 是否在登录方式里」而不是去数租户:匿名请求没有租户上下文,
69
+ // 统计租户要动用 db.asSystem(全仓仅 tenant-admin.ts 一处,不为此破例)。
63
70
  const [showTenant, setShowTenant] = useState(() => useTenantStore.getState().tenant !== DEFAULT_TENANT);
64
71
  // 表单里的企业代码:带上 persisted 值(URL `?tenant=` 已先落到 store),用户看得见才改得动
65
72
  const [tenant] = useState(() => {
@@ -77,18 +84,31 @@ export default function LoginPage() {
77
84
  // 企微 302 落地参数:只在首次渲染解析一次(ticket 一次性,解析结果必须稳定)
78
85
  const [landing] = useState(() => parseWecomLanding(search));
79
86
  const exchangedRef = useRef(false);
87
+ // 仅「认证中 / 已失败」两态:企微内、回跳落地、或 ?corp 深链即进入认证态,
88
+ // 直接展示「认证中」,不渲染登录表单;只有认证失败(换票失败 / 企微方式不可用)才回落表单。
89
+ const [phase, setPhase] = useState<"auth" | "form">(
90
+ inWecom || landing || urlCorp ? "auth" : "form",
91
+ );
80
92
 
81
93
  useEffect(() => {
82
94
  fetchLoginMethods()
83
95
  .then((r) => {
84
- const m = r.methods.filter(isKnownMethod);
96
+ setKnown(true);
97
+ // 先按 string[] 看一眼:runtime 的返回类型把 wecom 排除在外,而这里恰恰要认它
98
+ const raw: string[] = r.methods;
99
+ const m = raw.filter(isKnownMethod);
85
100
  if (!m.length)
86
101
  return;
87
102
  setMethods(m);
103
+ // 有企微登录 ⇒ 有企业租户(租户键 = corp_id)⇒ 默认展开企业代码,
104
+ // 否则平台/企业两类管理员没法切换登录目标(用户仍可点「收起」手动折叠)。
105
+ if (raw.includes("wecom"))
106
+ setShowTenant(true);
88
107
  if (!m.includes("local"))
89
108
  setActive(m[0]);
90
109
  })
91
110
  .catch(() => {
111
+ setKnown(true);
92
112
  // 发现失败时回落单表单(local),不阻断登录。
93
113
  });
94
114
  }, []);
@@ -110,6 +130,7 @@ export default function LoginPage() {
110
130
  }
111
131
  catch (e) {
112
132
  setError(e instanceof Error ? e.message : String(e));
133
+ setPhase("form");
113
134
  setLoading(false);
114
135
  }
115
136
  })();
@@ -121,13 +142,16 @@ export default function LoginPage() {
121
142
  useEffect(() => {
122
143
  const c = new URLSearchParams(search).get("corp")?.trim() ?? "";
123
144
  // 企微内(wxwork UA)从「应用主页」点开即自动发起授权;带 ?corp= 的深链同样自动发起。
124
- const inWecom = typeof navigator !== "undefined" && /wxwork/i.test(navigator.userAgent);
125
145
  if ((!c && !inWecom) || landing || autoFiredRef.current)
126
146
  return;
127
- if (new URLSearchParams(search).get("error"))
147
+ if (new URLSearchParams(search).get("error")) {
148
+ setPhase("form");
128
149
  return;
129
- if (!methods.includes("wecom"))
150
+ }
151
+ if (known && !methods.includes("wecom")) {
152
+ setPhase("form");
130
153
  return;
154
+ }
131
155
  autoFiredRef.current = true;
132
156
  window.location.assign(`${resolveApiBase()}${wecomAuthorizePath(corp, agent)}`);
133
157
  }, [landing, methods, search, corp, agent]);
@@ -245,6 +269,18 @@ export default function LoginPage() {
245
269
  return form(m);
246
270
  }
247
271
 
272
+ // 认证态:只展示「认证中」,不渲染任何登录表单(满足「只有登录失败才出现 login」)
273
+ if (phase === "auth") {
274
+ return (
275
+ <>
276
+ <Typography.Title level={3}>
277
+ {t("login:page.title")}
278
+ </Typography.Title>
279
+ <Alert style={{ marginBottom: 16 }} type="info" message={t("login:method.wecom.exchanging")} showIcon />
280
+ </>
281
+ );
282
+ }
283
+
248
284
  return (
249
285
  <>
250
286
  <Typography.Title level={3}>
@@ -253,26 +289,20 @@ export default function LoginPage() {
253
289
  <Typography.Paragraph type="secondary">
254
290
  {t("login:page.subtitle")}
255
291
  </Typography.Paragraph>
256
- {landing && loading && (
257
- <Alert style={{ marginBottom: 16 }} type="info" message={t("login:method.wecom.exchanging")} showIcon />
258
- )}
259
292
  {error && <Alert style={{ marginBottom: 16 }} type="error" message={error} showIcon />}
260
- {/* 换票期间不渲染 tab:避免用户在跳转前又点一次登录 */}
261
- {landing && loading
262
- ? null
263
- : showTabs
264
- ? (
265
- <Tabs
266
- activeKey={active}
267
- onChange={key => setActive(key as LoginMethod)}
268
- items={methods.map(m => ({
269
- key: m,
270
- label: t(`login:method.${m}`),
271
- children: panelOf(m),
272
- }))}
273
- />
274
- )
275
- : panelOf(methods[0] ?? "local")}
293
+ {showTabs
294
+ ? (
295
+ <Tabs
296
+ activeKey={active}
297
+ onChange={key => setActive(key as LoginMethod)}
298
+ items={methods.map(m => ({
299
+ key: m,
300
+ label: t(`login:method.${m}`),
301
+ children: panelOf(m),
302
+ }))}
303
+ />
304
+ )
305
+ : panelOf(methods[0] ?? "local")}
276
306
  </>
277
307
  );
278
308
  }