@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.
- package/devkit/SKILL.md +2 -2
- package/devkit/manual.md +32 -25
- package/devkit/wecom-internal-app.md +111 -4
- package/package.json +2 -2
- package/shell-dist/assets/{index-FYt9CjFd.js → index-e_Mp2mb5.js} +5 -1
- package/shell-dist/assets/runtime.js +29 -5
- package/shell-dist/index.html +9 -12
- package/shell-dist/versions.json +3 -3
- package/src/__tests__/templates-tenant-scope.test.ts +3 -2
- package/src/build.ts +19 -2
- package/src/config.ts +9 -0
- package/src/contract/__tests__/client-path.test.ts +30 -0
- package/src/contract/__tests__/emit-client.test.ts +18 -0
- package/src/contract/__tests__/handler-scan.test.ts +23 -0
- package/src/contract/check.ts +5 -5
- package/src/contract/client-path.ts +30 -0
- package/src/contract/emit-client.ts +18 -4
- package/src/contract/handler-scan.ts +40 -3
- package/src/contract/run.ts +5 -3
- package/src/init.ts +19 -1
- package/src/shared-deps.ts +27 -9
- package/src/site-base.ts +1 -1
- package/templates/README.md +40 -44
- package/templates/init.include.yaml +15 -14
- package/templates/openapi.json +3422 -1955
- package/templates/src/_platform/api/_shared/service/domain/users.ts +40 -4
- package/templates/src/_platform/api/contacts/api.ts +29 -1
- package/templates/src/_platform/api/contacts/detail/api.ts +25 -1
- package/templates/src/_platform/api/solutions/api.ts +60 -1
- package/templates/src/_platform/web/__client/api.schemas.ts +99 -35
- package/templates/src/_platform/web/__client/api.ts +103 -59
- package/templates/src/app/web/entry.ts +77 -0
- package/templates/src/{home/web → app/web/home}/__client/api.ts +5 -2
- package/templates/src/app/web/home/routes.ts +41 -0
- package/templates/src/{login/web → app/web/login}/pages/login.tsx +54 -24
- package/templates/src/app/web/login/routes.ts +71 -0
- package/templates/src/{notification/web → app/web/notification}/__client/api.schemas.ts +14 -0
- package/templates/src/{notification/web → app/web/notification}/__client/api.ts +67 -20
- package/templates/src/app/web/notification/routes.ts +70 -0
- package/templates/src/{personal-center/web → app/web/personal-center}/__client/api.ts +5 -2
- package/templates/src/app/web/personal-center/routes.ts +49 -0
- package/templates/src/{system/web → app/web/system}/__client/api.schemas.ts +28 -22
- package/templates/src/{system/web → app/web/system}/__client/api.ts +103 -58
- package/templates/src/{system/web → app/web/system}/locales/en-US.json +14 -0
- package/templates/src/{system/web → app/web/system}/locales/zh-CN.json +14 -0
- package/templates/src/{system/web → app/web/system}/pages/users/index.tsx +5 -2
- package/templates/src/{system/web → app/web/system}/pages/users/roles.tsx +14 -3
- package/templates/src/{system/web → app/web/system}/pages/wecom/index.tsx +139 -1
- package/templates/src/app/web/system/routes.ts +97 -0
- package/templates/src/auth/api/_shared/wecom/admin.ts +4 -0
- package/templates/src/auth/api/_shared/wecom/client.ts +9 -2
- package/templates/src/auth/api/_shared/wecom/events.ts +3 -0
- package/templates/src/auth/api/_shared/wecom/tenant-admin.ts +194 -0
- package/templates/src/auth/api/api.ts +59 -0
- package/templates/src/auth/api/ldap-login/api.ts +37 -1
- package/templates/src/auth/api/wecom/admin/tenant-admin/api.ts +41 -0
- package/templates/src/auth/api/wecom/admin/tenant-admin/password/api.ts +66 -0
- package/templates/src/auth/api/wecom/api.ts +23 -0
- package/templates/src/auth/web/__client/api.schemas.ts +133 -13
- package/templates/src/auth/web/__client/api.ts +129 -31
- package/templates/src/notification/api/api.ts +17 -0
- package/templates/src/notification/api/del/api.ts +2 -0
- package/templates/src/system/api/roles/api.ts +3 -0
- package/templates/src/system/api/roles/bindings/api.ts +3 -0
- package/templates/src/system/api/settings/api.ts +6 -3
- package/templates/src/system/api/users/forbidden/api.ts +3 -0
- package/templates/src/system/api/users/password/api.ts +3 -0
- package/templates/src/system/api/users/roles/api.ts +3 -0
- package/templates/src/web/api/get-async-routes/api.ts +16 -1
- package/templates/src/web/api/hello/api.ts +13 -1
- package/templates/src/web/api/user-info/api.ts +21 -1
- package/templates/src/web/web/__client/api.schemas.ts +28 -0
- package/templates/src/web/web/__client/api.ts +33 -9
- package/templates/tsconfig.paths.json +0 -1
- package/templates/web.config.ts +22 -17
- package/vendor/host-versions.json +3 -3
- package/shell-dist/assets/echarts-charts.js +0 -58571
- package/shell-dist/assets/echarts-features.js +0 -24057
- package/shell-dist/assets/echarts-for-react.js +0 -704
- package/shell-dist/assets/echarts.js +0 -82916
- package/shell-dist/assets/editor.js +0 -161457
- package/shell-dist/assets/vditor-index-css.js +0 -7
- package/shell-dist/assets/vditor.js +0 -15223
- package/templates/src/demo/api/manifest.yaml +0 -5
- package/templates/src/demo/api/migrations/0001__create_todos.sql +0 -9
- package/templates/src/demo/api/migrations/0002__tenant.mysql.sql +0 -2
- package/templates/src/demo/api/migrations/0002__tenant.sql +0 -2
- package/templates/src/demo/api/schema.yaml +0 -12
- package/templates/src/demo/api/seed.sql +0 -5
- package/templates/src/demo/api/todos/api.ts +0 -59
- package/templates/src/demo/web/__client/api.schemas.ts +0 -20
- package/templates/src/demo/web/__client/api.ts +0 -89
- package/templates/src/demo/web/entry.ts +0 -42
- package/templates/src/demo/web/locales/en-US.json +0 -10
- package/templates/src/demo/web/locales/zh-CN.json +0 -10
- package/templates/src/demo/web/pages/index.tsx +0 -45
- package/templates/src/home/web/entry.ts +0 -49
- package/templates/src/login/web/entry.ts +0 -79
- package/templates/src/notification/web/entry.ts +0 -84
- package/templates/src/personal-center/web/entry.ts +0 -60
- package/templates/src/system/web/entry.ts +0 -108
- /package/templates/src/{home/web → app/web/home}/__client/api.schemas.ts +0 -0
- /package/templates/src/{home/web → app/web/home}/locales/en-US.json +0 -0
- /package/templates/src/{home/web → app/web/home}/locales/zh-CN.json +0 -0
- /package/templates/src/{home/web → app/web/home}/pages/components/bar-chart.tsx +0 -0
- /package/templates/src/{home/web → app/web/home}/pages/components/card-list.tsx +0 -0
- /package/templates/src/{home/web → app/web/home}/pages/components/line-chart.tsx +0 -0
- /package/templates/src/{home/web → app/web/home}/pages/components/pie-chart.tsx +0 -0
- /package/templates/src/{home/web → app/web/home}/pages/index.tsx +0 -0
- /package/templates/src/{login/web → app/web/login}/auth-provider.ts +0 -0
- /package/templates/src/{login/web → app/web/login}/locales/en-US.json +0 -0
- /package/templates/src/{login/web → app/web/login}/locales/zh-CN.json +0 -0
- /package/templates/src/{login/web → app/web/login}/pages/change-password.tsx +0 -0
- /package/templates/src/{login/web → app/web/login}/wecom.ts +0 -0
- /package/templates/src/{notification/web → app/web/notification}/locales/en-US.json +0 -0
- /package/templates/src/{notification/web → app/web/notification}/locales/zh-CN.json +0 -0
- /package/templates/src/{notification/web → app/web/notification}/pages/center/index.tsx +0 -0
- /package/templates/src/{personal-center/web → app/web/personal-center}/__client/api.schemas.ts +0 -0
- /package/templates/src/{personal-center/web → app/web/personal-center}/locales/en-US.json +0 -0
- /package/templates/src/{personal-center/web → app/web/personal-center}/locales/zh-CN.json +0 -0
- /package/templates/src/{personal-center/web → app/web/personal-center}/pages/my-profile/index.tsx +0 -0
- /package/templates/src/{system/web → app/web/system}/pages/departments/index.tsx +0 -0
- /package/templates/src/{system/web → app/web/system}/pages/departments/sync-bar.tsx +0 -0
- /package/templates/src/{system/web → app/web/system}/pages/departments/tree.ts +0 -0
- /package/templates/src/{system/web → app/web/system}/pages/errors.ts +0 -0
- /package/templates/src/{system/web → app/web/system}/pages/forbidden.tsx +0 -0
- /package/templates/src/{system/web → app/web/system}/pages/login-methods/index.tsx +0 -0
- /package/templates/src/{system/web → app/web/system}/pages/oplog/index.tsx +0 -0
- /package/templates/src/{system/web → app/web/system}/pages/roles/index.tsx +0 -0
- /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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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/
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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/
|
|
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
|
|
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
|
|
451
|
-
export type
|
|
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
|
|
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/
|
|
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
|
|
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["
|
|
508
|
+
const r = schemas["platformOrgPickerGet"].data.safeParse(data);
|
|
464
509
|
if (!r.success)
|
|
465
|
-
throw new ContractApiError(-1, `[契约违例]
|
|
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
|
|
475
|
-
export type
|
|
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
|
|
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.
|
|
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
|
|
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["
|
|
533
|
+
const r = schemas["platformOrgResolvePost"].data.safeParse(data);
|
|
488
534
|
if (!r.success)
|
|
489
|
-
throw new ContractApiError(-1, `[契约违例]
|
|
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
|
|
499
|
-
export type
|
|
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
|
|
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/
|
|
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
|
|
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["
|
|
557
|
+
const r = schemas["platformOrgTreeGet"].data.safeParse(data);
|
|
512
558
|
if (!r.success)
|
|
513
|
-
throw new ContractApiError(-1, `[契约违例]
|
|
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
|
|
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
|
|
594
|
+
export async function platformSolutionsRoutesGet(): Promise<PlatformSolutionsRoutesGetData> {
|
|
550
595
|
const client = ensureReq();
|
|
551
596
|
try {
|
|
552
|
-
const env = await client.get(`_platform/
|
|
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
|
|
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["
|
|
604
|
+
const r = schemas["platformSolutionsRoutesGet"].data.safeParse(data);
|
|
560
605
|
if (!r.success)
|
|
561
|
-
throw new ContractApiError(-1, `[契约违例]
|
|
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
|
|
571
|
-
export type
|
|
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
|
|
618
|
+
export async function platformSolutionsVerifyPost(query: PlatformSolutionsVerifyPostQuery): Promise<PlatformSolutionsVerifyPostData> {
|
|
575
619
|
const client = ensureReq();
|
|
576
620
|
try {
|
|
577
|
-
const env = await client.post(`_platform/
|
|
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
|
|
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["
|
|
628
|
+
const r = schemas["platformSolutionsVerifyPost"].data.safeParse(data);
|
|
585
629
|
if (!r.success)
|
|
586
|
-
throw new ContractApiError(-1, `[契约违例]
|
|
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
|
|
596
|
-
export type
|
|
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
|
|
642
|
+
export async function platformSolutionsVerifyLastGet(query: PlatformSolutionsVerifyLastGetQuery): Promise<PlatformSolutionsVerifyLastGetData> {
|
|
599
643
|
const client = ensureReq();
|
|
600
644
|
try {
|
|
601
|
-
const env = await client.get(`_platform/
|
|
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
|
|
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["
|
|
652
|
+
const r = schemas["platformSolutionsVerifyLastGet"].data.safeParse(data);
|
|
609
653
|
if (!r.success)
|
|
610
|
-
throw new ContractApiError(-1, `[契约违例]
|
|
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 !==
|
|
32
|
-
console.warn(`[ojm-api] 模块 "${ctx.module.name}" 正在认领 ${MODULE_PREFIX} 的 client——跨模块认领会共享 request 单槽、卸载互相耦合;应由 "${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
}
|