@oj-module/cli 0.2.2 → 0.2.6
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 +14 -3
- package/devkit/SKILL.md +16 -4
- package/devkit/framework-dev.md +8 -6
- package/devkit/manual.md +243 -23
- package/package.json +2 -2
- package/shell-dist/assets/runtime.js +143 -8
- package/shell-dist/index.html +2 -2
- package/shell-dist/versions.json +3 -3
- package/src/args.ts +67 -0
- package/src/build.ts +11 -3
- package/src/bundle.ts +197 -0
- package/src/contract/__tests__/handler-scan.test.ts +82 -0
- package/src/contract/check.ts +2 -1
- package/src/contract/handler-scan.ts +32 -0
- package/src/deterministic-tar.ts +120 -0
- package/src/dev.ts +4 -2
- package/src/enable.ts +28 -0
- package/src/index.ts +63 -2
- package/src/info.ts +3 -2
- package/src/init.ts +189 -14
- package/src/install.ts +268 -0
- package/src/keygen.ts +88 -0
- package/src/layout.ts +31 -0
- package/src/lifecycle/__tests__/authoring.test.ts +109 -0
- package/src/lifecycle/__tests__/bundle.test.ts +150 -0
- package/src/lifecycle/__tests__/deterministic-tar.test.ts +145 -0
- package/src/lifecycle/__tests__/fixtures/README.md +59 -0
- package/src/lifecycle/__tests__/fixtures/fixture-invalid/EXPECTED-VIOLATIONS.md +27 -0
- package/src/lifecycle/__tests__/fixtures/fixture-invalid/api/src/Demo/manifest.yaml +6 -0
- package/src/lifecycle/__tests__/fixtures/fixture-invalid/manifest.yaml +40 -0
- package/src/lifecycle/__tests__/fixtures/fixture-invalid/web/src/Demo/entry.ts +14 -0
- package/src/lifecycle/__tests__/fixtures/fixture-invalid/web/src/Demo/locales/en-US.json +12 -0
- package/src/lifecycle/__tests__/fixtures/fixture-invalid/web/src/Demo/locales/zh-CN.json +12 -0
- package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/manifest.yaml +9 -0
- package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/migrations/0001__init.sql +6 -0
- package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/ping/api.ts +9 -0
- package/src/lifecycle/__tests__/fixtures/fixture-valid/api/src/demo-solution/schema.yaml +9 -0
- package/src/lifecycle/__tests__/fixtures/fixture-valid/manifest.yaml +96 -0
- package/src/lifecycle/__tests__/fixtures/fixture-valid/web/src/demo-solution/entry.ts +23 -0
- package/src/lifecycle/__tests__/fixtures/fixture-valid/web/src/demo-solution/locales/en-US.json +29 -0
- package/src/lifecycle/__tests__/fixtures/fixture-valid/web/src/demo-solution/locales/zh-CN.json +29 -0
- package/src/lifecycle/__tests__/fixtures/sig/demo-solution-0.2.0.tgz +0 -0
- package/src/lifecycle/__tests__/fixtures/sig/demo-solution-0.2.0.tgz.bad.sig +7 -0
- package/src/lifecycle/__tests__/fixtures/sig/demo-solution-0.2.0.tgz.sig +7 -0
- package/src/lifecycle/__tests__/fixtures/sig/demo-solution-0.2.0.tgz.unknown.sig +7 -0
- package/src/lifecycle/__tests__/fixtures/sig/gen-signed-fixtures.d.mts +21 -0
- package/src/lifecycle/__tests__/fixtures/sig/gen-signed-fixtures.mjs +196 -0
- package/src/lifecycle/__tests__/helpers.ts +78 -0
- package/src/lifecycle/__tests__/install.test.ts +514 -0
- package/src/lifecycle/__tests__/keygen.test.ts +61 -0
- package/src/lifecycle/__tests__/manifest-contract.test.ts +228 -0
- package/src/lifecycle/__tests__/publish.test.ts +80 -0
- package/src/lifecycle/__tests__/sig-fixtures.test.ts +100 -0
- package/src/lifecycle/__tests__/sign.test.ts +131 -0
- package/src/lifecycle/__tests__/smoke.test.ts +61 -0
- package/src/lifecycle/__tests__/trust.test.ts +177 -0
- package/src/lifecycle/__tests__/uninstall.test.ts +87 -0
- package/src/lifecycle/keygen.ts +40 -0
- package/src/lifecycle/sign.ts +74 -0
- package/src/manifest-contract.ts +696 -0
- package/src/oj-cert.ts +19 -0
- package/src/oj-cmd.ts +3 -3
- package/src/preview.ts +2 -2
- package/src/publish.ts +150 -0
- package/src/sign.ts +128 -0
- package/src/site.ts +146 -0
- package/src/trust.ts +116 -0
- package/src/uninstall.ts +29 -0
- package/src/usage.ts +35 -1
- package/templates/README.md +1 -1
- package/templates/api/config.yaml +47 -0
- package/templates/api/seed.mysql.sql +1 -1
- package/templates/api/seed.pg.sql +1 -1
- package/templates/api/src/_platform/_lifecycle/atomic.ts +98 -0
- package/templates/api/src/_platform/_lifecycle/history.ts +31 -0
- package/templates/api/src/_platform/_lifecycle/installer.ts +1080 -0
- package/templates/api/src/_platform/_lifecycle/lock.ts +148 -0
- package/templates/api/src/_platform/_lifecycle/semver.ts +61 -0
- package/templates/api/src/_platform/_lifecycle/sha384.ts +187 -0
- package/templates/api/src/_platform/_lifecycle/state.ts +256 -0
- package/templates/api/src/_platform/_lifecycle/steps/deploy-web.ts +182 -0
- package/templates/api/src/_platform/_lifecycle/steps/download.ts +90 -0
- package/templates/api/src/_platform/_lifecycle/steps/extract.ts +239 -0
- package/templates/api/src/_platform/_lifecycle/steps/migrate.ts +363 -0
- package/templates/api/src/_platform/_lifecycle/steps/preflight.ts +590 -0
- package/templates/api/src/_platform/_lifecycle/steps/register.ts +211 -0
- package/templates/api/src/_platform/_lifecycle/steps/reload.ts +81 -0
- package/templates/api/src/_platform/_lifecycle/steps/smoke.ts +216 -0
- package/templates/api/src/_platform/_lifecycle/steps/verify.ts +207 -0
- package/templates/api/src/_platform/_lifecycle/tasks.ts +119 -0
- package/templates/api/src/_platform/_lifecycle/text-edit.ts +110 -0
- package/templates/api/src/_platform/_lifecycle/tgz-guard.ts +41 -0
- package/templates/api/src/_platform/_lifecycle/trust.ts +229 -0
- package/templates/api/src/_platform/_lifecycle/types.ts +94 -0
- package/templates/api/src/_platform/_lifecycle/uninstall.ts +283 -0
- package/templates/api/src/_platform/_lifecycle/verify-checks.ts +228 -0
- package/templates/api/src/_platform/_lifecycle/verify-report.ts +92 -0
- package/templates/api/src/_platform/_shared/service/access.ts +2 -2
- package/templates/api/src/_platform/_shared/service/settings.ts +10 -1
- package/templates/api/src/_platform/_shared/service/solution-perms.ts +14 -7
- package/templates/api/src/_platform/_shared/site-facade.ts +44 -0
- package/templates/api/src/_platform/manifest.yaml +1 -0
- package/templates/api/src/_platform/market/api.ts +883 -0
- package/templates/api/src/_platform/market/bootstrap.ts +39 -0
- package/templates/api/src/_platform/migrations/0010__sys_solution_route.mysql.sql +17 -0
- package/templates/api/src/_platform/migrations/0010__sys_solution_route.pg.sql +16 -0
- package/templates/api/src/_platform/migrations/0010__sys_solution_route.sql +18 -0
- package/templates/api/src/_platform/migrations/0011__sys_setting_group_label.mysql.sql +2 -0
- package/templates/api/src/_platform/migrations/0011__sys_setting_group_label.sql +4 -0
- package/templates/api/src/_platform/migrations/0012__market_history.mysql.sql +13 -0
- package/templates/api/src/_platform/migrations/0012__market_history.pg.sql +12 -0
- package/templates/api/src/_platform/migrations/0012__market_history.sql +14 -0
- package/templates/api/src/_platform/schema.yaml +21 -4
- package/templates/api/src/_platform/seed.sql +2 -2
- package/templates/api/src/_platform/solutions/api.ts +212 -0
- package/templates/api/src/demo/manifest.yaml +5 -0
- package/templates/api/src/demo/migrations/0001__create_todos.sql +9 -0
- package/templates/api/src/demo/schema.yaml +11 -0
- package/templates/api/src/demo/seed.sql +5 -0
- package/templates/api/src/demo/todos/api.ts +59 -0
- package/templates/api/src/home/line/api.ts +57 -0
- package/templates/api/src/home/manifest.yaml +6 -0
- package/templates/api/src/home/migrations/0001__create_home_tables.sql +13 -0
- package/templates/api/src/home/pie/api.ts +40 -0
- package/templates/api/src/home/schema.yaml +14 -0
- package/templates/api/src/home/seed.sql +70 -0
- package/templates/api/src/system/oplog/api.ts +44 -2
- package/templates/api/tests/fixture.ts +16 -0
- package/templates/api/tests/lifecycle/atomic-state.spec.ts +183 -0
- package/templates/api/tests/lifecycle/bootstrap.spec.ts +116 -0
- package/templates/api/tests/lifecycle/deploy-smoke.spec.ts +492 -0
- package/templates/api/tests/lifecycle/installer.spec.ts +1218 -0
- package/templates/api/tests/lifecycle/market-api.spec.ts +971 -0
- package/templates/api/tests/lifecycle/migrate.spec.ts +575 -0
- package/templates/api/tests/lifecycle/preflight.spec.ts +255 -0
- package/templates/api/tests/lifecycle/register.spec.ts +194 -0
- package/templates/api/tests/lifecycle/semver.spec.ts +43 -0
- package/templates/api/tests/lifecycle/sha384.spec.ts +48 -0
- package/templates/api/tests/lifecycle/sig-fixtures.spec.ts +65 -0
- package/templates/api/tests/lifecycle/solutions-api.spec.ts +98 -0
- package/templates/api/tests/lifecycle/steps-fs.spec.ts +623 -0
- package/templates/api/tests/lifecycle/text-edit.spec.ts +177 -0
- package/templates/api/tests/lifecycle/tgz-guard.spec.ts +103 -0
- package/templates/api/tests/lifecycle/trust.spec.ts +310 -0
- package/templates/api/tests/lifecycle/uninstall.spec.ts +386 -0
- package/templates/api/tests/lifecycle/verify-checks.spec.ts +385 -0
- package/templates/api/tests/lifecycle/verify-report.spec.ts +290 -0
- package/templates/api/tests/rbac-settings.spec.ts +8 -0
- package/templates/init.include.yaml +62 -0
- package/templates/openapi.json +527 -0
- package/templates/package.json +52 -0
- package/templates/web/src/_platform/__client/api.schemas.ts +8 -0
- package/templates/web/src/_platform/__client/api.ts +335 -0
- package/templates/web/src/about/entry.ts +42 -0
- package/templates/web/src/about/locales/en-US.json +16 -0
- package/templates/web/src/about/locales/zh-CN.json +16 -0
- package/templates/web/src/about/pages/constants.ts +20 -0
- package/templates/web/src/about/pages/index.tsx +135 -0
- package/templates/web/src/access/entry.ts +74 -0
- package/templates/web/src/access/locales/en-US.json +46 -0
- package/templates/web/src/access/locales/zh-CN.json +46 -0
- package/templates/web/src/access/pages/access-mode/index.tsx +16 -0
- package/templates/web/src/access/pages/admin-visible/index.tsx +16 -0
- package/templates/web/src/access/pages/button-control/index.tsx +181 -0
- package/templates/web/src/access/pages/common-visible/index.tsx +16 -0
- package/templates/web/src/access/pages/page-control/index.tsx +142 -0
- package/templates/web/src/demo/__client/api.schemas.ts +20 -0
- package/templates/web/src/demo/__client/api.ts +89 -0
- package/templates/web/src/exception/entry.ts +80 -0
- package/templates/web/src/exception/locales/en-US.json +13 -0
- package/templates/web/src/exception/locales/zh-CN.json +13 -0
- package/templates/web/src/exception/pages/403/index.tsx +30 -0
- package/templates/web/src/exception/pages/404/index.tsx +30 -0
- package/templates/web/src/exception/pages/500/index.tsx +30 -0
- package/templates/web/src/exception/pages/unknown-component/index.tsx +38 -0
- package/templates/web/src/home/__client/api.schemas.ts +22 -0
- package/templates/web/src/home/__client/api.ts +113 -0
- package/templates/web/src/login/locales/en-US.json +6 -0
- package/templates/web/src/login/locales/zh-CN.json +6 -0
- package/templates/web/src/login/pages/login.tsx +67 -18
- package/templates/web/src/oj-market/__tests__/CardGrid.test.tsx +93 -0
- package/templates/web/src/oj-market/__tests__/HistoryDrawer.test.tsx +28 -0
- package/templates/web/src/oj-market/__tests__/InstallDialog.test.tsx +69 -0
- package/templates/web/src/oj-market/__tests__/RollbackDialog.test.tsx +43 -0
- package/templates/web/src/oj-market/__tests__/UninstallConfirm.test.tsx +30 -0
- package/templates/web/src/oj-market/__tests__/marketClient.fake.ts +70 -0
- package/templates/web/src/oj-market/__tests__/poll.test.ts +24 -0
- package/templates/web/src/oj-market/__tests__/render.tsx +14 -0
- package/templates/web/src/oj-market/__tests__/runtime-stub.ts +14 -0
- package/templates/web/src/oj-market/__tests__/semver.test.ts +26 -0
- package/templates/web/src/oj-market/api/MarketClientProvider.tsx +12 -0
- package/templates/web/src/oj-market/api/client.ts +135 -0
- package/templates/web/src/oj-market/api/context.ts +11 -0
- package/templates/web/src/oj-market/api/defaultClient.ts +7 -0
- package/templates/web/src/oj-market/components/CardGrid.tsx +76 -0
- package/templates/web/src/oj-market/components/CardItem.tsx +102 -0
- package/templates/web/src/oj-market/components/DetailDrawer.tsx +53 -0
- package/templates/web/src/oj-market/components/HistoryDrawer.tsx +75 -0
- package/templates/web/src/oj-market/components/InstallDialog.tsx +112 -0
- package/templates/web/src/oj-market/components/RollbackDialog.tsx +79 -0
- package/templates/web/src/oj-market/components/UninstallConfirm.tsx +74 -0
- package/templates/web/src/oj-market/entry.ts +27 -0
- package/templates/web/src/oj-market/hooks/useMarket.ts +91 -0
- package/templates/web/src/oj-market/locales/en-US.json +100 -0
- package/templates/web/src/oj-market/locales/zh-CN.json +100 -0
- package/templates/web/src/oj-market/pages/MarketApp.tsx +136 -0
- package/templates/web/src/oj-market/types.ts +84 -0
- package/templates/web/src/oj-market/utils/poll.ts +36 -0
- package/templates/web/src/oj-market/utils/semver.ts +14 -0
- package/templates/web/src/outside/entry.ts +80 -0
- package/templates/web/src/outside/locales/en-US.json +10 -0
- package/templates/web/src/outside/locales/zh-CN.json +10 -0
- package/templates/web/src/personal-center/entry.ts +1 -10
- package/templates/web/src/personal-center/locales/en-US.json +1 -5
- package/templates/web/src/personal-center/locales/zh-CN.json +1 -5
- package/templates/web/src/route-nest/entry.ts +73 -0
- package/templates/web/src/route-nest/locales/en-US.json +9 -0
- package/templates/web/src/route-nest/locales/zh-CN.json +9 -0
- package/templates/web/src/route-nest/pages/menu1/menu1-1/index.tsx +5 -0
- package/templates/web/src/route-nest/pages/menu1/menu1-2/index.tsx +5 -0
- package/templates/web/src/route-nest/pages/menu2/index.tsx +5 -0
- package/templates/web/src/system/__client/api.schemas.ts +12 -0
- package/templates/web/src/system/__client/api.ts +25 -1
- package/templates/web/src/system/pages/oplog/index.tsx +1 -1
- package/templates/web/src/system/pages/roles/index.tsx +1 -0
- package/templates/web/src/system/pages/settings/index.tsx +1 -1
- package/templates/web/src/system/pages/users/index.tsx +1 -1
- package/templates/web.config.ts +32 -40
- package/vendor/host-versions.json +3 -3
- package/templates/pnpm-workspace.yaml +0 -14
- package/templates/web/src/personal-center/pages/settings/index.tsx +0 -5
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"accessMode": {
|
|
3
|
+
"title": "后端访问权限可见",
|
|
4
|
+
"description": "当前页面仅支持通过后端接口获取路由可见"
|
|
5
|
+
},
|
|
6
|
+
"adminVisible": {
|
|
7
|
+
"title": "Admin 可见",
|
|
8
|
+
"description": "当前页面仅 Admin 账号可见"
|
|
9
|
+
},
|
|
10
|
+
"commonVisible": {
|
|
11
|
+
"title": "Common 可见",
|
|
12
|
+
"description": "当前页面仅 Common 账号可见"
|
|
13
|
+
},
|
|
14
|
+
"pageControl": {
|
|
15
|
+
"alertMessage": "前端页面访问权限演示",
|
|
16
|
+
"alertDescription": "切换不同的账号,观察左侧「权限演示」子菜单变化。",
|
|
17
|
+
"cardTitle": "权限模式",
|
|
18
|
+
"frontendControl": "前端权限控制",
|
|
19
|
+
"backendControl": "后端权限控制",
|
|
20
|
+
"switchToFrontend": "切换为前端权限模式",
|
|
21
|
+
"switchToBackend": "切换为后端权限模式",
|
|
22
|
+
"switchAdmin": "切换 Admin 用户",
|
|
23
|
+
"switchCommon": "切换 Common 用户",
|
|
24
|
+
"currentPermissionMode": "当前权限模式:",
|
|
25
|
+
"accountSwitching": "账号切换",
|
|
26
|
+
"warningMessage": "enableFrontendAceess 和 enableBackendAceess 相等时不允许切换路由模式。"
|
|
27
|
+
},
|
|
28
|
+
"buttonControl": {
|
|
29
|
+
"alertMessage": "前端按钮访问权限演示",
|
|
30
|
+
"alertDescription": "切换不同的账号,观察按钮变化。",
|
|
31
|
+
"currentRole": "当前角色",
|
|
32
|
+
"switchAdmin": "切换 Admin 用户",
|
|
33
|
+
"switchCommon": "切换 Common 用户",
|
|
34
|
+
"componentControlPermissionCodes": "组件形式控制 - 权限码",
|
|
35
|
+
"componentControlRoles": "组件形式控制 - 角色",
|
|
36
|
+
"functionControlPermissionCodes": "函数形式控制 - 权限码",
|
|
37
|
+
"functionControlRoles": "函数形式控制 - 角色"
|
|
38
|
+
},
|
|
39
|
+
"menu": {
|
|
40
|
+
"access": "权限演示",
|
|
41
|
+
"pageControl": "页面控制",
|
|
42
|
+
"buttonControl": "按钮控制",
|
|
43
|
+
"adminVisible": "Admin 可见",
|
|
44
|
+
"commonVisible": "Common 可见"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CloudOutlined } from "@ant-design/icons";
|
|
2
|
+
import { Result } from "antd";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
4
|
+
|
|
5
|
+
export default function AccessMode() {
|
|
6
|
+
const { t } = useTranslation();
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<Result
|
|
10
|
+
icon={<CloudOutlined />}
|
|
11
|
+
status="success"
|
|
12
|
+
title={t("access:accessMode.title")}
|
|
13
|
+
subTitle={t("access:accessMode.description")}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SmileOutlined } from "@ant-design/icons";
|
|
2
|
+
import { Result } from "antd";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
4
|
+
|
|
5
|
+
export default function AdminVisible() {
|
|
6
|
+
const { t } = useTranslation();
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<Result
|
|
10
|
+
icon={<SmileOutlined />}
|
|
11
|
+
status="success"
|
|
12
|
+
title={t("access:adminVisible.title")}
|
|
13
|
+
subTitle={t("access:adminVisible.description")}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { LoginInfo } from "@oj-module/runtime";
|
|
2
|
+
import { AccessControl, accessControlCodes, AccessControlRoles, BasicContent, useAccess, useAuthStore, useUserStore } from "@oj-module/runtime";
|
|
3
|
+
|
|
4
|
+
import { Alert, Button, Card, Typography } from "antd";
|
|
5
|
+
import { useTranslation } from "react-i18next";
|
|
6
|
+
import { useNavigate } from "react-router";
|
|
7
|
+
|
|
8
|
+
const accounts: Record<string, LoginInfo> = {
|
|
9
|
+
[AccessControlRoles.admin]: {
|
|
10
|
+
password: "123456789admin",
|
|
11
|
+
username: AccessControlRoles.admin,
|
|
12
|
+
},
|
|
13
|
+
[AccessControlRoles.common]: {
|
|
14
|
+
password: "123456789admin",
|
|
15
|
+
username: AccessControlRoles.common,
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default function ButtonControl() {
|
|
20
|
+
const { t } = useTranslation();
|
|
21
|
+
const navigate = useNavigate();
|
|
22
|
+
const { hasAccessByCodes, hasAccessByRoles } = useAccess();
|
|
23
|
+
const { roles: userRoles } = useUserStore();
|
|
24
|
+
const resetAllStores = useAuthStore(state => state.reset);
|
|
25
|
+
const authLogin = useAuthStore(state => state.login);
|
|
26
|
+
|
|
27
|
+
function roleButtonType(role: string) {
|
|
28
|
+
return userRoles.includes(role) ? "primary" : "default";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function changeAccount(role: string) {
|
|
32
|
+
if (userRoles.includes(role)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const account = accounts[role];
|
|
37
|
+
resetAllStores();
|
|
38
|
+
if (account) {
|
|
39
|
+
authLogin(account).then(() => {
|
|
40
|
+
navigate(0);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<BasicContent className="flex flex-col gap-4">
|
|
47
|
+
<Alert title={t("access:buttonControl.alertMessage")} description={t("access:buttonControl.alertDescription")}></Alert>
|
|
48
|
+
<Card
|
|
49
|
+
title={(
|
|
50
|
+
<>
|
|
51
|
+
{t("access:buttonControl.currentRole")}
|
|
52
|
+
|
|
53
|
+
<Typography.Text mark code>{userRoles}</Typography.Text>
|
|
54
|
+
</>
|
|
55
|
+
)}
|
|
56
|
+
>
|
|
57
|
+
<div className="flex gap-4">
|
|
58
|
+
<Button type={roleButtonType(AccessControlRoles.admin)} onClick={() => changeAccount(AccessControlRoles.admin)}>
|
|
59
|
+
{t("access:buttonControl.switchAdmin")}
|
|
60
|
+
</Button>
|
|
61
|
+
<Button type={roleButtonType(AccessControlRoles.common)} onClick={() => changeAccount(AccessControlRoles.common)}>
|
|
62
|
+
{t("access:buttonControl.switchCommon")}
|
|
63
|
+
</Button>
|
|
64
|
+
</div>
|
|
65
|
+
</Card>
|
|
66
|
+
<Card title={t("access:buttonControl.componentControlPermissionCodes")}>
|
|
67
|
+
<div className="flex items-center gap-4">
|
|
68
|
+
<AccessControl codes={accessControlCodes.get}>
|
|
69
|
+
<Typography.Text code>
|
|
70
|
+
{accessControlCodes.get}
|
|
71
|
+
</Typography.Text>
|
|
72
|
+
</AccessControl>
|
|
73
|
+
|
|
74
|
+
<AccessControl codes={accessControlCodes.update}>
|
|
75
|
+
<Typography.Text code>
|
|
76
|
+
{accessControlCodes.update}
|
|
77
|
+
</Typography.Text>
|
|
78
|
+
</AccessControl>
|
|
79
|
+
|
|
80
|
+
<AccessControl codes={accessControlCodes.delete}>
|
|
81
|
+
<Typography.Text code>
|
|
82
|
+
{accessControlCodes.delete}
|
|
83
|
+
</Typography.Text>
|
|
84
|
+
</AccessControl>
|
|
85
|
+
|
|
86
|
+
<AccessControl codes={accessControlCodes.add}>
|
|
87
|
+
<Typography.Text code>
|
|
88
|
+
{accessControlCodes.add}
|
|
89
|
+
</Typography.Text>
|
|
90
|
+
</AccessControl>
|
|
91
|
+
</div>
|
|
92
|
+
</Card>
|
|
93
|
+
<Card title={t("access:buttonControl.componentControlRoles")}>
|
|
94
|
+
<div className="flex items-center gap-4">
|
|
95
|
+
<AccessControl type="role" codes={[AccessControlRoles.admin, AccessControlRoles.common]}>
|
|
96
|
+
<Typography.Text code>
|
|
97
|
+
{t("access:adminVisible.title")}
|
|
98
|
+
&
|
|
99
|
+
{t("access:commonVisible.title")}
|
|
100
|
+
</Typography.Text>
|
|
101
|
+
</AccessControl>
|
|
102
|
+
|
|
103
|
+
<AccessControl type="role" codes={AccessControlRoles.admin}>
|
|
104
|
+
<Typography.Text code>
|
|
105
|
+
{t("access:adminVisible.title")}
|
|
106
|
+
</Typography.Text>
|
|
107
|
+
</AccessControl>
|
|
108
|
+
|
|
109
|
+
<AccessControl type="role" codes={AccessControlRoles.common}>
|
|
110
|
+
<Typography.Text code>
|
|
111
|
+
{t("access:commonVisible.title")}
|
|
112
|
+
</Typography.Text>
|
|
113
|
+
</AccessControl>
|
|
114
|
+
</div>
|
|
115
|
+
</Card>
|
|
116
|
+
<Card title={t("access:buttonControl.functionControlPermissionCodes")}>
|
|
117
|
+
<div className="flex items-center gap-4">
|
|
118
|
+
{
|
|
119
|
+
hasAccessByCodes(accessControlCodes.get) && (
|
|
120
|
+
<Typography.Text code>
|
|
121
|
+
{accessControlCodes.get}
|
|
122
|
+
</Typography.Text>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
{
|
|
127
|
+
hasAccessByCodes(accessControlCodes.update) && (
|
|
128
|
+
<Typography.Text code>
|
|
129
|
+
{accessControlCodes.update}
|
|
130
|
+
</Typography.Text>
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
{
|
|
135
|
+
hasAccessByCodes(accessControlCodes.delete) && (
|
|
136
|
+
<Typography.Text code>
|
|
137
|
+
{accessControlCodes.delete}
|
|
138
|
+
</Typography.Text>
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
{
|
|
143
|
+
hasAccessByCodes([accessControlCodes.add]) && (
|
|
144
|
+
<Typography.Text code>
|
|
145
|
+
{accessControlCodes.add}
|
|
146
|
+
</Typography.Text>
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
</div>
|
|
151
|
+
</Card>
|
|
152
|
+
<Card title={t("access:buttonControl.functionControlRoles")}>
|
|
153
|
+
<div className="flex items-center gap-4">
|
|
154
|
+
{
|
|
155
|
+
hasAccessByRoles([AccessControlRoles.admin, AccessControlRoles.common]) && (
|
|
156
|
+
<Typography.Text code>
|
|
157
|
+
{t("access:adminVisible.title")}
|
|
158
|
+
&
|
|
159
|
+
{t("access:commonVisible.title")}
|
|
160
|
+
</Typography.Text>
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
{
|
|
164
|
+
hasAccessByRoles([AccessControlRoles.admin]) && (
|
|
165
|
+
<Typography.Text code>
|
|
166
|
+
{t("access:adminVisible.title")}
|
|
167
|
+
</Typography.Text>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
{
|
|
171
|
+
hasAccessByRoles(AccessControlRoles.common) && (
|
|
172
|
+
<Typography.Text code>
|
|
173
|
+
{t("access:commonVisible.title")}
|
|
174
|
+
</Typography.Text>
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
</div>
|
|
178
|
+
</Card>
|
|
179
|
+
</BasicContent>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SmileOutlined } from "@ant-design/icons";
|
|
2
|
+
import { Result } from "antd";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
4
|
+
|
|
5
|
+
export default function CommonVisible() {
|
|
6
|
+
const { t } = useTranslation();
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<Result
|
|
10
|
+
icon={<SmileOutlined />}
|
|
11
|
+
status="success"
|
|
12
|
+
title={t("access:commonVisible.title")}
|
|
13
|
+
subTitle={t("access:commonVisible.description")}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { LoginInfo } from "@oj-module/runtime";
|
|
2
|
+
import { AccessControlRoles, BasicContent, useAuthStore, usePreferences, useUserStore } from "@oj-module/runtime";
|
|
3
|
+
|
|
4
|
+
import { Alert, Button, Card, Typography } from "antd";
|
|
5
|
+
import { clsx } from "clsx";
|
|
6
|
+
import { useTranslation } from "react-i18next";
|
|
7
|
+
import { useNavigate } from "react-router";
|
|
8
|
+
|
|
9
|
+
const accounts: Record<string, LoginInfo> = {
|
|
10
|
+
[AccessControlRoles.admin]: {
|
|
11
|
+
password: "123456789admin",
|
|
12
|
+
username: AccessControlRoles.admin,
|
|
13
|
+
},
|
|
14
|
+
[AccessControlRoles.common]: {
|
|
15
|
+
password: "123456789admin",
|
|
16
|
+
username: AccessControlRoles.common,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default function PageControl() {
|
|
21
|
+
const { t } = useTranslation();
|
|
22
|
+
const navigate = useNavigate();
|
|
23
|
+
const { enableFrontendAceess, enableBackendAccess, setPreferences } = usePreferences();
|
|
24
|
+
const { roles: userRoles } = useUserStore();
|
|
25
|
+
const resetAllStores = useAuthStore(state => state.reset);
|
|
26
|
+
const authLogin = useAuthStore(state => state.login);
|
|
27
|
+
|
|
28
|
+
function roleButtonType(role: string) {
|
|
29
|
+
return userRoles.includes(role) ? "primary" : "default";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function changeAccount(role: string) {
|
|
33
|
+
if (userRoles.includes(role)) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const account = accounts[role];
|
|
38
|
+
resetAllStores();
|
|
39
|
+
if (account) {
|
|
40
|
+
authLogin(account).then(() => {
|
|
41
|
+
navigate(0);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function toggleAccessMode() {
|
|
47
|
+
if (enableFrontendAceess && !enableBackendAccess) {
|
|
48
|
+
setPreferences({
|
|
49
|
+
enableFrontendAceess: false,
|
|
50
|
+
enableBackendAccess: true,
|
|
51
|
+
});
|
|
52
|
+
resetAllStores();
|
|
53
|
+
authLogin(accounts.admin).then(() => {
|
|
54
|
+
setTimeout(() => {
|
|
55
|
+
navigate(0);
|
|
56
|
+
}, 150);
|
|
57
|
+
});
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (enableBackendAccess && !enableFrontendAceess) {
|
|
61
|
+
setPreferences({
|
|
62
|
+
enableFrontendAceess: true,
|
|
63
|
+
enableBackendAccess: false,
|
|
64
|
+
});
|
|
65
|
+
resetAllStores();
|
|
66
|
+
authLogin(accounts.admin).then(() => {
|
|
67
|
+
setTimeout(() => {
|
|
68
|
+
navigate(0);
|
|
69
|
+
}, 150);
|
|
70
|
+
});
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
window.$message?.warning(t("access:pageControl.warningMessage"));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<BasicContent className="flex flex-col gap-4">
|
|
78
|
+
<Alert type="info" title={t("access:pageControl.alertMessage")} description={t("access:pageControl.alertDescription")}></Alert>
|
|
79
|
+
<Card title={t("access:pageControl.cardTitle")}>
|
|
80
|
+
<Alert
|
|
81
|
+
type="warning"
|
|
82
|
+
className={clsx(
|
|
83
|
+
"mb-4",
|
|
84
|
+
{ hidden: enableFrontendAceess !== enableBackendAccess },
|
|
85
|
+
)}
|
|
86
|
+
description={t("access:pageControl.warningMessage")}
|
|
87
|
+
/>
|
|
88
|
+
<div className="flex items-center gap-4">
|
|
89
|
+
{t("access:pageControl.currentPermissionMode")}
|
|
90
|
+
{enableFrontendAceess
|
|
91
|
+
? (
|
|
92
|
+
<Typography.Text code>{t("access:pageControl.frontendControl")}</Typography.Text>
|
|
93
|
+
)
|
|
94
|
+
: (
|
|
95
|
+
""
|
|
96
|
+
)}
|
|
97
|
+
|
|
98
|
+
{enableBackendAccess
|
|
99
|
+
? (
|
|
100
|
+
<Typography.Text code>{t("access:pageControl.backendControl")}</Typography.Text>
|
|
101
|
+
)
|
|
102
|
+
: (
|
|
103
|
+
""
|
|
104
|
+
)}
|
|
105
|
+
|
|
106
|
+
{enableBackendAccess
|
|
107
|
+
? (
|
|
108
|
+
<Button
|
|
109
|
+
disabled={enableFrontendAceess === enableBackendAccess}
|
|
110
|
+
type="primary"
|
|
111
|
+
onClick={() => toggleAccessMode()}
|
|
112
|
+
>
|
|
113
|
+
{t("access:pageControl.switchToFrontend")}
|
|
114
|
+
</Button>
|
|
115
|
+
)
|
|
116
|
+
: null}
|
|
117
|
+
{enableFrontendAceess
|
|
118
|
+
? (
|
|
119
|
+
<Button
|
|
120
|
+
disabled={enableFrontendAceess === enableBackendAccess}
|
|
121
|
+
type="primary"
|
|
122
|
+
onClick={() => toggleAccessMode()}
|
|
123
|
+
>
|
|
124
|
+
{t("access:pageControl.switchToBackend")}
|
|
125
|
+
</Button>
|
|
126
|
+
)
|
|
127
|
+
: null}
|
|
128
|
+
</div>
|
|
129
|
+
</Card>
|
|
130
|
+
<Card title={t("access:pageControl.accountSwitching")}>
|
|
131
|
+
<div className="flex gap-4">
|
|
132
|
+
<Button type={roleButtonType(AccessControlRoles.admin)} onClick={() => changeAccount(AccessControlRoles.admin)}>
|
|
133
|
+
{t("access:pageControl.switchAdmin")}
|
|
134
|
+
</Button>
|
|
135
|
+
<Button type={roleButtonType(AccessControlRoles.common)} onClick={() => changeAccount(AccessControlRoles.common)}>
|
|
136
|
+
{t("access:pageControl.switchCommon")}
|
|
137
|
+
</Button>
|
|
138
|
+
</div>
|
|
139
|
+
</Card>
|
|
140
|
+
</BasicContent>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
"demoTodosGet": {
|
|
8
|
+
query: z.object({
|
|
9
|
+
"keyword": z.string().optional()
|
|
10
|
+
}),
|
|
11
|
+
data: z.object({
|
|
12
|
+
"list": z.array(z.object({
|
|
13
|
+
"id": z.number(),
|
|
14
|
+
"title": z.string(),
|
|
15
|
+
"done": z.boolean()
|
|
16
|
+
})),
|
|
17
|
+
"total": z.number()
|
|
18
|
+
}),
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
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 = "/demo";
|
|
16
|
+
|
|
17
|
+
/** 认领身份前缀(契约 apiPrefix,字面等于模块目录名):跨模块认领护栏对比基准 */
|
|
18
|
+
export const MODULE_PREFIX = "/demo";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 模块 entry 的 onInit 里一行创建(构造即 install):完成 apiPrefix 登记 +
|
|
22
|
+
* scoped request 绑定,返回绑好前缀的端点函数集合。一模块一 client;
|
|
23
|
+
* 重复构造幂等安全(前缀重复登记同值、request 重绑同值)。
|
|
24
|
+
*/
|
|
25
|
+
export function createDemoClient(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 { demoTodosGet };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @deprecated 请改用 createDemoClient(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 里调用 createDemoClient(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 DemoTodosGetQuery = z.input<(typeof schemas)["demoTodosGet"]["query"]>;
|
|
68
|
+
export type DemoTodosGetData = z.infer<(typeof schemas)["demoTodosGet"]["data"]>;
|
|
69
|
+
|
|
70
|
+
export async function demoTodosGet(query: DemoTodosGetQuery): Promise<DemoTodosGetData> {
|
|
71
|
+
const client = ensureReq();
|
|
72
|
+
try {
|
|
73
|
+
const env = await client.get(`demo/todos`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<DemoTodosGetData>>();
|
|
74
|
+
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
75
|
+
if (typeof env.code === "number" && env.code !== 0)
|
|
76
|
+
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
77
|
+
const data = env.data as DemoTodosGetData;
|
|
78
|
+
if (import.meta.env.DEV) {
|
|
79
|
+
const { schemas } = await import("./api.schemas");
|
|
80
|
+
const r = schemas["demoTodosGet"].data.safeParse(data);
|
|
81
|
+
if (!r.success)
|
|
82
|
+
throw new ContractApiError(-1, `[契约违例] demoTodosGet 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
|
|
83
|
+
}
|
|
84
|
+
return data;
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
throw await toApiError(e);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { AppRouteRecordRaw, ModuleDefinition } from "@oj-module/runtime";
|
|
2
|
+
import {
|
|
3
|
+
AppstoreOutlined,
|
|
4
|
+
IssuesCloseOutlined,
|
|
5
|
+
MinusSquareOutlined,
|
|
6
|
+
StopOutlined,
|
|
7
|
+
} from "@ant-design/icons";
|
|
8
|
+
|
|
9
|
+
import { ServerErrorIcon } from "@oj-module/runtime";
|
|
10
|
+
import { createElement, lazy } from "react";
|
|
11
|
+
|
|
12
|
+
const exceptionPath = "/exception";
|
|
13
|
+
const exception403Path = "/exception/403";
|
|
14
|
+
const exception404Path = "/exception/404";
|
|
15
|
+
const exception500Path = "/exception/500";
|
|
16
|
+
const exceptionUnknownComponentPath = "/exception/not-found-component";
|
|
17
|
+
|
|
18
|
+
const Exception403 = lazy(() => import("./pages/403"));
|
|
19
|
+
const Exception404 = lazy(() => import("./pages/404"));
|
|
20
|
+
const Exception500 = lazy(() => import("./pages/500"));
|
|
21
|
+
const ExceptionUnknownComponent = lazy(() => import("./pages/unknown-component"));
|
|
22
|
+
|
|
23
|
+
const routes: AppRouteRecordRaw[] = [
|
|
24
|
+
{
|
|
25
|
+
path: exceptionPath,
|
|
26
|
+
handle: {
|
|
27
|
+
layout: "container",
|
|
28
|
+
order: 90,
|
|
29
|
+
title: "exception:menu.exception",
|
|
30
|
+
icon: createElement(IssuesCloseOutlined),
|
|
31
|
+
},
|
|
32
|
+
children: [
|
|
33
|
+
{
|
|
34
|
+
path: exception403Path,
|
|
35
|
+
Component: Exception403,
|
|
36
|
+
handle: {
|
|
37
|
+
title: "exception:menu.exception_403",
|
|
38
|
+
icon: createElement(StopOutlined),
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
path: exception404Path,
|
|
43
|
+
Component: Exception404,
|
|
44
|
+
handle: {
|
|
45
|
+
title: "exception:menu.exception_404",
|
|
46
|
+
icon: createElement(MinusSquareOutlined),
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
path: exception500Path,
|
|
51
|
+
Component: Exception500,
|
|
52
|
+
handle: {
|
|
53
|
+
title: "exception:menu.exception_500",
|
|
54
|
+
icon: createElement(ServerErrorIcon),
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
path: exceptionUnknownComponentPath,
|
|
59
|
+
Component: ExceptionUnknownComponent,
|
|
60
|
+
handle: {
|
|
61
|
+
title: "exception:menu.exceptionUnknownComponent",
|
|
62
|
+
icon: createElement(AppstoreOutlined),
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const mod: ModuleDefinition = {
|
|
70
|
+
name: "exception",
|
|
71
|
+
description: "异常页面模块",
|
|
72
|
+
version: "1.0.0",
|
|
73
|
+
routes,
|
|
74
|
+
i18n: {
|
|
75
|
+
"zh-CN": () => import("./locales/zh-CN.json"),
|
|
76
|
+
"en-US": () => import("./locales/en-US.json"),
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export default mod;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"404SubTitle": "Sorry, the page you visited does not exist.",
|
|
3
|
+
"403SubTitle": "Sorry, you are not authorized to access this page.",
|
|
4
|
+
"500SubTitle": "Sorry, the server is reporting an error.",
|
|
5
|
+
"unknownComponentSubTitle": "Sorry, the current route has no corresponding component, please contact the developer.",
|
|
6
|
+
"menu": {
|
|
7
|
+
"exception": "Exception Page",
|
|
8
|
+
"exception_403": "403",
|
|
9
|
+
"exception_404": "404",
|
|
10
|
+
"exception_500": "500",
|
|
11
|
+
"exceptionUnknownComponent": "Unknown Component"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"404SubTitle": "抱歉,您访问的页面不存在。",
|
|
3
|
+
"403SubTitle": "抱歉,您没有权限访问这个页面。",
|
|
4
|
+
"500SubTitle": "抱歉,服务器出错了。",
|
|
5
|
+
"unknownComponentSubTitle": "抱歉,当前路由没有对应的组件,请联系开发人员。",
|
|
6
|
+
"menu": {
|
|
7
|
+
"exception": "异常页面",
|
|
8
|
+
"exception_403": "403",
|
|
9
|
+
"exception_404": "404",
|
|
10
|
+
"exception_500": "500",
|
|
11
|
+
"exceptionUnknownComponent": "未知组件"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ArrowLeftOutlined } from "@ant-design/icons";
|
|
2
|
+
import { Button, Result } from "antd";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
4
|
+
import { useNavigate } from "react-router";
|
|
5
|
+
|
|
6
|
+
export default function Exception403() {
|
|
7
|
+
const { t } = useTranslation();
|
|
8
|
+
const navigate = useNavigate();
|
|
9
|
+
|
|
10
|
+
const Result403 = (
|
|
11
|
+
<Result
|
|
12
|
+
status="403"
|
|
13
|
+
title="403"
|
|
14
|
+
subTitle={t("exception:403SubTitle")}
|
|
15
|
+
extra={(
|
|
16
|
+
<Button
|
|
17
|
+
icon={<ArrowLeftOutlined />}
|
|
18
|
+
type="primary"
|
|
19
|
+
onClick={() => {
|
|
20
|
+
navigate(import.meta.env.VITE_BASE_HOME_PATH);
|
|
21
|
+
}}
|
|
22
|
+
>
|
|
23
|
+
{t("common.backHome")}
|
|
24
|
+
</Button>
|
|
25
|
+
)}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
return Result403;
|
|
30
|
+
}
|