@oj-module/cli 0.1.16 → 0.1.20
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/README.md +11 -10
- package/devkit/SKILL.md +35 -18
- package/devkit/manual.md +359 -131
- package/package.json +2 -2
- package/shell-dist/assets/contract.js +0 -55
- package/shell-dist/assets/runtime.js +238 -213
- package/shell-dist/index.html +2 -2
- package/shell-dist/versions.json +5 -5
- package/src/build.ts +1 -0
- package/src/contract/__tests__/check-api.test.ts +74 -0
- package/src/contract/__tests__/emit-client.test.ts +132 -0
- package/src/contract/__tests__/emit-jsonschema-zod.test.ts +93 -0
- package/src/contract/__tests__/mock.test.ts +56 -0
- package/src/contract/__tests__/openapi-ir.test.ts +137 -0
- package/src/contract/__tests__/run-api.test.ts +67 -0
- package/src/contract/check.ts +67 -294
- package/src/contract/emit-client.ts +23 -15
- package/src/contract/emit-jsonschema-zod.ts +209 -0
- package/src/contract/emit-oj-schema.ts +108 -161
- package/src/contract/emit-stub.ts +17 -57
- package/src/contract/handler-scan.ts +263 -0
- package/src/contract/ir.ts +11 -179
- package/src/contract/json-schema-sample.ts +72 -0
- package/src/contract/mock.ts +28 -100
- package/src/contract/openapi-ir.ts +145 -0
- package/src/contract/run.ts +71 -120
- package/src/dev.ts +20 -18
- package/src/init.ts +18 -3
- package/src/oj.ts +6 -2
- package/src/vendor.ts +8 -4
- package/templates/api/config.yaml +1 -0
- package/templates/api/src/auth/api.ts +66 -0
- package/templates/api/src/notification/_shared/notify.ts +199 -0
- package/templates/api/src/notification/api.ts +231 -0
- package/templates/api/src/notification/del/api.ts +47 -0
- package/templates/api/src/notification/manifest.yaml +3 -1
- package/templates/api/src/notification/migrations/0001__create_notifications.sql +1 -1
- package/templates/api/src/notification/migrations/0002__notifications_user.sql +9 -0
- package/templates/api/src/notification/migrations/0003__notifications_broadcast.sql +22 -0
- package/templates/api/src/notification/schema.yaml +23 -1
- package/templates/api/src/notification/seed.sql +6 -4
- package/templates/api/src/personal-center/upload/api.ts +12 -1
- package/templates/env.d.ts +1 -1
- package/templates/openapi.json +471 -0
- package/templates/web/src/auth/__client/api.schemas.ts +8 -0
- package/templates/web/src/auth/__client/api.ts +110 -0
- package/templates/web/src/notification/__client/api.schemas.ts +50 -0
- package/templates/web/src/notification/__client/api.ts +186 -0
- package/templates/web/src/notification/entry.ts +20 -9
- package/templates/web/src/notifications/__client/api.schemas.ts +8 -0
- package/templates/web/src/notifications/__client/api.ts +80 -0
- package/templates/web/src/{notification/client → personal-center/__client}/api.schemas.ts +2 -14
- package/templates/web/src/personal-center/__client/api.ts +88 -0
- package/templates/web/src/web/__client/api.schemas.ts +8 -0
- package/templates/web/src/{notification/client → web/__client}/api.ts +12 -37
- package/vendor/host-versions.json +5 -5
- package/src/contract/emit-meta.ts +0 -175
- package/src/contract/emit-schema.ts +0 -133
- package/src/contract/evaluate.ts +0 -88
- package/templates/api/src/auth/login/api.ts +0 -27
- package/templates/api/src/auth/logout/api.ts +0 -10
- package/templates/api/src/auth/refresh/api.ts +0 -26
- package/templates/api/src/notification/contract.ts +0 -52
- package/templates/api/src/notification/notifications/api.ts +0 -24
- package/templates/api/src/notification/notifications/clear/api.ts +0 -13
- package/templates/api/src/notification/notifications/read/api.ts +0 -19
- package/templates/api/src/notification/notifications/read-all/api.ts +0 -13
- package/templates/api/src/personal-center/contract.ts +0 -16
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oj-module/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.20",
|
|
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.1.
|
|
52
|
+
"@oj-module/runtime": "0.1.20",
|
|
53
53
|
"@redocly/cli": "^2.51.0",
|
|
54
54
|
"@tailwindcss/vite": "^4.3.0",
|
|
55
55
|
"esbuild": "^0.28.2",
|
|
@@ -9,63 +9,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
9
9
|
// ../runtime/dist/contract/index.js
|
|
10
10
|
var contract_exports = {};
|
|
11
11
|
__export(contract_exports, {
|
|
12
|
-
API_DEF: () => API_DEF,
|
|
13
12
|
ContractApiError: () => ContractApiError,
|
|
14
|
-
defineApi: () => defineApi,
|
|
15
13
|
z: () => external_exports
|
|
16
14
|
});
|
|
17
15
|
|
|
18
|
-
// ../runtime/dist/contract/define-api.js
|
|
19
|
-
var PARAM_SEGMENT = /^\{\*?[a-z_]\w*\}$/i;
|
|
20
|
-
function fail(route, reason) {
|
|
21
|
-
throw new Error(`[\u5951\u7EA6] \u7AEF\u70B9\u5B9A\u4E49\u975E\u6CD5\uFF08route: ${route}\uFF09\uFF1A${reason}`);
|
|
22
|
-
}
|
|
23
|
-
function validateDefinition(def) {
|
|
24
|
-
const { apiPrefix, route } = def;
|
|
25
|
-
if (!apiPrefix?.startsWith("/")) {
|
|
26
|
-
throw new Error(`[\u5951\u7EA6] apiPrefix \u5FC5\u987B\u4EE5 "/" \u5F00\u5934\uFF08\u6536\u5230: ${apiPrefix}\uFF09\u2014\u2014\u5982 "/order"\uFF1Buni-dev \u5F62\u6001\u8BF7\u4E0E oj \u6A21\u5757\u6BB5\u540D\u4FDD\u6301\u4E00\u81F4\uFF08AC-D9\uFF09\u3002`);
|
|
27
|
-
}
|
|
28
|
-
if (def.urlPrefix !== void 0 && !def.urlPrefix.startsWith("/"))
|
|
29
|
-
fail(route, `urlPrefix \u5FC5\u987B\u4EE5 "/" \u5F00\u5934\uFF08\u6536\u5230: ${def.urlPrefix}\uFF09\u3002`);
|
|
30
|
-
if (!route?.startsWith("/"))
|
|
31
|
-
fail(route, `route \u5FC5\u987B\u4EE5 "/" \u5F00\u5934\uFF08\u6536\u5230: ${route}\uFF09\uFF0C\u4E14\u4E00\u5F8B\u76F8\u5BF9 apiPrefix\u2014\u2014\u4E0D\u652F\u6301 oj \u7684\u6839\u7EDD\u5BF9\u5199\u6CD5\uFF08\u6A21\u5757\u624B\u518C D11 \u524D\u7F00\u6536\u655B\uFF09\u3002`);
|
|
32
|
-
if (route.split("/").some((seg) => seg === ".." || seg === "." || seg === "\\"))
|
|
33
|
-
fail(route, "route \u542B\u8DEF\u5F84\u7A7F\u8D8A\u6BB5\uFF08.././\\\uFF09\uFF0C\u8BF7\u6539\u4E3A\u6B63\u5E38\u9759\u6001\u6BB5\u3002");
|
|
34
|
-
for (const seg of route.split("/")) {
|
|
35
|
-
if (seg.includes("{") && !PARAM_SEGMENT.test(seg))
|
|
36
|
-
fail(route, `\u53C2\u6570\u6BB5 "${seg}" \u6DF7\u5165\u5B57\u9762\u91CF\u2014\u2014matchit \u7EA6\u675F\uFF1A\u53C2\u6570\u6BB5\u5FC5\u987B\u6574\u6BB5\u4E3A {name} \u6216 {*name}\uFF0C\u9700\u8981\u524D\u7F00/\u540E\u7F00\u5B57\u9762\u65F6\u8BF7\u62C6\u6210\u9759\u6001\u591A\u6BB5\u3002`);
|
|
37
|
-
}
|
|
38
|
-
if (def.data && def.response === "raw")
|
|
39
|
-
fail(route, 'data schema \u4E0E response:"raw" \u4E92\u65A5\u2014\u2014raw \u7AEF\u70B9\u4E0D\u89E3\u5305\u4FE1\u5C01\uFF0C\u4E0D\u9700\u8981 data schema\u3002');
|
|
40
|
-
if (def.form && def.body)
|
|
41
|
-
fail(route, "form \u4E0E body \u4E92\u65A5\u2014\u2014\u4E00\u4E2A\u7AEF\u70B9\u53EA\u6709\u4E00\u79CD\u8BF7\u6C42\u4F53\u5F62\u6001\uFF1Amultipart/form-data\uFF08form\uFF09\u4E0E JSON\uFF08body\uFF09\u58F0\u660E\u5176\u4E00\uFF0C\u6587\u672C\u5B57\u6BB5\u8BF7\u5199\u8FDB form.fields\u3002");
|
|
42
|
-
if (def.form) {
|
|
43
|
-
const files = def.form.files ?? [];
|
|
44
|
-
if (!def.form.fields && files.length === 0)
|
|
45
|
-
fail(route, "form \u81F3\u5C11\u9700\u8981 fields \u6216\u4E00\u9879 files\u2014\u2014\u7A7A multipart \u8BF7\u6C42\u4F53\u65E0\u610F\u4E49\uFF1B\u65E0\u8BF7\u6C42\u4F53\u8BF7\u5220\u6389 form\uFF08\u6539\u7528 query/params\uFF09\u3002");
|
|
46
|
-
const seen = /* @__PURE__ */ new Set();
|
|
47
|
-
for (const file2 of files) {
|
|
48
|
-
if (!file2?.name)
|
|
49
|
-
fail(route, "form.files \u6BCF\u9879\u5FC5\u987B\u6709 name\u2014\u2014\u5B83\u65E2\u662F FormData \u7684 append key\uFF0C\u4E5F\u662F oj \u4FA7 http.files[].field \u7684\u5B57\u6BB5\u540D\u3002");
|
|
50
|
-
if (seen.has(file2.name))
|
|
51
|
-
fail(route, `form.files \u5B57\u6BB5\u540D "${file2.name}" \u91CD\u590D\u2014\u2014\u540C\u540D\u591A\u6587\u4EF6\u8BF7\u7528 multiple: true\u3002`);
|
|
52
|
-
seen.add(file2.name);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
if (def.response !== void 0 && def.response !== "raw")
|
|
56
|
-
fail(route, `response \u4EC5\u652F\u6301 "raw"\uFF08\u6536\u5230: ${String(def.response)}\uFF09\u3002`);
|
|
57
|
-
if (def.method === "OPTIONS")
|
|
58
|
-
fail(route, "OPTIONS \u4E0D\u5728\u652F\u6301\u8303\u56F4\uFF08ky/oj \u5747\u65E0\u5BF9\u5E94\u65B9\u6CD5\uFF09\u2014\u2014\u9884\u68C0\u8BF7\u6C42\u7531 fetch/CORS \u5C42\u5904\u7406\uFF0C\u5951\u7EA6\u4E0D\u8868\u8FBE\u3002");
|
|
59
|
-
if (def.method === "HEAD" && def.data)
|
|
60
|
-
fail(route, "HEAD \u7AEF\u70B9\u65E0\u54CD\u5E94\u4F53\uFF0C\u4E0D\u80FD\u58F0\u660E data schema\u2014\u2014\u9700\u8981\u54CD\u5E94\u4F53\u8BF7\u6539\u7528 GET\u3002");
|
|
61
|
-
}
|
|
62
|
-
var API_DEF = /* @__PURE__ */ Symbol.for("ojm.api.def");
|
|
63
|
-
function defineApi(def) {
|
|
64
|
-
validateDefinition(def);
|
|
65
|
-
Object.defineProperty(def, API_DEF, { value: true, enumerable: false });
|
|
66
|
-
return def;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
16
|
// ../runtime/dist/contract/errors.js
|
|
70
17
|
var ContractApiError = class extends Error {
|
|
71
18
|
constructor(code, msg) {
|
|
@@ -18947,9 +18894,7 @@ function date4(params) {
|
|
|
18947
18894
|
// shell/.ojm-shim-contract.mjs
|
|
18948
18895
|
var ojm_shim_contract_default = void 0 ?? contract_exports;
|
|
18949
18896
|
export {
|
|
18950
|
-
API_DEF,
|
|
18951
18897
|
ContractApiError,
|
|
18952
18898
|
ojm_shim_contract_default as default,
|
|
18953
|
-
defineApi,
|
|
18954
18899
|
external_exports as z
|
|
18955
18900
|
};
|
|
@@ -122,6 +122,36 @@ function unregisterApiProviders(moduleName) {
|
|
|
122
122
|
var systemCurrent, notificationsCurrent, uploadCurrent, routesCurrent;
|
|
123
123
|
var init_api_provider = __esmMin((() => {}));
|
|
124
124
|
//#endregion
|
|
125
|
+
//#region src/api/notify/index.ts
|
|
126
|
+
init_api_provider();
|
|
127
|
+
var NOT_REGISTERED = "NotificationsBackendNotRegistered";
|
|
128
|
+
function requirePushProvider() {
|
|
129
|
+
const p = getNotificationsApiProvider();
|
|
130
|
+
if (!p || typeof p.push !== "function" || typeof p.pushBroadcast !== "function" || typeof p.del !== "function") throw new Error(`[notify] ${NOT_REGISTERED}:未注册 notificationsApi provider 的发射方法(push/pushBroadcast/del)。`);
|
|
131
|
+
return p;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 跨模块通知发射原语(前端侧 facade)。委托给已注册 notificationsApi provider 的
|
|
135
|
+
* 发射方法(经 HTTP 落到后端通知模块)。后端模块请改用 `api/src/_shared/notify`
|
|
136
|
+
* (直接 db 写入两表)——那是真正的服务端原语。
|
|
137
|
+
*
|
|
138
|
+
* 语义:原语不做任何业务判断(不判空间/角色/可见性),收件人过滤是发送方责任。
|
|
139
|
+
*/
|
|
140
|
+
var notify = {
|
|
141
|
+
/** 给指定用户推一条按人通知,返回命名空间 id "u:<id>" */
|
|
142
|
+
push(input) {
|
|
143
|
+
return requirePushProvider().push(input);
|
|
144
|
+
},
|
|
145
|
+
/** 广播一条通知(全员可见,一行存储),返回命名空间 id "b:<id>" */
|
|
146
|
+
pushBroadcast(input) {
|
|
147
|
+
return requirePushProvider().pushBroadcast(input);
|
|
148
|
+
},
|
|
149
|
+
/** 删除通知:按人项校验归属(非本人删自己);广播项仅删本人回执 */
|
|
150
|
+
del(id, userId) {
|
|
151
|
+
return requirePushProvider().del(id, userId);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
//#endregion
|
|
125
155
|
//#region src/router/extra-info/route-path.ts
|
|
126
156
|
/**
|
|
127
157
|
* 判断 pathname 是否为登录页(basename 感知,P0)。
|
|
@@ -225,10 +255,10 @@ function getAppInfo() {
|
|
|
225
255
|
return {
|
|
226
256
|
"pkg": {
|
|
227
257
|
"name": "@oj-module/runtime",
|
|
228
|
-
"version": "0.1.
|
|
258
|
+
"version": "0.1.20",
|
|
229
259
|
"license": "MIT"
|
|
230
260
|
},
|
|
231
|
-
"lastBuildTime": "2026-10-
|
|
261
|
+
"lastBuildTime": "2026-10-15 16:50:57"
|
|
232
262
|
};
|
|
233
263
|
}
|
|
234
264
|
var init_get_app_info = __esmMin((() => {}));
|
|
@@ -3550,7 +3580,7 @@ var init_use_layout_style = __esmMin((() => {
|
|
|
3550
3580
|
}));
|
|
3551
3581
|
//#endregion
|
|
3552
3582
|
//#region src/store/tabs.ts
|
|
3553
|
-
var initialState$3, useTabsStore;
|
|
3583
|
+
var initialState$3, closingKeys, useTabsStore;
|
|
3554
3584
|
var init_tabs = __esmMin((() => {
|
|
3555
3585
|
init_preferences$3();
|
|
3556
3586
|
init_get_app_namespace();
|
|
@@ -3576,224 +3606,218 @@ var init_tabs = __esmMin((() => {
|
|
|
3576
3606
|
*/
|
|
3577
3607
|
isMaximize: false
|
|
3578
3608
|
};
|
|
3579
|
-
|
|
3580
|
-
|
|
3609
|
+
closingKeys = /* @__PURE__ */ new Set();
|
|
3610
|
+
useTabsStore = create()(persist((set, get) => {
|
|
3581
3611
|
/**
|
|
3582
|
-
*
|
|
3583
|
-
*
|
|
3612
|
+
* 关闭前守卫:待关闭集合里任一标签页声明了 beforeClose 且返回 false / 抛错时,整体中止。
|
|
3613
|
+
* fail-safe:守卫抛错按中止处理,防误关丢内容;console.error 留排查线索。
|
|
3584
3614
|
*/
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
/**
|
|
3596
|
-
* @zh 在最前面插入标签页
|
|
3597
|
-
* @en Insert a tab at the front.
|
|
3598
|
-
*/
|
|
3599
|
-
insertBeforeTab: (routePath, tabProps) => {
|
|
3600
|
-
set((state) => {
|
|
3601
|
-
if (routePath?.length) {
|
|
3602
|
-
const newMap = /* @__PURE__ */ new Map([[routePath, tabProps]]);
|
|
3603
|
-
for (const [key, value] of state.openTabs) newMap.set(key, value);
|
|
3604
|
-
return { openTabs: newMap };
|
|
3615
|
+
const guardClose = async (keys) => {
|
|
3616
|
+
for (const key of keys) {
|
|
3617
|
+
const fn = get().openTabs.get(key)?.beforeClose;
|
|
3618
|
+
if (fn == null) continue;
|
|
3619
|
+
const fns = Array.isArray(fn) ? fn : [fn];
|
|
3620
|
+
for (const f of fns) try {
|
|
3621
|
+
if (!await f()) return false;
|
|
3622
|
+
} catch (e) {
|
|
3623
|
+
console.error("[tabs] beforeClose 守卫抛错,按中止关闭处理", e);
|
|
3624
|
+
return false;
|
|
3605
3625
|
}
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
}
|
|
3609
|
-
/**
|
|
3610
|
-
* @zh 添加标签页
|
|
3611
|
-
* @en Add a tab.
|
|
3612
|
-
*/
|
|
3613
|
-
addTab: (routePath, tabProps) => {
|
|
3614
|
-
set((state) => {
|
|
3615
|
-
if (routePath?.length) {
|
|
3616
|
-
const newTabs = new Map(state.openTabs);
|
|
3617
|
-
/**
|
|
3618
|
-
* 1. 如果 tab 已经存在,则更新 historyState 属性,所以不去重,且 ...newTabs.get(routePath) 是为了保证首页的 closable 属性不被覆盖
|
|
3619
|
-
* 2. 如果 tab 不存在,则添加到 Map 中
|
|
3620
|
-
*/
|
|
3621
|
-
newTabs.set(routePath, {
|
|
3622
|
-
...newTabs.get(routePath),
|
|
3623
|
-
...tabProps
|
|
3624
|
-
});
|
|
3625
|
-
return { openTabs: newTabs };
|
|
3626
|
-
}
|
|
3627
|
-
return state;
|
|
3628
|
-
});
|
|
3629
|
-
},
|
|
3626
|
+
}
|
|
3627
|
+
return true;
|
|
3628
|
+
};
|
|
3630
3629
|
/**
|
|
3631
|
-
*
|
|
3632
|
-
*
|
|
3630
|
+
* 共用关闭路径:in-flight 去重 → 守卫 → 精确删除获批集合(TOCTOU 防护:
|
|
3631
|
+
* 不对 live state 重算,await 期间新开的 tab 不受影响)→ activeKey 修正。
|
|
3633
3632
|
*/
|
|
3634
|
-
|
|
3635
|
-
|
|
3633
|
+
const guardAndClose = async (removed, fallbackActive) => {
|
|
3634
|
+
if (removed.length === 0) return;
|
|
3635
|
+
for (const k of removed) closingKeys.add(k);
|
|
3636
|
+
try {
|
|
3637
|
+
if (!await guardClose(removed)) return;
|
|
3638
|
+
set((state) => {
|
|
3639
|
+
const toClose = new Set(removed);
|
|
3640
|
+
const newTabs = new Map([...state.openTabs].filter(([k]) => !toClose.has(k)));
|
|
3641
|
+
if (newTabs.size === state.openTabs.size) return state;
|
|
3642
|
+
const remaining = [...newTabs.keys()];
|
|
3643
|
+
return {
|
|
3644
|
+
openTabs: newTabs,
|
|
3645
|
+
activeKey: toClose.has(state.activeKey) || !newTabs.has(state.activeKey) ? fallbackActive(remaining) : state.activeKey
|
|
3646
|
+
};
|
|
3647
|
+
});
|
|
3648
|
+
} finally {
|
|
3649
|
+
for (const k of removed) closingKeys.delete(k);
|
|
3650
|
+
}
|
|
3651
|
+
};
|
|
3652
|
+
return {
|
|
3653
|
+
...initialState$3,
|
|
3654
|
+
/**
|
|
3655
|
+
* @zh 设置标签页是否处于刷新状态
|
|
3656
|
+
* @en Set whether the tab is in a refresh state.
|
|
3657
|
+
*/
|
|
3658
|
+
setIsRefresh: (state) => {
|
|
3659
|
+
set({ isRefresh: state });
|
|
3660
|
+
},
|
|
3661
|
+
/**
|
|
3662
|
+
* @zh 设置标签页
|
|
3663
|
+
* @en Set the tab.
|
|
3664
|
+
*/
|
|
3665
|
+
setActiveKey: (routePath) => {
|
|
3666
|
+
set({ activeKey: routePath });
|
|
3667
|
+
},
|
|
3668
|
+
/**
|
|
3669
|
+
* @zh 在最前面插入标签页
|
|
3670
|
+
* @en Insert a tab at the front.
|
|
3671
|
+
*/
|
|
3672
|
+
insertBeforeTab: (routePath, tabProps) => {
|
|
3673
|
+
set((state) => {
|
|
3674
|
+
if (routePath?.length) {
|
|
3675
|
+
const newMap = /* @__PURE__ */ new Map([[routePath, tabProps]]);
|
|
3676
|
+
for (const [key, value] of state.openTabs) newMap.set(key, value);
|
|
3677
|
+
return { openTabs: newMap };
|
|
3678
|
+
}
|
|
3679
|
+
return state;
|
|
3680
|
+
});
|
|
3681
|
+
},
|
|
3682
|
+
/**
|
|
3683
|
+
* @zh 添加标签页
|
|
3684
|
+
* @en Add a tab.
|
|
3685
|
+
*/
|
|
3686
|
+
addTab: (routePath, tabProps) => {
|
|
3687
|
+
set((state) => {
|
|
3688
|
+
if (routePath?.length) {
|
|
3689
|
+
const newTabs = new Map(state.openTabs);
|
|
3690
|
+
/**
|
|
3691
|
+
* 1. 如果 tab 已经存在,则更新 historyState 属性,所以不去重,且 ...newTabs.get(routePath) 是为了保证首页的 closable 属性不被覆盖
|
|
3692
|
+
* 2. 如果 tab 不存在,则添加到 Map 中
|
|
3693
|
+
* 3. beforeClose 是注册语义而非覆盖语义:数组 concat + 按引用去重
|
|
3694
|
+
* (keep-alive 重挂载重复注册不叠加);null 显式清除全部守卫
|
|
3695
|
+
*/
|
|
3696
|
+
const prev = newTabs.get(routePath);
|
|
3697
|
+
const merged = {
|
|
3698
|
+
...prev,
|
|
3699
|
+
...tabProps
|
|
3700
|
+
};
|
|
3701
|
+
if (tabProps.beforeClose === null) merged.beforeClose = void 0;
|
|
3702
|
+
else if (prev?.beforeClose != null && tabProps.beforeClose !== void 0) merged.beforeClose = [...Array.isArray(prev.beforeClose) ? prev.beforeClose : [prev.beforeClose], ...Array.isArray(tabProps.beforeClose) ? tabProps.beforeClose : [tabProps.beforeClose]].filter((f, i, a) => a.indexOf(f) === i);
|
|
3703
|
+
newTabs.set(routePath, merged);
|
|
3704
|
+
return { openTabs: newTabs };
|
|
3705
|
+
}
|
|
3706
|
+
return state;
|
|
3707
|
+
});
|
|
3708
|
+
},
|
|
3709
|
+
/**
|
|
3710
|
+
* @zh 移除标签页
|
|
3711
|
+
* @en Remove a tab.
|
|
3712
|
+
*/
|
|
3713
|
+
removeTab: async (routePath) => {
|
|
3636
3714
|
const homePath = "/home";
|
|
3637
|
-
if (routePath === homePath) return
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
}
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
set((state) => {
|
|
3658
|
-
const newTabs = /* @__PURE__ */ new Map();
|
|
3659
|
-
let found = false;
|
|
3660
|
-
let activeKeyFound = false;
|
|
3661
|
-
let newActiveKey = state.activeKey;
|
|
3662
|
-
for (const [key, value] of state.openTabs) {
|
|
3663
|
-
if (found) break;
|
|
3664
|
-
newTabs.set(key, value);
|
|
3665
|
-
if (key === routePath) found = true;
|
|
3666
|
-
if (key === state.activeKey) activeKeyFound = true;
|
|
3667
|
-
}
|
|
3668
|
-
if (!activeKeyFound) newActiveKey = routePath;
|
|
3669
|
-
return {
|
|
3670
|
-
openTabs: newTabs,
|
|
3671
|
-
activeKey: newActiveKey
|
|
3672
|
-
};
|
|
3673
|
-
});
|
|
3674
|
-
},
|
|
3675
|
-
/**
|
|
3676
|
-
* @zh 关闭左侧标签页
|
|
3677
|
-
* @en Close tabs on the left.
|
|
3678
|
-
*/
|
|
3679
|
-
closeLeftTabs: (routePath) => {
|
|
3680
|
-
set((state) => {
|
|
3681
|
-
const newTabs = /* @__PURE__ */ new Map();
|
|
3715
|
+
if (!routePath?.length || routePath === homePath || closingKeys.has(routePath)) return;
|
|
3716
|
+
await guardAndClose([routePath], (keys) => keys.at(-1) ?? homePath);
|
|
3717
|
+
},
|
|
3718
|
+
/**
|
|
3719
|
+
* @zh 关闭右侧标签页
|
|
3720
|
+
* @en Close tabs on the right.
|
|
3721
|
+
*/
|
|
3722
|
+
closeRightTabs: async (routePath) => {
|
|
3723
|
+
if (closingKeys.has(routePath)) return;
|
|
3724
|
+
const keys = [...get().openTabs.keys()];
|
|
3725
|
+
const idx = keys.indexOf(routePath);
|
|
3726
|
+
if (idx < 0) return;
|
|
3727
|
+
await guardAndClose(keys.slice(idx + 1), () => routePath);
|
|
3728
|
+
},
|
|
3729
|
+
/**
|
|
3730
|
+
* @zh 关闭左侧标签页
|
|
3731
|
+
* @en Close tabs on the left.
|
|
3732
|
+
*/
|
|
3733
|
+
closeLeftTabs: async (routePath) => {
|
|
3734
|
+
if (closingKeys.has(routePath)) return;
|
|
3682
3735
|
const homePath = "/home";
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
}
|
|
3695
|
-
if (!activeKeyOnRight) newActiveKey = routePath;
|
|
3696
|
-
return {
|
|
3697
|
-
openTabs: newTabs,
|
|
3698
|
-
activeKey: newActiveKey
|
|
3699
|
-
};
|
|
3700
|
-
});
|
|
3701
|
-
},
|
|
3702
|
-
/**
|
|
3703
|
-
* @zh 关闭其他标签页
|
|
3704
|
-
* @en Close other tabs.
|
|
3705
|
-
*/
|
|
3706
|
-
closeOtherTabs: (routePath) => {
|
|
3707
|
-
set((state) => {
|
|
3708
|
-
const newTabs = /* @__PURE__ */ new Map();
|
|
3736
|
+
const keys = [...get().openTabs.keys()];
|
|
3737
|
+
const idx = keys.indexOf(routePath);
|
|
3738
|
+
if (idx < 0) return;
|
|
3739
|
+
await guardAndClose(keys.slice(0, idx).filter((k) => k !== homePath), () => routePath);
|
|
3740
|
+
},
|
|
3741
|
+
/**
|
|
3742
|
+
* @zh 关闭其他标签页
|
|
3743
|
+
* @en Close other tabs.
|
|
3744
|
+
*/
|
|
3745
|
+
closeOtherTabs: async (routePath) => {
|
|
3746
|
+
if (closingKeys.has(routePath)) return;
|
|
3709
3747
|
const homePath = "/home";
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
};
|
|
3718
|
-
});
|
|
3719
|
-
},
|
|
3720
|
-
/**
|
|
3721
|
-
* @zh 关闭所有标签页
|
|
3722
|
-
* @en Close all tabs.
|
|
3723
|
-
*/
|
|
3724
|
-
closeAllTabs: () => {
|
|
3725
|
-
set((state) => {
|
|
3726
|
-
const newTabs = /* @__PURE__ */ new Map();
|
|
3748
|
+
await guardAndClose([...get().openTabs.keys()].filter((k) => k !== homePath && k !== routePath), () => routePath);
|
|
3749
|
+
},
|
|
3750
|
+
/**
|
|
3751
|
+
* @zh 关闭所有标签页
|
|
3752
|
+
* @en Close all tabs.
|
|
3753
|
+
*/
|
|
3754
|
+
closeAllTabs: async () => {
|
|
3727
3755
|
const homePath = "/home";
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
}
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
return { ...initialState$3 };
|
|
3794
|
-
});
|
|
3795
|
-
}
|
|
3796
|
-
}), {
|
|
3756
|
+
await guardAndClose([...get().openTabs.keys()].filter((k) => k !== homePath), () => homePath);
|
|
3757
|
+
},
|
|
3758
|
+
/**
|
|
3759
|
+
* @zh 更改标签页顺序
|
|
3760
|
+
* @en Change tab order.
|
|
3761
|
+
*/
|
|
3762
|
+
changeTabOrder: (from, to) => {
|
|
3763
|
+
set((state) => {
|
|
3764
|
+
const newTabs = Array.from(state.openTabs.entries());
|
|
3765
|
+
const [movedTab] = newTabs.splice(from, 1);
|
|
3766
|
+
newTabs.splice(to, 0, movedTab);
|
|
3767
|
+
return { openTabs: new Map(newTabs) };
|
|
3768
|
+
});
|
|
3769
|
+
},
|
|
3770
|
+
/**
|
|
3771
|
+
* @zh 切换标签页最大化状态
|
|
3772
|
+
* @en Toggle tab maximization status
|
|
3773
|
+
* @param {boolean} state - 最大化状态
|
|
3774
|
+
*/
|
|
3775
|
+
toggleMaximize: (state) => {
|
|
3776
|
+
set({ isMaximize: state });
|
|
3777
|
+
},
|
|
3778
|
+
/**
|
|
3779
|
+
* @zh 设置标签页标题
|
|
3780
|
+
* @en Set the tab title
|
|
3781
|
+
*/
|
|
3782
|
+
setTableTitle: (routePath, title) => {
|
|
3783
|
+
set((state) => {
|
|
3784
|
+
const newTabs = new Map(state.openTabs);
|
|
3785
|
+
const targetTab = newTabs.get(routePath);
|
|
3786
|
+
if (targetTab) {
|
|
3787
|
+
targetTab.newTabTitle = title;
|
|
3788
|
+
newTabs.set(routePath, targetTab);
|
|
3789
|
+
return { openTabs: newTabs };
|
|
3790
|
+
}
|
|
3791
|
+
return state;
|
|
3792
|
+
});
|
|
3793
|
+
},
|
|
3794
|
+
/**
|
|
3795
|
+
* @zh 重置标签页标题(删除自定义的标题)
|
|
3796
|
+
* @en Reset the tab title (delete custom titles)
|
|
3797
|
+
*/
|
|
3798
|
+
resetTableTitle: (routePath) => {
|
|
3799
|
+
set((state) => {
|
|
3800
|
+
const newTabs = new Map(state.openTabs);
|
|
3801
|
+
const targetTab = newTabs.get(routePath);
|
|
3802
|
+
if (targetTab) {
|
|
3803
|
+
delete targetTab.newTabTitle;
|
|
3804
|
+
newTabs.set(routePath, targetTab);
|
|
3805
|
+
return { openTabs: newTabs };
|
|
3806
|
+
}
|
|
3807
|
+
return state;
|
|
3808
|
+
});
|
|
3809
|
+
},
|
|
3810
|
+
/**
|
|
3811
|
+
* @zh 重置所有标签页的状态
|
|
3812
|
+
* @en Reset all tab states
|
|
3813
|
+
*/
|
|
3814
|
+
resetTabs: () => {
|
|
3815
|
+
set(() => {
|
|
3816
|
+
return { ...initialState$3 };
|
|
3817
|
+
});
|
|
3818
|
+
}
|
|
3819
|
+
};
|
|
3820
|
+
}, {
|
|
3797
3821
|
name: getAppNamespace("tabbar"),
|
|
3798
3822
|
/**
|
|
3799
3823
|
* activeKey 不需要持久化存储
|
|
@@ -4429,6 +4453,7 @@ var init_notification = __esmMin((() => {
|
|
|
4429
4453
|
const handleClick = (item) => {
|
|
4430
4454
|
if (readOnly) return;
|
|
4431
4455
|
onEventChange && onEventChange("read", item);
|
|
4456
|
+
if (item.link) window.open(item.link, "_blank", "noopener");
|
|
4432
4457
|
};
|
|
4433
4458
|
dot = useMemo(() => {
|
|
4434
4459
|
return !!notifications?.filter((item) => !item?.isRead).length;
|
|
@@ -27416,4 +27441,4 @@ init_ri();
|
|
|
27416
27441
|
init_menu_icons();
|
|
27417
27442
|
init_tree();
|
|
27418
27443
|
//#endregion
|
|
27419
|
-
export { AccessControl, AccessControlRoles, AntdApp, BasicButton, BasicContent, BasicTable, EmbeddedIcon, ErrorBoundary, ExternalIcon, FormAvatarItem, FormTreeItem, Iframe, LayoutCenterIcon, LayoutEffects, LayoutLeftIcon, LayoutRightIcon, MixedNavigationIcon, OutsidePageIcon, PageError, ProfileCardIcon, RiAccountCircleLine, RiContrastFill, RiFullscreenExitLine, RiFullscreenLine, RiMailCheckLine, RiMoonIcon, RiReactjsLine, RiSunIcon, RiUserSettingsLine, ServerErrorIcon, SideNavigationIcon, TopNavigationIcon, TwoColumnNavigationIcon, accessControlCodes, defineModule, fetchAddMenuItem, fetchAddRoleItem, fetchAsyncRoutes, fetchDeleteMenuItem, fetchDeleteRoleItem, fetchLogin, fetchLogout, fetchMenuByRoleId, fetchMenuList, fetchRefreshToken, fetchRoleList, fetchRoleMenu, fetchUpdateMenuItem, fetchUpdateRoleItem, fetchUserInfo, filterTree, getAllExpandedKeys, getAppInfo, getBooleanOptions, getModule, getModules, getRedirectPath, getRegisteredApiPrefix, getRegisteredStore, getRoutes, getYesNoOptions, handleTree, loadAll, mapTree, menuIcons, permissionPrefix, setupI18n, traverseTreeValues, unloadModule, useAccess, useAuthStore, usePreferences, useSlotNodes, useTabsStore, useUserStore, external_exports as z };
|
|
27444
|
+
export { AccessControl, AccessControlRoles, AntdApp, BasicButton, BasicContent, BasicTable, EmbeddedIcon, ErrorBoundary, ExternalIcon, FormAvatarItem, FormTreeItem, Iframe, LayoutCenterIcon, LayoutEffects, LayoutLeftIcon, LayoutRightIcon, MixedNavigationIcon, OutsidePageIcon, PageError, ProfileCardIcon, RiAccountCircleLine, RiContrastFill, RiFullscreenExitLine, RiFullscreenLine, RiMailCheckLine, RiMoonIcon, RiReactjsLine, RiSunIcon, RiUserSettingsLine, ServerErrorIcon, SideNavigationIcon, TopNavigationIcon, TwoColumnNavigationIcon, accessControlCodes, defineModule, fetchAddMenuItem, fetchAddRoleItem, fetchAsyncRoutes, fetchDeleteMenuItem, fetchDeleteRoleItem, fetchLogin, fetchLogout, fetchMenuByRoleId, fetchMenuList, fetchRefreshToken, fetchRoleList, fetchRoleMenu, fetchUpdateMenuItem, fetchUpdateRoleItem, fetchUserInfo, filterTree, getAllExpandedKeys, getAppInfo, getBooleanOptions, getModule, getModules, getRedirectPath, getRegisteredApiPrefix, getRegisteredStore, getRoutes, getYesNoOptions, handleTree, loadAll, mapTree, menuIcons, notify, permissionPrefix, setupI18n, traverseTreeValues, unloadModule, useAccess, useAuthStore, usePreferences, useSlotNodes, useTabsStore, useUserStore, external_exports as z };
|
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-aMxR7a5yEi7eE8DlhFuiNg=='; 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="aMxR7a5yEi7eE8DlhFuiNg==">{
|
|
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,11 +7,11 @@
|
|
|
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.1.
|
|
11
|
-
"@oj-module/runtime/contract": "0.1.
|
|
12
|
-
"@oj-module/runtime/contract/errors": "0.1.
|
|
13
|
-
"@plane/kit": "0.1.
|
|
14
|
-
"@plane/editor": "0.1.
|
|
10
|
+
"@oj-module/runtime": "0.1.20",
|
|
11
|
+
"@oj-module/runtime/contract": "0.1.20",
|
|
12
|
+
"@oj-module/runtime/contract/errors": "0.1.20",
|
|
13
|
+
"@plane/kit": "0.1.20",
|
|
14
|
+
"@plane/editor": "0.1.20",
|
|
15
15
|
"antd": "6.6.1",
|
|
16
16
|
"antd/locale/en_US": "6.6.1",
|
|
17
17
|
"antd/locale/zh_CN": "6.6.1",
|
package/src/build.ts
CHANGED