@oj-module/cli 0.2.1 → 0.2.2
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/package.json +2 -2
- package/shell-dist/assets/runtime.js +2 -2
- package/shell-dist/index.html +2 -2
- package/shell-dist/versions.json +3 -3
- package/templates/README.md +46 -7
- package/templates/web/src/system/__client/api.schemas.ts +150 -0
- package/templates/web/src/system/__client/api.ts +358 -0
- package/templates/web.config.ts +2 -2
- package/vendor/host-versions.json +3 -3
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oj-module/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.2",
|
|
5
5
|
"description": "React Antd Admin 模块工程命令行工具(含预构建宿主 shell-dist)",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"exports": {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
],
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@faker-js/faker": "^10.4.0",
|
|
52
|
-
"@oj-module/runtime": "0.2.
|
|
52
|
+
"@oj-module/runtime": "0.2.2",
|
|
53
53
|
"@redocly/cli": "^2.51.0",
|
|
54
54
|
"@tailwindcss/vite": "^4.3.0",
|
|
55
55
|
"esbuild": "^0.28.2",
|
|
@@ -255,10 +255,10 @@ function getAppInfo() {
|
|
|
255
255
|
return {
|
|
256
256
|
"pkg": {
|
|
257
257
|
"name": "@oj-module/runtime",
|
|
258
|
-
"version": "0.2.
|
|
258
|
+
"version": "0.2.2",
|
|
259
259
|
"license": "MIT"
|
|
260
260
|
},
|
|
261
|
-
"lastBuildTime": "2026-10-16
|
|
261
|
+
"lastBuildTime": "2026-10-16 22:08:56"
|
|
262
262
|
};
|
|
263
263
|
}
|
|
264
264
|
var init_get_app_info = __esmMin((() => {}));
|
package/shell-dist/index.html
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" href="/favicon.ico" />
|
|
7
7
|
<title>React Antd Admin · Shell</title>
|
|
8
|
-
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' https://modules.cdn.example.com 'wasm-unsafe-eval' 'nonce-
|
|
8
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' https://modules.cdn.example.com 'wasm-unsafe-eval' 'nonce-ELLiP0aL4p7v4pSj08o8IQ=='; script-src-attr 'none'; connect-src 'self' data: https://modules.cdn.example.com; style-src 'self' https://modules.cdn.example.com 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; frame-src https://ant.design https://react.dev https://condorheroblog.github.io; form-action 'none'; base-uri 'none'; object-src 'none'; upgrade-insecure-requests" />
|
|
9
9
|
|
|
10
|
-
<script type="importmap" nonce="
|
|
10
|
+
<script type="importmap" nonce="ELLiP0aL4p7v4pSj08o8IQ==">{
|
|
11
11
|
"imports": {
|
|
12
12
|
"react": "/assets/react.js",
|
|
13
13
|
"react/jsx-runtime": "/assets/jsx-runtime.js",
|
package/shell-dist/versions.json
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
"react-router": "7.18.2",
|
|
8
8
|
"react-router/dom": "7.18.2",
|
|
9
9
|
"@tanstack/react-query": "5.102.8",
|
|
10
|
-
"@oj-module/runtime": "0.2.
|
|
11
|
-
"@oj-module/runtime/contract": "0.2.
|
|
12
|
-
"@oj-module/runtime/contract/errors": "0.2.
|
|
10
|
+
"@oj-module/runtime": "0.2.2",
|
|
11
|
+
"@oj-module/runtime/contract": "0.2.2",
|
|
12
|
+
"@oj-module/runtime/contract/errors": "0.2.2",
|
|
13
13
|
"@plane/kit": "0.1.20",
|
|
14
14
|
"@plane/editor": "0.1.20",
|
|
15
15
|
"antd": "6.6.1",
|
package/templates/README.md
CHANGED
|
@@ -16,17 +16,19 @@ pnpm preview # 预览:oj migrate + server(release 产物)
|
|
|
16
16
|
## 系统管理模块
|
|
17
17
|
|
|
18
18
|
`web/src/system/`(用户管理 / 成员资格 / 角色权限 / 操作日志 / 站点设置 / 登录方式)
|
|
19
|
-
的前端 API 客户端 `__client/`
|
|
19
|
+
的前端 API 客户端 `__client/` **已随模板分发**(与 auth/notification 等一致),`pnpm dev`
|
|
20
|
+
开箱即用、「系统管理」菜单直接可见。
|
|
21
|
+
|
|
22
|
+
`__client` 是从 `openapi.json`(模板自带,由 `api/src/system` 契约产出)生成的产物;
|
|
23
|
+
契约变更后刷新一次即可:
|
|
20
24
|
|
|
21
25
|
```bash
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
pnpm exec ojm api # 生成 web/src/*/__client(dev 检测到 openapi.json 变化也会自动重生成)
|
|
26
|
+
bin/oj openapi -c config.yaml -d api/src -b /api -o openapi.json # 重产契约
|
|
27
|
+
pnpm exec ojm api # 重新生成 web/src/*/__client(dev 检测到 openapi.json 变化也会自动重生成)
|
|
25
28
|
```
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
(`ojm api --check` 可对账,零漂移为发布门禁)。成员资格页仅 superadmin 可见。
|
|
30
|
+
成员资格页仅 superadmin 可见。改了 `api/src` 的 handler `.schema`/`.response` 后重跑这两条保持同步
|
|
31
|
+
(`ojm api --check` 可对账,零漂移为发布门禁)。
|
|
30
32
|
|
|
31
33
|
## 目录
|
|
32
34
|
|
|
@@ -46,3 +48,40 @@ web/dist/ 构建产物(完整站点)
|
|
|
46
48
|
手册按章节按需读。
|
|
47
49
|
- `api/config.yaml` 的证书为 init 现场签发的本地 dev 证书,不构成信任边界;
|
|
48
50
|
生产环境用 oj 仓库 `oj-cert gen` 全新签发并替换公钥。
|
|
51
|
+
|
|
52
|
+
## 排障 / 常见问题
|
|
53
|
+
|
|
54
|
+
- **`pnpm dev` / `pnpm build` 报 `Could not resolve "./__client/api"`**
|
|
55
|
+
某前端模块的 `entry.ts` 引用了 `web/src/<模块>/__client/api`,但该目录不存在——它是
|
|
56
|
+
`ojm api` 从 `openapi.json` 生成的产物。在工程根跑一次即可补齐:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pnpm exec ojm api # 生成 web/src/*/__client(dev 检测到 openapi.json 变化也会自动重生成)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
改完后端 `api/src` 的契约后,重产契约再刷新:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
bin/oj openapi -c config.yaml -d api/src -b /api -o openapi.json
|
|
66
|
+
pnpm exec ojm api
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- **`__client/` 下的文件能直接改吗?**
|
|
70
|
+
不能手改、也不入库——它是生成物,下次 `ojm api` 会覆盖你的手改。需要改接口就改
|
|
71
|
+
`api/src` 的 handler `.schema`/`.response`,再跑上面的命令刷新。
|
|
72
|
+
`ojm api --check` 可对账契约与产物的漂移(零漂移为发布门禁)。
|
|
73
|
+
|
|
74
|
+
- **`pnpm dlx @oj-module/cli@latest init` 生成的脚手架缺模块 / 看起来很旧**
|
|
75
|
+
pnpm dlx 会按首次解析到的版本**缓存** CLI 可执行文件。若某次 `@latest` 指向的是旧版
|
|
76
|
+
(例如早于系统管理模块的 `0.1.x`),之后重跑仍可能命中旧缓存,生成的脚手架就会缺新模块、
|
|
77
|
+
模板偏旧(典型表现:`web/src` 下没有 `system` 目录)。
|
|
78
|
+
排查:`ls web/src` 看是否缺预期模块。
|
|
79
|
+
解决:清掉 dlx 缓存后重跑——
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
rm -rf ~/Library/Caches/pnpm/dlx # macOS;其它平台见 pnpm 文档的 dlx-store 路径
|
|
83
|
+
# 或:pnpm dlx clear @oj-module/cli
|
|
84
|
+
pnpm dlx @oj-module/cli@<明确版本> init <工程名> --yes # 显式指定版本最稳
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
养成显式带版本号(如 `@oj-module/cli@0.2.2`)的习惯,可避免 `@latest` 缓存歧义。
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// 生成物:ojm api 从契约生成,勿手改(改动请改契约文件后重跑 ojm api)
|
|
3
|
+
// AC-D15:仅供 DEV 校验动态 import,生产构建不进产物
|
|
4
|
+
import { z } from "@oj-module/runtime";
|
|
5
|
+
|
|
6
|
+
export const schemas = {
|
|
7
|
+
"systemLoginMethodsGet": {
|
|
8
|
+
data: z.object({
|
|
9
|
+
"methods": z.array(z.string())
|
|
10
|
+
}),
|
|
11
|
+
},
|
|
12
|
+
"systemOplogGet": {
|
|
13
|
+
query: z.object({
|
|
14
|
+
"action": z.string().optional(),
|
|
15
|
+
"username": z.string().optional(),
|
|
16
|
+
"module": z.string().optional(),
|
|
17
|
+
"limit": z.number().min(1).max(200).optional()
|
|
18
|
+
}),
|
|
19
|
+
data: z.object({
|
|
20
|
+
"list": z.array(z.object({
|
|
21
|
+
"id": z.number(),
|
|
22
|
+
"create_time": z.number(),
|
|
23
|
+
"user_id": z.number(),
|
|
24
|
+
"username": z.string(),
|
|
25
|
+
"action": z.string(),
|
|
26
|
+
"module": z.string(),
|
|
27
|
+
"version": z.string().nullable(),
|
|
28
|
+
"target": z.string().nullable(),
|
|
29
|
+
"detail": z.string().nullable(),
|
|
30
|
+
"level": z.number()
|
|
31
|
+
})),
|
|
32
|
+
"total": z.number(),
|
|
33
|
+
"truncated": z.boolean()
|
|
34
|
+
}),
|
|
35
|
+
},
|
|
36
|
+
"systemPermissionsGet": {
|
|
37
|
+
data: z.array(z.object({
|
|
38
|
+
"code": z.string(),
|
|
39
|
+
"module": z.string(),
|
|
40
|
+
"title_key": z.string().nullable(),
|
|
41
|
+
"deprecated": z.boolean()
|
|
42
|
+
})),
|
|
43
|
+
},
|
|
44
|
+
"systemRolesGet": {
|
|
45
|
+
data: z.array(z.object({
|
|
46
|
+
"name": z.string(),
|
|
47
|
+
"module": z.string(),
|
|
48
|
+
"title": z.string().nullable(),
|
|
49
|
+
"description": z.string().nullable(),
|
|
50
|
+
"builtin": z.boolean(),
|
|
51
|
+
"deprecated": z.boolean(),
|
|
52
|
+
"holders": z.number(),
|
|
53
|
+
"permissions": z.array(z.string())
|
|
54
|
+
})),
|
|
55
|
+
},
|
|
56
|
+
"systemRolesPost": {
|
|
57
|
+
body: z.object({
|
|
58
|
+
"name": // @oj-warn: pattern="^custom\\.[a-z0-9][a-z0-9-]*$" 为 Rust regex,未在前端执行(跨语言语义漂移/ReDoS 风险)
|
|
59
|
+
z.string(),
|
|
60
|
+
"title": z.string().min(1),
|
|
61
|
+
"permissions": z.array(z.string()).optional()
|
|
62
|
+
}),
|
|
63
|
+
data: z.object({
|
|
64
|
+
"name": z.string(),
|
|
65
|
+
"module": z.string()
|
|
66
|
+
}),
|
|
67
|
+
},
|
|
68
|
+
"systemRolesDelete": {
|
|
69
|
+
query: z.object({
|
|
70
|
+
"name": z.string().min(1)
|
|
71
|
+
}),
|
|
72
|
+
},
|
|
73
|
+
"systemRolesBindingsPut": {
|
|
74
|
+
query: z.object({
|
|
75
|
+
"name": z.string().min(1)
|
|
76
|
+
}),
|
|
77
|
+
body: z.object({
|
|
78
|
+
"permissions": z.array(z.string())
|
|
79
|
+
}),
|
|
80
|
+
},
|
|
81
|
+
"systemSettingsGet": {
|
|
82
|
+
data: z.array(z.object({
|
|
83
|
+
"group": z.string(),
|
|
84
|
+
"items": z.array(z.object({
|
|
85
|
+
"key": z.string(),
|
|
86
|
+
"module": z.string(),
|
|
87
|
+
"group": z.string(),
|
|
88
|
+
"type": z.string(),
|
|
89
|
+
"value": z.string().nullable(),
|
|
90
|
+
"default_value": z.string().nullable(),
|
|
91
|
+
"label_key": z.string().nullable(),
|
|
92
|
+
"options": z.string().nullable(),
|
|
93
|
+
"secret": z.boolean(),
|
|
94
|
+
"update_time": z.number()
|
|
95
|
+
}))
|
|
96
|
+
})),
|
|
97
|
+
},
|
|
98
|
+
"systemSettingsPut": {
|
|
99
|
+
body: z.object({
|
|
100
|
+
"key": z.string().min(1),
|
|
101
|
+
"value": z.string()
|
|
102
|
+
}),
|
|
103
|
+
},
|
|
104
|
+
"systemUsersGet": {
|
|
105
|
+
data: z.array(z.object({
|
|
106
|
+
"id": z.number(),
|
|
107
|
+
"username": z.string(),
|
|
108
|
+
"display_name": z.string().nullable(),
|
|
109
|
+
"email": z.string().nullable(),
|
|
110
|
+
"is_forbidden": z.boolean(),
|
|
111
|
+
"must_change": z.boolean(),
|
|
112
|
+
"roles": z.array(z.string())
|
|
113
|
+
})),
|
|
114
|
+
},
|
|
115
|
+
"systemUsersPost": {
|
|
116
|
+
body: z.object({
|
|
117
|
+
"username": z.string().min(1),
|
|
118
|
+
"password": z.string().min(6),
|
|
119
|
+
"displayName": z.string().optional(),
|
|
120
|
+
"email": z.string().optional()
|
|
121
|
+
}),
|
|
122
|
+
data: z.object({
|
|
123
|
+
"id": z.number()
|
|
124
|
+
}),
|
|
125
|
+
},
|
|
126
|
+
"systemUsersForbiddenPut": {
|
|
127
|
+
query: z.object({
|
|
128
|
+
"uid": z.number()
|
|
129
|
+
}),
|
|
130
|
+
body: z.object({
|
|
131
|
+
"forbidden": z.boolean()
|
|
132
|
+
}),
|
|
133
|
+
},
|
|
134
|
+
"systemUsersPasswordPut": {
|
|
135
|
+
query: z.object({
|
|
136
|
+
"uid": z.number()
|
|
137
|
+
}),
|
|
138
|
+
body: z.object({
|
|
139
|
+
"password": z.string().min(6)
|
|
140
|
+
}),
|
|
141
|
+
},
|
|
142
|
+
"systemUsersRolesPut": {
|
|
143
|
+
query: z.object({
|
|
144
|
+
"uid": z.number()
|
|
145
|
+
}),
|
|
146
|
+
body: z.object({
|
|
147
|
+
"roles": z.array(z.string())
|
|
148
|
+
}),
|
|
149
|
+
},
|
|
150
|
+
};
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// 生成物:ojm api 从契约生成,勿手改(改动请改契约文件后重跑 ojm api)
|
|
3
|
+
import { ContractApiError } from "@oj-module/runtime/contract/errors";
|
|
4
|
+
import type { ScopedRequestLike } from "@oj-module/runtime/contract/errors";
|
|
5
|
+
import type { ModuleContext } from "@oj-module/runtime";
|
|
6
|
+
import type { z } from "@oj-module/runtime";
|
|
7
|
+
import type { schemas } from "./api.schemas";
|
|
8
|
+
|
|
9
|
+
/** oj 信封(AC-D16):code=0 成功;非 0 时 HTTP status=code,由 toApiError 归一为 ContractApiError */
|
|
10
|
+
interface OjEnvelope<T> { code: number, msg?: string, data?: T }
|
|
11
|
+
|
|
12
|
+
let req: ScopedRequestLike | undefined;
|
|
13
|
+
|
|
14
|
+
/** 线上 API 前缀(ojm api 从契约抽取,唯一真源,勿手改):请求基址 + 前缀登记用 */
|
|
15
|
+
export const API_PREFIX = "/system";
|
|
16
|
+
|
|
17
|
+
/** 认领身份前缀(契约 apiPrefix,字面等于模块目录名):跨模块认领护栏对比基准 */
|
|
18
|
+
export const MODULE_PREFIX = "/system";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 模块 entry 的 onInit 里一行创建(构造即 install):完成 apiPrefix 登记 +
|
|
22
|
+
* scoped request 绑定,返回绑好前缀的端点函数集合。一模块一 client;
|
|
23
|
+
* 重复构造幂等安全(前缀重复登记同值、request 重绑同值)。
|
|
24
|
+
*/
|
|
25
|
+
export function createSystemClient(ctx: ModuleContext) {
|
|
26
|
+
// 跨模块认领护栏(DEV):工厂知道自己该属于谁(MODULE_PREFIX = 契约 apiPrefix),
|
|
27
|
+
// ctx 又带 module.name——名实不符说明别的模块在替本模块创建 client,会共享 request
|
|
28
|
+
// 单槽、卸载互相耦合;跨模块需求应由本模块创建后经 provider 暴露。
|
|
29
|
+
// 注意对比基准是 MODULE_PREFIX(目录名)而非 API_PREFIX——D-M8 映射时线上前缀
|
|
30
|
+
// 属于别的命名空间,认领身份仍按目录名。
|
|
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 暴露。`);
|
|
33
|
+
}
|
|
34
|
+
ctx.register.apiPrefix(API_PREFIX);
|
|
35
|
+
bindRequest(ctx.utils.request);
|
|
36
|
+
return { systemLoginMethodsGet, systemOplogGet, systemPermissionsGet, systemRolesGet, systemRolesPost, systemRolesDelete, systemRolesBindingsPut, systemSettingsGet, systemSettingsPut, systemUsersGet, systemUsersPost, systemUsersForbiddenPut, systemUsersPasswordPut, systemUsersRolesPut };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @deprecated 请改用 createSystemClient(ctx)——工厂构造即完成
|
|
41
|
+
* 前缀登记 + request 绑定,不会出现「登记了前缀没绑 request」的半接线状态。
|
|
42
|
+
*/
|
|
43
|
+
export function bindRequest(r: ScopedRequestLike): void {
|
|
44
|
+
req = r;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function ensureReq(): ScopedRequestLike {
|
|
48
|
+
if (!req)
|
|
49
|
+
throw new ContractApiError(-1, "[ojm-api] 请求未绑定——请在模块 entry.ts 的 onInit 里调用 createSystemClient(ctx)(或 bindRequest(ctx.utils.request))。");
|
|
50
|
+
return req;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** ky HTTPError → ContractApiError(错误体为信封时取 code/msg);契约违例与原错误原样透传 */
|
|
54
|
+
async function toApiError(e: unknown): Promise<unknown> {
|
|
55
|
+
if (e instanceof ContractApiError)
|
|
56
|
+
return e;
|
|
57
|
+
// ky 2.x 构造 HTTPError 时已用 getReader() 消费掉原 response 体(res.clone().json() 按规范抛 TypeError,
|
|
58
|
+
// 体方法一律失效),解析好的错误体在公开字段 error.data——故读 error.data,勿改回 clone().json()。
|
|
59
|
+
const err = e as { data?: { code?: unknown, msg?: string }, response?: Response } | null;
|
|
60
|
+
const res = err?.response;
|
|
61
|
+
const d = err?.data;
|
|
62
|
+
if (res instanceof Response && d && typeof d.code === "number")
|
|
63
|
+
return new ContractApiError(d.code, d.msg ?? res.statusText);
|
|
64
|
+
return e;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type SystemLoginMethodsGetData = z.infer<(typeof schemas)["systemLoginMethodsGet"]["data"]>;
|
|
68
|
+
|
|
69
|
+
export async function systemLoginMethodsGet(): Promise<SystemLoginMethodsGetData> {
|
|
70
|
+
const client = ensureReq();
|
|
71
|
+
try {
|
|
72
|
+
const env = await client.get(`system/login-methods`).json<OjEnvelope<SystemLoginMethodsGetData>>();
|
|
73
|
+
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
74
|
+
if (typeof env.code === "number" && env.code !== 0)
|
|
75
|
+
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
76
|
+
const data = env.data as SystemLoginMethodsGetData;
|
|
77
|
+
if (import.meta.env.DEV) {
|
|
78
|
+
const { schemas } = await import("./api.schemas");
|
|
79
|
+
const r = schemas["systemLoginMethodsGet"].data.safeParse(data);
|
|
80
|
+
if (!r.success)
|
|
81
|
+
throw new ContractApiError(-1, `[契约违例] systemLoginMethodsGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
|
|
82
|
+
}
|
|
83
|
+
return data;
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
throw await toApiError(e);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export type SystemOplogGetQuery = z.input<(typeof schemas)["systemOplogGet"]["query"]>;
|
|
91
|
+
export type SystemOplogGetData = z.infer<(typeof schemas)["systemOplogGet"]["data"]>;
|
|
92
|
+
|
|
93
|
+
export async function systemOplogGet(query: SystemOplogGetQuery): Promise<SystemOplogGetData> {
|
|
94
|
+
const client = ensureReq();
|
|
95
|
+
try {
|
|
96
|
+
const env = await client.get(`system/oplog`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<SystemOplogGetData>>();
|
|
97
|
+
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
98
|
+
if (typeof env.code === "number" && env.code !== 0)
|
|
99
|
+
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
100
|
+
const data = env.data as SystemOplogGetData;
|
|
101
|
+
if (import.meta.env.DEV) {
|
|
102
|
+
const { schemas } = await import("./api.schemas");
|
|
103
|
+
const r = schemas["systemOplogGet"].data.safeParse(data);
|
|
104
|
+
if (!r.success)
|
|
105
|
+
throw new ContractApiError(-1, `[契约违例] systemOplogGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
|
|
106
|
+
}
|
|
107
|
+
return data;
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
throw await toApiError(e);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export type SystemPermissionsGetData = z.infer<(typeof schemas)["systemPermissionsGet"]["data"]>;
|
|
115
|
+
|
|
116
|
+
export async function systemPermissionsGet(): Promise<SystemPermissionsGetData> {
|
|
117
|
+
const client = ensureReq();
|
|
118
|
+
try {
|
|
119
|
+
const env = await client.get(`system/permissions`).json<OjEnvelope<SystemPermissionsGetData>>();
|
|
120
|
+
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
121
|
+
if (typeof env.code === "number" && env.code !== 0)
|
|
122
|
+
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
123
|
+
const data = env.data as SystemPermissionsGetData;
|
|
124
|
+
if (import.meta.env.DEV) {
|
|
125
|
+
const { schemas } = await import("./api.schemas");
|
|
126
|
+
const r = schemas["systemPermissionsGet"].data.safeParse(data);
|
|
127
|
+
if (!r.success)
|
|
128
|
+
throw new ContractApiError(-1, `[契约违例] systemPermissionsGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
|
|
129
|
+
}
|
|
130
|
+
return data;
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
throw await toApiError(e);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export type SystemRolesGetData = z.infer<(typeof schemas)["systemRolesGet"]["data"]>;
|
|
138
|
+
|
|
139
|
+
export async function systemRolesGet(): Promise<SystemRolesGetData> {
|
|
140
|
+
const client = ensureReq();
|
|
141
|
+
try {
|
|
142
|
+
const env = await client.get(`system/roles`).json<OjEnvelope<SystemRolesGetData>>();
|
|
143
|
+
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
144
|
+
if (typeof env.code === "number" && env.code !== 0)
|
|
145
|
+
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
146
|
+
const data = env.data as SystemRolesGetData;
|
|
147
|
+
if (import.meta.env.DEV) {
|
|
148
|
+
const { schemas } = await import("./api.schemas");
|
|
149
|
+
const r = schemas["systemRolesGet"].data.safeParse(data);
|
|
150
|
+
if (!r.success)
|
|
151
|
+
throw new ContractApiError(-1, `[契约违例] systemRolesGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
|
|
152
|
+
}
|
|
153
|
+
return data;
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
throw await toApiError(e);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export type SystemRolesPostBody = z.input<(typeof schemas)["systemRolesPost"]["body"]>;
|
|
161
|
+
export type SystemRolesPostData = z.infer<(typeof schemas)["systemRolesPost"]["data"]>;
|
|
162
|
+
|
|
163
|
+
export async function systemRolesPost(body: SystemRolesPostBody): Promise<SystemRolesPostData> {
|
|
164
|
+
const client = ensureReq();
|
|
165
|
+
try {
|
|
166
|
+
const env = await client.post(`system/roles`, { json: body }).json<OjEnvelope<SystemRolesPostData>>();
|
|
167
|
+
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
168
|
+
if (typeof env.code === "number" && env.code !== 0)
|
|
169
|
+
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
170
|
+
const data = env.data as SystemRolesPostData;
|
|
171
|
+
if (import.meta.env.DEV) {
|
|
172
|
+
const { schemas } = await import("./api.schemas");
|
|
173
|
+
const r = schemas["systemRolesPost"].data.safeParse(data);
|
|
174
|
+
if (!r.success)
|
|
175
|
+
throw new ContractApiError(-1, `[契约违例] systemRolesPost 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
|
|
176
|
+
}
|
|
177
|
+
return data;
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
throw await toApiError(e);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export type SystemRolesDeleteQuery = z.input<(typeof schemas)["systemRolesDelete"]["query"]>;
|
|
185
|
+
|
|
186
|
+
export async function systemRolesDelete(query: SystemRolesDeleteQuery): Promise<unknown> {
|
|
187
|
+
const client = ensureReq();
|
|
188
|
+
try {
|
|
189
|
+
const env = await client.delete(`system/roles`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<unknown>>();
|
|
190
|
+
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
191
|
+
if (typeof env.code === "number" && env.code !== 0)
|
|
192
|
+
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
193
|
+
const data = env.data as unknown;
|
|
194
|
+
return data;
|
|
195
|
+
}
|
|
196
|
+
catch (e) {
|
|
197
|
+
throw await toApiError(e);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export type SystemRolesBindingsPutQuery = z.input<(typeof schemas)["systemRolesBindingsPut"]["query"]>;
|
|
202
|
+
export type SystemRolesBindingsPutBody = z.input<(typeof schemas)["systemRolesBindingsPut"]["body"]>;
|
|
203
|
+
|
|
204
|
+
export async function systemRolesBindingsPut(input: { query: SystemRolesBindingsPutQuery, body: SystemRolesBindingsPutBody }): Promise<unknown> {
|
|
205
|
+
const client = ensureReq();
|
|
206
|
+
try {
|
|
207
|
+
const env = await client.put(`system/roles/bindings`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<unknown>>();
|
|
208
|
+
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
209
|
+
if (typeof env.code === "number" && env.code !== 0)
|
|
210
|
+
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
211
|
+
const data = env.data as unknown;
|
|
212
|
+
return data;
|
|
213
|
+
}
|
|
214
|
+
catch (e) {
|
|
215
|
+
throw await toApiError(e);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export type SystemSettingsGetData = z.infer<(typeof schemas)["systemSettingsGet"]["data"]>;
|
|
220
|
+
|
|
221
|
+
export async function systemSettingsGet(): Promise<SystemSettingsGetData> {
|
|
222
|
+
const client = ensureReq();
|
|
223
|
+
try {
|
|
224
|
+
const env = await client.get(`system/settings`).json<OjEnvelope<SystemSettingsGetData>>();
|
|
225
|
+
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
226
|
+
if (typeof env.code === "number" && env.code !== 0)
|
|
227
|
+
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
228
|
+
const data = env.data as SystemSettingsGetData;
|
|
229
|
+
if (import.meta.env.DEV) {
|
|
230
|
+
const { schemas } = await import("./api.schemas");
|
|
231
|
+
const r = schemas["systemSettingsGet"].data.safeParse(data);
|
|
232
|
+
if (!r.success)
|
|
233
|
+
throw new ContractApiError(-1, `[契约违例] systemSettingsGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
|
|
234
|
+
}
|
|
235
|
+
return data;
|
|
236
|
+
}
|
|
237
|
+
catch (e) {
|
|
238
|
+
throw await toApiError(e);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export type SystemSettingsPutBody = z.input<(typeof schemas)["systemSettingsPut"]["body"]>;
|
|
243
|
+
|
|
244
|
+
export async function systemSettingsPut(body: SystemSettingsPutBody): Promise<unknown> {
|
|
245
|
+
const client = ensureReq();
|
|
246
|
+
try {
|
|
247
|
+
const env = await client.put(`system/settings`, { json: body }).json<OjEnvelope<unknown>>();
|
|
248
|
+
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
249
|
+
if (typeof env.code === "number" && env.code !== 0)
|
|
250
|
+
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
251
|
+
const data = env.data as unknown;
|
|
252
|
+
return data;
|
|
253
|
+
}
|
|
254
|
+
catch (e) {
|
|
255
|
+
throw await toApiError(e);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export type SystemUsersGetData = z.infer<(typeof schemas)["systemUsersGet"]["data"]>;
|
|
260
|
+
|
|
261
|
+
export async function systemUsersGet(): Promise<SystemUsersGetData> {
|
|
262
|
+
const client = ensureReq();
|
|
263
|
+
try {
|
|
264
|
+
const env = await client.get(`system/users`).json<OjEnvelope<SystemUsersGetData>>();
|
|
265
|
+
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
266
|
+
if (typeof env.code === "number" && env.code !== 0)
|
|
267
|
+
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
268
|
+
const data = env.data as SystemUsersGetData;
|
|
269
|
+
if (import.meta.env.DEV) {
|
|
270
|
+
const { schemas } = await import("./api.schemas");
|
|
271
|
+
const r = schemas["systemUsersGet"].data.safeParse(data);
|
|
272
|
+
if (!r.success)
|
|
273
|
+
throw new ContractApiError(-1, `[契约违例] systemUsersGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
|
|
274
|
+
}
|
|
275
|
+
return data;
|
|
276
|
+
}
|
|
277
|
+
catch (e) {
|
|
278
|
+
throw await toApiError(e);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export type SystemUsersPostBody = z.input<(typeof schemas)["systemUsersPost"]["body"]>;
|
|
283
|
+
export type SystemUsersPostData = z.infer<(typeof schemas)["systemUsersPost"]["data"]>;
|
|
284
|
+
|
|
285
|
+
export async function systemUsersPost(body: SystemUsersPostBody): Promise<SystemUsersPostData> {
|
|
286
|
+
const client = ensureReq();
|
|
287
|
+
try {
|
|
288
|
+
const env = await client.post(`system/users`, { json: body }).json<OjEnvelope<SystemUsersPostData>>();
|
|
289
|
+
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
290
|
+
if (typeof env.code === "number" && env.code !== 0)
|
|
291
|
+
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
292
|
+
const data = env.data as SystemUsersPostData;
|
|
293
|
+
if (import.meta.env.DEV) {
|
|
294
|
+
const { schemas } = await import("./api.schemas");
|
|
295
|
+
const r = schemas["systemUsersPost"].data.safeParse(data);
|
|
296
|
+
if (!r.success)
|
|
297
|
+
throw new ContractApiError(-1, `[契约违例] systemUsersPost 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
|
|
298
|
+
}
|
|
299
|
+
return data;
|
|
300
|
+
}
|
|
301
|
+
catch (e) {
|
|
302
|
+
throw await toApiError(e);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export type SystemUsersForbiddenPutQuery = z.input<(typeof schemas)["systemUsersForbiddenPut"]["query"]>;
|
|
307
|
+
export type SystemUsersForbiddenPutBody = z.input<(typeof schemas)["systemUsersForbiddenPut"]["body"]>;
|
|
308
|
+
|
|
309
|
+
export async function systemUsersForbiddenPut(input: { query: SystemUsersForbiddenPutQuery, body: SystemUsersForbiddenPutBody }): Promise<unknown> {
|
|
310
|
+
const client = ensureReq();
|
|
311
|
+
try {
|
|
312
|
+
const env = await client.put(`system/users/forbidden`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<unknown>>();
|
|
313
|
+
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
314
|
+
if (typeof env.code === "number" && env.code !== 0)
|
|
315
|
+
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
316
|
+
const data = env.data as unknown;
|
|
317
|
+
return data;
|
|
318
|
+
}
|
|
319
|
+
catch (e) {
|
|
320
|
+
throw await toApiError(e);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export type SystemUsersPasswordPutQuery = z.input<(typeof schemas)["systemUsersPasswordPut"]["query"]>;
|
|
325
|
+
export type SystemUsersPasswordPutBody = z.input<(typeof schemas)["systemUsersPasswordPut"]["body"]>;
|
|
326
|
+
|
|
327
|
+
export async function systemUsersPasswordPut(input: { query: SystemUsersPasswordPutQuery, body: SystemUsersPasswordPutBody }): Promise<unknown> {
|
|
328
|
+
const client = ensureReq();
|
|
329
|
+
try {
|
|
330
|
+
const env = await client.put(`system/users/password`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<unknown>>();
|
|
331
|
+
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
332
|
+
if (typeof env.code === "number" && env.code !== 0)
|
|
333
|
+
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
334
|
+
const data = env.data as unknown;
|
|
335
|
+
return data;
|
|
336
|
+
}
|
|
337
|
+
catch (e) {
|
|
338
|
+
throw await toApiError(e);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export type SystemUsersRolesPutQuery = z.input<(typeof schemas)["systemUsersRolesPut"]["query"]>;
|
|
343
|
+
export type SystemUsersRolesPutBody = z.input<(typeof schemas)["systemUsersRolesPut"]["body"]>;
|
|
344
|
+
|
|
345
|
+
export async function systemUsersRolesPut(input: { query: SystemUsersRolesPutQuery, body: SystemUsersRolesPutBody }): Promise<unknown> {
|
|
346
|
+
const client = ensureReq();
|
|
347
|
+
try {
|
|
348
|
+
const env = await client.put(`system/users/roles`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<unknown>>();
|
|
349
|
+
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
350
|
+
if (typeof env.code === "number" && env.code !== 0)
|
|
351
|
+
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
352
|
+
const data = env.data as unknown;
|
|
353
|
+
return data;
|
|
354
|
+
}
|
|
355
|
+
catch (e) {
|
|
356
|
+
throw await toApiError(e);
|
|
357
|
+
}
|
|
358
|
+
}
|
package/templates/web.config.ts
CHANGED
|
@@ -38,8 +38,8 @@ export default {
|
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
// 系统管理:用户/成员资格/角色权限/操作日志/站点设置/登录方式(oj 后端 /api/system/*)。
|
|
41
|
-
// __client
|
|
42
|
-
//
|
|
41
|
+
// __client 已随模板分发(与 auth/notification 等一致),开箱即用;契约变更后重跑
|
|
42
|
+
// `oj openapi -o openapi.json && ojm api` 可刷新,README「系统管理模块」节有说明
|
|
43
43
|
name: "system",
|
|
44
44
|
entry: "web/src/system/entry.ts",
|
|
45
45
|
enabled: true,
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
"react-router": "7.18.2",
|
|
9
9
|
"react-router/dom": "7.18.2",
|
|
10
10
|
"@tanstack/react-query": "5.102.8",
|
|
11
|
-
"@oj-module/runtime": "0.2.
|
|
12
|
-
"@oj-module/runtime/contract": "0.2.
|
|
13
|
-
"@oj-module/runtime/contract/errors": "0.2.
|
|
11
|
+
"@oj-module/runtime": "0.2.2",
|
|
12
|
+
"@oj-module/runtime/contract": "0.2.2",
|
|
13
|
+
"@oj-module/runtime/contract/errors": "0.2.2",
|
|
14
14
|
"@plane/kit": "0.1.20",
|
|
15
15
|
"@plane/editor": "0.1.20",
|
|
16
16
|
"antd": "6.6.1",
|