@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
|
@@ -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 !==
|
|
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);
|
|
36
|
-
return { systemDepartmentsGet, systemDepartmentsPost, systemDepartmentsPut, systemDepartmentsDelete,
|
|
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<
|
|
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<
|
|
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
|
|
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<
|
|
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<
|
|
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
|
|
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<
|
|
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<
|
|
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
|
|
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
|
|
598
|
-
export type
|
|
599
|
-
export type
|
|
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
|
|
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/
|
|
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
|
|
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["
|
|
635
|
+
const r = schemas["systemUsersForbiddenPut"].data.safeParse(data);
|
|
612
636
|
if (!r.success)
|
|
613
|
-
throw new ContractApiError(-1, `[契约违例]
|
|
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
|
|
623
|
-
export type
|
|
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
|
|
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/
|
|
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
|
|
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
|
|
641
|
-
export type
|
|
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
|
|
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/
|
|
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
|
|
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<
|
|
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<
|
|
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
|
|
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) {
|
|
@@ -228,6 +228,20 @@
|
|
|
228
228
|
"connectConfirm": "Connect checks: whether the app Secret can fetch a token, whether contacts are readable, and whether any member is visible. Re-run it whenever the Corporation ID / AgentId changed or the Secret was rotated.",
|
|
229
229
|
"connectOk": "Connected: {{depts}} departments visible, {{members}} members in the sample department",
|
|
230
230
|
"connectHint": "After rotating the app Secret you must click \"Verify and connect\" again, otherwise every call fails with 40001. How many people get synced depends on the app's visible scope — set it to the whole company for a full sync."
|
|
231
|
+
},
|
|
232
|
+
"tenantAdmin": {
|
|
233
|
+
"title": "Tenant administrators",
|
|
234
|
+
"hint": "Each enterprise tenant (tenant key = corp ID) gets an admin account automatically on provisioning, used to manage that tenant's org chart, members and roles. It is a tenant-level admin and cannot see other enterprises. The initial password is random and known to no one — to take over, reset a password you know here, hand it to the tenant admin, and they will be forced to change it on first login.",
|
|
235
|
+
"username": "Admin account",
|
|
236
|
+
"state": "State",
|
|
237
|
+
"exists": "Provisioned",
|
|
238
|
+
"missing": "Not provisioned",
|
|
239
|
+
"action": "Action",
|
|
240
|
+
"reset": "Reset password",
|
|
241
|
+
"resetTitle": "Reset tenant admin password",
|
|
242
|
+
"resetHint": "This resets the tenant admin password of enterprise {{corpId}}. Choose the new password yourself and deliver it out-of-band — only a bcrypt hash is stored, so no existing password can ever be shown. The admin must change it on first login, and their existing sessions are revoked immediately.",
|
|
243
|
+
"resetPlaceholder": "New password (at least 6 chars)",
|
|
244
|
+
"resetOk": "Tenant admin password reset"
|
|
231
245
|
}
|
|
232
246
|
},
|
|
233
247
|
"err": {
|
|
@@ -228,6 +228,20 @@
|
|
|
228
228
|
"connectConfirm": "接入会依次校验:应用 Secret 能否换 token、能否读通讯录、可见范围内能否读到人。改过企业ID / AgentId 或换过 Secret 都必须重新点一次。",
|
|
229
229
|
"connectOk": "接入成功:可见范围内 {{depts}} 个部门,示例部门 {{members}} 人",
|
|
230
230
|
"connectHint": "换过应用 Secret 后必须重新「验证并接入」,否则所有调用都会 40001;同步到多少人取决于「应用可见范围」,要全量请设为全公司。"
|
|
231
|
+
},
|
|
232
|
+
"tenantAdmin": {
|
|
233
|
+
"title": "租户管理员",
|
|
234
|
+
"hint": "每个企业租户(租户键=企业ID)在开通时会自动建一个 admin 账号,用于管理本租户的组织架构、成员与角色。它是**租户级**管理员,看不到别家企业。初始口令随机且无人知晓——需要接管时由你在这里重设一个已知口令,交给对方后对方首次登录会被强制改密。",
|
|
235
|
+
"username": "管理员账号",
|
|
236
|
+
"state": "状态",
|
|
237
|
+
"exists": "已开通",
|
|
238
|
+
"missing": "未开通",
|
|
239
|
+
"action": "操作",
|
|
240
|
+
"reset": "重设口令",
|
|
241
|
+
"resetTitle": "重设租户管理员口令",
|
|
242
|
+
"resetHint": "将重设企业 {{corpId}} 的租户管理员口令。新口令请自行设定并线下告知对方——系统只存 bcrypt,不会也无法回显任何已有口令。重设后对方首次登录必须改密,其旧会话立即失效。",
|
|
243
|
+
"resetPlaceholder": "新口令(至少 6 位)",
|
|
244
|
+
"resetOk": "租户管理员口令已重设"
|
|
231
245
|
}
|
|
232
246
|
},
|
|
233
247
|
"err": {
|
|
@@ -19,7 +19,8 @@ type UserRow = SystemUsersGetData[number];
|
|
|
19
19
|
/**
|
|
20
20
|
* 用户管理(C1):GET /system/users 列表 + 建号 / 禁用切换 / 重置口令。
|
|
21
21
|
* 本人行禁用一切自操作(后端 assertNotSelf 400 兜底,前端不给会失败的按钮,R3);
|
|
22
|
-
*
|
|
22
|
+
* 成员资格入口:superadmin 与 **本租户 admin** 均可见(授予 admin 已下放到租户内,
|
|
23
|
+
* 但授予 superadmin 仍只许 superadmin——见 domain/users.ts::assertMembershipAllowed)。
|
|
23
24
|
* 部门列标出主部门,顶部可按部门筛选(含子部门)——筛选在前端做,名单本来就是一页全量。
|
|
24
25
|
*/
|
|
25
26
|
export default function UsersPage() {
|
|
@@ -30,6 +31,8 @@ export default function UsersPage() {
|
|
|
30
31
|
const me = useUserStore(s => s.id);
|
|
31
32
|
const myRoles = useUserStore(s => s.roles);
|
|
32
33
|
const isSuper = myRoles.includes("superadmin");
|
|
34
|
+
// 任命本租户管理员已下放到租户内的 admin(提权到 superadmin 仍只许 superadmin)
|
|
35
|
+
const canManageMembership = isSuper || myRoles.includes("admin");
|
|
33
36
|
|
|
34
37
|
const [createOpen, setCreateOpen] = useState(false);
|
|
35
38
|
const [createForm] = Form.useForm<{ username: string, password: string, displayName?: string, email?: string }>();
|
|
@@ -129,7 +132,7 @@ export default function UsersPage() {
|
|
|
129
132
|
<Button size="small" disabled={String(row.id) === me} onClick={() => setPwdTarget(row)}>
|
|
130
133
|
{t("system:users.resetPwd")}
|
|
131
134
|
</Button>
|
|
132
|
-
{
|
|
135
|
+
{canManageMembership
|
|
133
136
|
? (
|
|
134
137
|
<Button size="small" type="link" onClick={() => navigate(`/system/users/${row.id}/roles`)}>
|
|
135
138
|
{t("system:users.membership")}
|
|
@@ -11,9 +11,13 @@ import { explainError, isForbidden } from "../errors";
|
|
|
11
11
|
import { ForbiddenPanel } from "../forbidden";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* 成员资格(AC1-7
|
|
14
|
+
* 成员资格(AC1-7):整集替换某用户角色。后端无 GET 单用户端点,
|
|
15
15
|
* 当前角色从 GET /system/users 列表按 uid 回显;写走 PUT /system/users/roles?uid=。
|
|
16
|
-
*
|
|
16
|
+
*
|
|
17
|
+
* 可见性已从「superadmin-only」放宽到「本租户 admin 也可以」(见 users/index.tsx 的入口):
|
|
18
|
+
* 授予 admin 已下放到租户内,否则企业租户永远配不出管理员(平台 superadmin 因租户
|
|
19
|
+
* 隔离查不到企业租户的用户)。但 **superadmin 仍只许 superadmin 授**——它握着跨租户
|
|
20
|
+
* 权限,故这里对非 superadmin 直接把该选项置灰,比让他提交后吃 403 清楚。
|
|
17
21
|
*/
|
|
18
22
|
export default function UserRolesPage() {
|
|
19
23
|
const { t } = useTranslation();
|
|
@@ -22,6 +26,8 @@ export default function UserRolesPage() {
|
|
|
22
26
|
const { uid } = useParams();
|
|
23
27
|
const queryClient = useQueryClient();
|
|
24
28
|
const me = useUserStore(s => s.id);
|
|
29
|
+
const myRoles = useUserStore(s => s.roles);
|
|
30
|
+
const isSuper = myRoles.includes("superadmin");
|
|
25
31
|
|
|
26
32
|
// null = 尚未改动,回显服务端值;一旦勾选即持本地整集
|
|
27
33
|
const [selected, setSelected] = useState<string[] | null>(null);
|
|
@@ -51,7 +57,12 @@ export default function UserRolesPage() {
|
|
|
51
57
|
<Card title={`${t("system:userRoles.title")}: ${user?.username ?? `#${uid}`}`}>
|
|
52
58
|
<Space direction="vertical">
|
|
53
59
|
<Checkbox.Group
|
|
54
|
-
options={(rolesQuery.data ?? []).map(r => ({
|
|
60
|
+
options={(rolesQuery.data ?? []).map(r => ({
|
|
61
|
+
label: `${r.name}${r.deprecated ? "(已废弃)" : ""}`,
|
|
62
|
+
value: r.name,
|
|
63
|
+
// 非 superadmin 授不了平台级角色:置灰,提交后 403 太晚也太含糊
|
|
64
|
+
disabled: r.name === "superadmin" && !isSuper,
|
|
65
|
+
}))}
|
|
55
66
|
value={value}
|
|
56
67
|
onChange={v => setSelected(v)}
|
|
57
68
|
/>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { request, useUserStore } from "@oj-module/runtime";
|
|
2
2
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
|
-
import { Alert, App, Button, Card, Descriptions, Popconfirm, QRCode, Space, Table, Tag, Typography } from "antd";
|
|
3
|
+
import { Alert, App, Button, Card, Descriptions, Input, Modal, Popconfirm, QRCode, Space, Table, Tag, Typography } from "antd";
|
|
4
4
|
import { useEffect, useState } from "react";
|
|
5
5
|
import { useTranslation } from "react-i18next";
|
|
6
6
|
|
|
@@ -47,6 +47,16 @@ interface ConnectResult {
|
|
|
47
47
|
members: number
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/** GET /auth/wecom/admin/tenant-admin:某企业的租户管理员状态 */
|
|
51
|
+
interface TenantAdminState {
|
|
52
|
+
corpId: string
|
|
53
|
+
username: string
|
|
54
|
+
/** 该租户是否已有一个握着 admin 角色的账号 */
|
|
55
|
+
exists: boolean
|
|
56
|
+
/** 是否须改密(开通后恒 true,直到本人改过一次) */
|
|
57
|
+
mustChange: boolean
|
|
58
|
+
}
|
|
59
|
+
|
|
50
60
|
/**
|
|
51
61
|
* 端点不在本模块的生成 client 里(`__client` 按模块切分),故直接用 runtime 的 ky 出口。
|
|
52
62
|
* 路径不带前导 `/`:与 request 的 baseURL 拼接口径一致(fetchLogin 同款)。
|
|
@@ -80,6 +90,24 @@ async function connectCorp(): Promise<ConnectResult> {
|
|
|
80
90
|
return env.data;
|
|
81
91
|
}
|
|
82
92
|
|
|
93
|
+
async function fetchTenantAdmin(corpId: string): Promise<TenantAdminState> {
|
|
94
|
+
const env = await request.get("auth/wecom/admin/tenant-admin", { searchParams: { corpId } }).json<{ code: number, data?: TenantAdminState }>();
|
|
95
|
+
return env.data ?? { corpId, username: "admin", exists: false, mustChange: false };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 重设租户管理员口令。
|
|
100
|
+
*
|
|
101
|
+
* 口令**不由系统生成**:让 superadmin 自己输入,他知道这个值 → 直接告知对方即可。
|
|
102
|
+
* 后端只存 bcrypt,没有任何可逆副本,所以「系统生成后回显」这条路本来也走不通。
|
|
103
|
+
*/
|
|
104
|
+
async function resetTenantAdminPassword(input: { corpId: string, password: string }): Promise<TenantAdminState> {
|
|
105
|
+
const env = await request.post("auth/wecom/admin/tenant-admin/password", { json: input }).json<{ code: number, data?: TenantAdminState }>();
|
|
106
|
+
if (!env.data)
|
|
107
|
+
throw new Error("企业微信未返回重设结果");
|
|
108
|
+
return env.data;
|
|
109
|
+
}
|
|
110
|
+
|
|
83
111
|
/**
|
|
84
112
|
* 企微后台(superadmin 独占),两块卡片按「本部署配了哪套凭据」决定显不显示:
|
|
85
113
|
* · 代开发:已授权企业 × 应用清单、启停、开通入口(点「生成开通链接」→ 后端调
|
|
@@ -155,6 +183,80 @@ export default function WecomAdminPage() {
|
|
|
155
183
|
onError: e => message.error(explainError(e, t)),
|
|
156
184
|
});
|
|
157
185
|
|
|
186
|
+
// 企业清单按 corp_id 去重:租户键就是 corp_id,同一企业的多个应用共用一个租户管理员
|
|
187
|
+
const corps = [...new Map((appsQuery.data ?? []).map(r => [r.corp_id, { corpId: r.corp_id, name: r.corp_name }])).values()];
|
|
188
|
+
|
|
189
|
+
// 租户管理员状态:一个企业一条查询并并发取(企业数量级很小,不值得为它加批量端点)
|
|
190
|
+
const tenantAdminQuery = useQuery({
|
|
191
|
+
queryKey: ["system", "wecom-tenant-admin", corps.map(c => c.corpId).join(",")],
|
|
192
|
+
queryFn: async () => {
|
|
193
|
+
const out: Record<string, TenantAdminState> = {};
|
|
194
|
+
await Promise.all(corps.map(async (c) => {
|
|
195
|
+
out[c.corpId] = await fetchTenantAdmin(c.corpId);
|
|
196
|
+
}));
|
|
197
|
+
return out;
|
|
198
|
+
},
|
|
199
|
+
enabled: isSuper && corps.length > 0,
|
|
200
|
+
retry: false,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const [resetTarget, setResetTarget] = useState<{ corpId: string, name: string | null } | null>(null);
|
|
204
|
+
const [resetPwd, setResetPwd] = useState("");
|
|
205
|
+
|
|
206
|
+
const resetMut = useMutation({
|
|
207
|
+
mutationFn: resetTenantAdminPassword,
|
|
208
|
+
onSuccess: () => {
|
|
209
|
+
message.success(t("system:wecom.tenantAdmin.resetOk"));
|
|
210
|
+
setResetTarget(null);
|
|
211
|
+
setResetPwd("");
|
|
212
|
+
queryClient.invalidateQueries({ queryKey: ["system", "wecom-tenant-admin"] });
|
|
213
|
+
},
|
|
214
|
+
onError: e => message.error(explainError(e, t)),
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const tenantAdminColumns = [
|
|
218
|
+
{
|
|
219
|
+
title: t("system:wecom.col.corp"),
|
|
220
|
+
dataIndex: "corpId",
|
|
221
|
+
render: (_: string, row: { corpId: string, name: string | null }) => (
|
|
222
|
+
<div>
|
|
223
|
+
<div>{row.corpId}</div>
|
|
224
|
+
{row.name && <Typography.Text type="secondary">{row.name}</Typography.Text>}
|
|
225
|
+
</div>
|
|
226
|
+
),
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
title: t("system:wecom.tenantAdmin.username"),
|
|
230
|
+
dataIndex: "username",
|
|
231
|
+
render: (_: unknown, row: { corpId: string }) => tenantAdminQuery.data?.[row.corpId]?.username ?? "-",
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
title: t("system:wecom.tenantAdmin.state"),
|
|
235
|
+
dataIndex: "exists",
|
|
236
|
+
render: (_: unknown, row: { corpId: string }) => {
|
|
237
|
+
const s = tenantAdminQuery.data?.[row.corpId];
|
|
238
|
+
return s?.exists
|
|
239
|
+
? <Tag color="green">{t("system:wecom.tenantAdmin.exists")}</Tag>
|
|
240
|
+
: <Tag color="orange">{t("system:wecom.tenantAdmin.missing")}</Tag>;
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
title: t("system:wecom.tenantAdmin.action"),
|
|
245
|
+
key: "action",
|
|
246
|
+
render: (_: unknown, row: { corpId: string, name: string | null }) => (
|
|
247
|
+
<Button
|
|
248
|
+
size="small"
|
|
249
|
+
onClick={() => {
|
|
250
|
+
setResetTarget(row);
|
|
251
|
+
setResetPwd("");
|
|
252
|
+
}}
|
|
253
|
+
>
|
|
254
|
+
{t("system:wecom.tenantAdmin.reset")}
|
|
255
|
+
</Button>
|
|
256
|
+
),
|
|
257
|
+
},
|
|
258
|
+
];
|
|
259
|
+
|
|
158
260
|
if (!isSuper)
|
|
159
261
|
return <ForbiddenPanel t={t} />;
|
|
160
262
|
|
|
@@ -319,6 +421,42 @@ export default function WecomAdminPage() {
|
|
|
319
421
|
</Typography.Paragraph>
|
|
320
422
|
</Card>
|
|
321
423
|
)}
|
|
424
|
+
{corps.length > 0 && (
|
|
425
|
+
<Card title={t("system:wecom.tenantAdmin.title")}>
|
|
426
|
+
<Typography.Paragraph type="secondary">
|
|
427
|
+
{t("system:wecom.tenantAdmin.hint")}
|
|
428
|
+
</Typography.Paragraph>
|
|
429
|
+
<Table
|
|
430
|
+
rowKey={row => row.corpId}
|
|
431
|
+
size="small"
|
|
432
|
+
loading={tenantAdminQuery.isLoading}
|
|
433
|
+
dataSource={corps}
|
|
434
|
+
columns={tenantAdminColumns}
|
|
435
|
+
pagination={false}
|
|
436
|
+
/>
|
|
437
|
+
</Card>
|
|
438
|
+
)}
|
|
439
|
+
<Modal
|
|
440
|
+
open={resetTarget != null}
|
|
441
|
+
title={t("system:wecom.tenantAdmin.resetTitle")}
|
|
442
|
+
onCancel={() => {
|
|
443
|
+
setResetTarget(null);
|
|
444
|
+
setResetPwd("");
|
|
445
|
+
}}
|
|
446
|
+
onOk={() => resetMut.mutate({ corpId: resetTarget!.corpId, password: resetPwd })}
|
|
447
|
+
confirmLoading={resetMut.isPending}
|
|
448
|
+
okButtonProps={{ disabled: resetPwd.length < 6 }}
|
|
449
|
+
destroyOnHidden
|
|
450
|
+
>
|
|
451
|
+
<Typography.Paragraph type="secondary">
|
|
452
|
+
{t("system:wecom.tenantAdmin.resetHint", { corpId: resetTarget?.corpId ?? "" })}
|
|
453
|
+
</Typography.Paragraph>
|
|
454
|
+
<Input.Password
|
|
455
|
+
value={resetPwd}
|
|
456
|
+
onChange={e => setResetPwd(e.target.value)}
|
|
457
|
+
placeholder={t("system:wecom.tenantAdmin.resetPlaceholder")}
|
|
458
|
+
/>
|
|
459
|
+
</Modal>
|
|
322
460
|
<Card title={t("system:wecom.appsTitle")}>
|
|
323
461
|
<Typography.Paragraph type="secondary" style={{ marginBottom: 12 }}>
|
|
324
462
|
{t("system:wecom.authModeHint")}
|