create-windy 0.3.19 → 0.4.1
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/README.md +28 -13
- package/dist/cli.js +788 -280
- package/package.json +1 -1
- package/template/.agents/skills/windy-business-module/SKILL.md +34 -10
- package/template/.windy-template.json +2 -2
- package/template/AGENTS.md +18 -8
- package/template/README.md +32 -22
- package/template/apps/server/Dockerfile +1 -0
- package/template/apps/server/package.json +1 -0
- package/template/apps/server/scripts/validate-module-composition-once.ts +4 -0
- package/template/apps/server/scripts/validate-module-composition.ts +28 -0
- package/template/apps/server/src/agent/data-scope-routes.test.ts +20 -5
- package/template/apps/server/src/agent/routes.test.ts +20 -7
- package/template/apps/server/src/agent/routes.ts +14 -0
- package/template/apps/server/src/agent/runtime-bootstrap.ts +22 -6
- package/template/apps/server/src/agent/sandbox-worker.ts +2 -2
- package/template/apps/server/src/agent/unavailable-routes.ts +33 -0
- package/template/apps/server/src/app/server-composition.ts +56 -0
- package/template/apps/server/src/application-services.ts +2 -0
- package/template/apps/server/src/bulk-data/service.ts +14 -0
- package/template/apps/server/src/data-governance/export/authorization-port.ts +1 -1
- package/template/apps/server/src/data-governance/export/postgres-api-fixture.ts +18 -2
- package/template/apps/server/src/data-governance/export/postgres-recovery.integration.test.ts +10 -9
- package/template/apps/server/src/data-governance/export/routes-test-fixture.ts +6 -8
- package/template/apps/server/src/data-governance/user-plaintext-routes.test.ts +4 -2
- package/template/apps/server/src/feature/routes.authorization.test.ts +26 -1
- package/template/apps/server/src/feature/routes.ts +33 -15
- package/template/apps/server/src/foundation-activation.test.ts +60 -0
- package/template/apps/server/src/foundation.ts +64 -5
- package/template/apps/server/src/guards.test.ts +23 -0
- package/template/apps/server/src/guards.ts +12 -0
- package/template/apps/server/src/index.ts +28 -34
- package/template/apps/server/src/installed-business-modules.ts +13 -24
- package/template/apps/server/src/jobs/legacy-adapter.integration.test.ts +1 -0
- package/template/apps/server/src/jobs/legacy-adapter.test.ts +106 -0
- package/template/apps/server/src/jobs/legacy-service-runtime.ts +40 -0
- package/template/apps/server/src/license/activation.test.ts +30 -0
- package/template/apps/server/src/license/activation.ts +26 -0
- package/template/apps/server/src/license/restriction.test.ts +16 -0
- package/template/apps/server/src/license/routes-offline-code.test.ts +6 -2
- package/template/apps/server/src/license/routes.ts +20 -4
- package/template/apps/server/src/license/runtime-service.test.ts +10 -2
- package/template/apps/server/src/module-bootstrap.ts +14 -23
- package/template/apps/server/src/module-composition.test.ts +43 -2
- package/template/apps/server/src/module-composition.ts +29 -0
- package/template/apps/server/src/module-host/durable-context-guard.test.ts +172 -0
- package/template/apps/server/src/module-host/durable-context-unavailable.test.ts +50 -1
- package/template/apps/server/src/module-host/durable-context.test.ts +42 -25
- package/template/apps/server/src/module-host/durable-context.ts +42 -2
- package/template/apps/server/src/module-host/durable-handler-registration.ts +96 -5
- package/template/apps/server/src/module-host/implementation-completeness.test.ts +185 -0
- package/template/apps/server/src/module-host/implementation-completeness.ts +132 -0
- package/template/apps/server/src/module-host/initialize-contracts.ts +14 -0
- package/template/apps/server/src/module-host/initialize.test.ts +78 -2
- package/template/apps/server/src/module-host/initialize.ts +15 -4
- package/template/apps/server/src/module-host/project-attachments.test.ts +92 -0
- package/template/apps/server/src/module-host/project-attachments.ts +58 -0
- package/template/apps/server/src/module-host/project-composition-package.test.ts +186 -0
- package/template/apps/server/src/module-host/project-composition.test.ts +260 -0
- package/template/apps/server/src/module-host/project-composition.ts +311 -0
- package/template/apps/server/src/module-host/request-context.ts +3 -0
- package/template/apps/server/src/module-host/route-declaration.ts +15 -6
- package/template/apps/server/src/module-host/route-installer.test.ts +15 -2
- package/template/apps/server/src/module-host/route-installer.ts +12 -2
- package/template/apps/server/src/module-host/route-registration.test.ts +13 -4
- package/template/apps/server/src/module-host/runtime-composition-compatibility.test.ts +63 -0
- package/template/apps/server/src/module-host/runtime-composition-compatibility.ts +46 -0
- package/template/apps/server/src/module-host/runtime-composition.ts +34 -0
- package/template/apps/server/src/module-host/search-provider-registration.test.ts +64 -0
- package/template/apps/server/src/module-host/search-provider-registration.ts +35 -0
- package/template/apps/server/src/module-host/static-route-authorization.test.ts +99 -0
- package/template/apps/server/src/module-host/static-route-authorization.ts +90 -0
- package/template/apps/server/src/module-host/task-definitions.ts +1 -0
- package/template/apps/server/src/module-host/user-directory-composition.test.ts +4 -1
- package/template/apps/server/src/module-host.test.ts +2 -0
- package/template/apps/server/src/module-preflight.test.ts +289 -0
- package/template/apps/server/src/module-preflight.ts +96 -0
- package/template/apps/server/src/module-runtime-validation.ts +1 -0
- package/template/apps/server/src/platform-app-bootstrap.ts +50 -0
- package/template/apps/server/src/platform-http-runtime.ts +32 -7
- package/template/apps/server/src/route-guards.test.ts +22 -0
- package/template/apps/server/src/route-guards.ts +10 -3
- package/template/apps/server/src/runtime-audit-query.ts +50 -0
- package/template/apps/server/src/runtime-feature.test.ts +46 -0
- package/template/apps/server/src/runtime-feature.ts +5 -0
- package/template/apps/server/src/runtime.ts +10 -49
- package/template/apps/server/src/scheduler/audit-action.test.ts +49 -0
- package/template/apps/server/src/scheduler/audit.ts +1 -1
- package/template/apps/server/src/scheduler/execution-recorder.ts +10 -5
- package/template/apps/server/src/scheduler/recovery-service.test.ts +4 -3
- package/template/apps/server/src/scheduler/service-idle.test.ts +4 -3
- package/template/apps/server/src/scheduler/service.test.ts +1 -0
- package/template/apps/server/src/scheduler/service.ts +18 -21
- package/template/apps/server/src/scheduler/types.ts +5 -1
- package/template/apps/server/src/settings/drizzle-watermark-repository.integration.test.ts +4 -0
- package/template/apps/server/src/settings/drizzle-watermark-repository.ts +11 -3
- package/template/apps/server/src/settings/watermark-routes.test.ts +54 -1
- package/template/apps/server/src/settings/watermark-routes.ts +29 -1
- package/template/apps/server/src/startup-composition-validation.test.ts +35 -0
- package/template/apps/server/src/startup-composition-validation.ts +54 -0
- package/template/apps/server/src/startup-preflight.test.ts +36 -0
- package/template/apps/server/src/startup-preflight.ts +37 -0
- package/template/apps/server/src/system/audit-routes.test.ts +9 -3
- package/template/apps/server/src/system/department-routes.test.ts +5 -3
- package/template/apps/server/src/system/feature-routes.test.ts +4 -3
- package/template/apps/server/src/system/interface-catalog.test.ts +5 -3
- package/template/apps/server/src/system/interface-catalog.ts +3 -0
- package/template/apps/server/src/system/menu-routes.test.ts +5 -3
- package/template/apps/server/src/system/route-test-runtime.ts +24 -0
- package/template/apps/server/src/system/routes-validation.test.ts +7 -3
- package/template/apps/server/src/system/routes.test.ts +21 -11
- package/template/apps/server/src/system/routes.ts +11 -2
- package/template/apps/server/src/work-order/foundation-modules.test.ts +6 -4
- package/template/apps/server/src/work-order/routes.test.ts +8 -0
- package/template/apps/server/src/work-order/task.test.ts +6 -1
- package/template/apps/web/Dockerfile +5 -0
- package/template/apps/web/package.json +1 -0
- package/template/apps/web/scripts/validate-admin-ui-composition.ts +106 -0
- package/template/apps/web/src/app/web-composition.ts +24 -0
- package/template/apps/web/src/app/web-composition.webtest.ts +15 -0
- package/template/apps/web/src/composables/useWatermarkSettings.webtest.ts +14 -0
- package/template/apps/web/src/composables/watermark-settings.ts +40 -11
- package/template/apps/web/src/composables/watermark-settings.webtest.ts +19 -6
- package/template/apps/web/src/layout/AppSidebar.vue +8 -1
- package/template/apps/web/src/layout/GlobalWatermark.layering.webtest.ts +2 -0
- package/template/apps/web/src/layout/GlobalWatermark.vue +4 -8
- package/template/apps/web/src/layout/GlobalWatermark.webtest.ts +12 -5
- package/template/apps/web/src/layout/NavigationSearchDialog.vue +8 -1
- package/template/apps/web/src/layout/navigation.ts +29 -4
- package/template/apps/web/src/layout/navigation.webtest.ts +51 -0
- package/template/apps/web/src/pages/dashboard/dashboard-presenter.ts +6 -1
- package/template/apps/web/src/pages/settings/WatermarkPreview.vue +72 -0
- package/template/apps/web/src/pages/settings/WatermarkPreview.webtest.ts +55 -0
- package/template/apps/web/src/pages/settings/WatermarkSettingsSection.vue +80 -24
- package/template/apps/web/src/pages/settings/WatermarkSettingsSection.webtest.ts +115 -0
- package/template/apps/web/src/pages/work-orders/WorkOrderListPage.vue +202 -0
- package/template/apps/web/src/pages/work-orders/WorkOrderListPage.webtest.ts +51 -0
- package/template/apps/web/src/router/admin-extension-loader.ts +44 -0
- package/template/apps/web/src/router/admin-extension-loader.webtest.ts +49 -0
- package/template/apps/web/src/router/admin-ui-composition-plugin.ts +33 -0
- package/template/apps/web/src/router/admin-ui-composition-plugin.webtest.ts +20 -0
- package/template/apps/web/src/router/admin-ui-composition.ts +313 -0
- package/template/apps/web/src/router/admin-ui-composition.webtest.ts +225 -0
- package/template/apps/web/src/router/admin-ui-module-registry-plugin.ts +55 -0
- package/template/apps/web/src/router/admin-ui-module-registry-plugin.webtest.ts +46 -0
- package/template/apps/web/src/router/business-routes.ts +32 -45
- package/template/apps/web/src/router/business-routes.webtest.ts +40 -44
- package/template/apps/web/src/router/index.ts +64 -54
- package/template/apps/web/src/router/manifest-routes.ts +27 -7
- package/template/apps/web/src/router/manifest-routes.webtest.ts +10 -2
- package/template/apps/web/src/router/platform-admin-route-bindings.ts +58 -0
- package/template/apps/web/src/router/reserved-route-paths.ts +14 -0
- package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +10 -4
- package/template/apps/web/src/router/web-composition-host.ts +20 -0
- package/template/apps/web/src/services/work-order-api.ts +16 -0
- package/template/apps/web/src/services/work-order-api.webtest.ts +21 -0
- package/template/apps/web/src/virtual-admin-ui-modules.d.ts +5 -0
- package/template/apps/web/vite.config.ts +22 -1
- package/template/docs/architecture/migration-bundle.md +10 -7
- package/template/docs/architecture/module-manifest-composition.md +85 -3
- package/template/docs/architecture/search-provider-protocol.md +6 -1
- package/template/docs/architecture/server-module-interface.md +70 -7
- package/template/docs/development/business-web-routes.md +53 -50
- package/template/docs/development/module-lifecycle.md +48 -54
- package/template/docs/platform/navigation-registry.md +5 -4
- package/template/docs/platform/platform-shell-settings.md +15 -10
- package/template/docs/platform/web-system-crud.md +1 -1
- package/template/package.json +1 -1
- package/template/packages/config/package.json +1 -1
- package/template/packages/database/drizzle/0044_sweet_tarantula.sql +6 -0
- package/template/packages/database/drizzle/meta/0044_snapshot.json +8349 -0
- package/template/packages/database/drizzle/meta/_journal.json +7 -0
- package/template/packages/database/package.json +1 -1
- package/template/packages/database/src/schema/platform-settings.ts +3 -0
- package/template/packages/example-work-order/package.json +6 -0
- package/template/packages/example-work-order/src/admin-page.ts +78 -0
- package/template/packages/example-work-order/src/admin.ts +35 -0
- package/template/packages/example-work-order/src/manifest.test.ts +62 -5
- package/template/packages/example-work-order/src/manifest.ts +54 -7
- package/template/packages/example-work-order/src/migration-bundle.ts +8 -0
- package/template/packages/example-work-order/src/server-module.ts +2 -2
- package/template/packages/jobs/package.json +1 -1
- package/template/packages/modules/index.ts +4 -0
- package/template/packages/modules/package.json +3 -2
- package/template/packages/modules/src/admin-ui-entry.ts +8 -0
- package/template/packages/modules/src/admin-ui-manifest.test.ts +52 -0
- package/template/packages/modules/src/admin-ui-manifest.ts +53 -0
- package/template/packages/modules/src/capability-audit-validation.test.ts +127 -0
- package/template/packages/modules/src/capability-audit-validation.ts +38 -0
- package/template/packages/modules/src/capability-guard-validation.ts +148 -0
- package/template/packages/modules/src/capability-key-validation.test.ts +150 -0
- package/template/packages/modules/src/composition-fixtures.test-support.ts +7 -5
- package/template/packages/modules/src/composition.test.ts +74 -9
- package/template/packages/modules/src/composition.ts +8 -0
- package/template/packages/modules/src/manifest.ts +1 -0
- package/template/packages/modules/src/migration-bundle-validation.test.ts +23 -0
- package/template/packages/modules/src/module-host-range.test.ts +57 -0
- package/template/packages/modules/src/module-host-range.ts +125 -0
- package/template/packages/modules/src/project-module-assembly.test.ts +133 -0
- package/template/packages/modules/src/project-module-assembly.ts +126 -0
- package/template/packages/modules/src/system-admin-routes.ts +6 -5
- package/template/packages/modules/src/system-admin-ui-catalog.ts +72 -0
- package/template/packages/modules/src/system-agent-tools.ts +2 -2
- package/template/packages/modules/src/system-api-binding.ts +43 -9
- package/template/packages/modules/src/system-api-control-plane.ts +202 -0
- package/template/packages/modules/src/system-api-data-governance.ts +4 -2
- package/template/packages/modules/src/system-api-operational.ts +57 -8
- package/template/packages/modules/src/system-api-permissions.ts +115 -21
- package/template/packages/modules/src/system-api-watermark.ts +3 -0
- package/template/packages/modules/src/system-audit-actions.ts +7 -0
- package/template/packages/modules/src/system-data-governance.ts +5 -2
- package/template/packages/modules/src/system-features.ts +31 -0
- package/template/packages/modules/src/system-governed-export-bindings.ts +8 -4
- package/template/packages/modules/src/system-module-catalog.test.ts +39 -4
- package/template/packages/modules/src/system-module-catalog.ts +68 -7
- package/template/packages/modules/src/system-module-tasks.ts +8 -1
- package/template/packages/modules/src/system-modules-access.test.ts +4 -0
- package/template/packages/modules/src/system-modules.test.ts +10 -2
- package/template/packages/modules/src/system-modules.ts +6 -5
- package/template/packages/modules/src/system-permissions.ts +4 -0
- package/template/packages/modules/user/module.ts +19 -2
- package/template/packages/server-sdk/index.ts +8 -0
- package/template/packages/server-sdk/package.json +2 -1
- package/template/packages/server-sdk/src/durable-handler.ts +16 -1
- package/template/packages/server-sdk/src/module-adapter.test.ts +44 -0
- package/template/packages/server-sdk/src/module-adapter.ts +59 -0
- package/template/packages/server-sdk/src/route-definition.ts +3 -2
- package/template/packages/shared/package.json +1 -1
- package/template/packages/shared/src/audit.test.ts +7 -0
- package/template/packages/shared/src/audit.ts +16 -0
- package/template/packages/shared/src/migration-bundle-validation.ts +9 -11
- package/template/packages/shared/src/platform-settings.ts +17 -0
- package/template/packages/shared/src/rbac.ts +2 -0
- package/template/apps/web/src/app/business-routes.ts +0 -22
|
@@ -1,23 +1,39 @@
|
|
|
1
1
|
import type { RouteRecordRaw } from "vue-router";
|
|
2
|
-
import type {
|
|
2
|
+
import type { AdminUiModule } from "./admin-ui-composition";
|
|
3
|
+
import { isolateAdminPageLoader } from "./admin-extension-loader";
|
|
3
4
|
|
|
4
5
|
export function materializeBusinessAdminRoutes(
|
|
5
|
-
|
|
6
|
+
modules: readonly AdminUiModule[],
|
|
6
7
|
): RouteRecordRaw[] {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
8
|
+
return modules.flatMap((module) =>
|
|
9
|
+
module.manifest.adminRoutes.map((definition) => {
|
|
10
|
+
const binding = module.pages.find(
|
|
11
|
+
({ routeKey }) => routeKey === definition.key,
|
|
12
|
+
);
|
|
13
|
+
if (!binding) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
`${module.manifest.name} 缺少 Admin Route 页面绑定:${definition.key}`,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
const guarded = definition.directAccess !== "authenticated";
|
|
19
|
+
return {
|
|
20
|
+
path: definition.path,
|
|
21
|
+
name: definition.key,
|
|
22
|
+
component: isolateAdminPageLoader(
|
|
23
|
+
module.manifest.name,
|
|
24
|
+
definition.key,
|
|
25
|
+
binding.load,
|
|
26
|
+
),
|
|
27
|
+
meta: {
|
|
28
|
+
title: definition.title,
|
|
29
|
+
layout: "admin",
|
|
30
|
+
businessRouteKey: definition.key,
|
|
31
|
+
featureKey: guarded ? definition.featureKey : undefined,
|
|
32
|
+
permissionKey: guarded ? definition.permissionKey : undefined,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
21
37
|
}
|
|
22
38
|
|
|
23
39
|
export function assertNoRouteCollisions(
|
|
@@ -37,32 +53,3 @@ export function assertNoRouteCollisions(
|
|
|
37
53
|
names.add(route.name);
|
|
38
54
|
}
|
|
39
55
|
}
|
|
40
|
-
|
|
41
|
-
function assertBusinessAdminRouteRegistrations(
|
|
42
|
-
registrations: readonly BusinessAdminRouteRegistration[],
|
|
43
|
-
): void {
|
|
44
|
-
const keys = new Set<string>();
|
|
45
|
-
for (const registration of registrations) {
|
|
46
|
-
for (const [field, value] of [
|
|
47
|
-
["key", registration.key],
|
|
48
|
-
["path", registration.path],
|
|
49
|
-
["title", registration.title],
|
|
50
|
-
["featureKey", registration.featureKey],
|
|
51
|
-
["permissionKey", registration.permissionKey],
|
|
52
|
-
] as const) {
|
|
53
|
-
if (!value.trim()) {
|
|
54
|
-
throw new Error(`业务 Web Route 的 ${field} 不能为空`);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
if (!registration.path.startsWith("/")) {
|
|
58
|
-
throw new Error(`业务 Web Route 必须使用绝对路径:${registration.path}`);
|
|
59
|
-
}
|
|
60
|
-
if (registration.path.includes(":pathMatch")) {
|
|
61
|
-
throw new Error(`业务 Web Route 不能注册兜底路径:${registration.path}`);
|
|
62
|
-
}
|
|
63
|
-
if (keys.has(registration.key)) {
|
|
64
|
-
throw new Error(`业务 Web Route Key 重复:${registration.key}`);
|
|
65
|
-
}
|
|
66
|
-
keys.add(registration.key);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
@@ -3,24 +3,17 @@ import {
|
|
|
3
3
|
assertNoRouteCollisions,
|
|
4
4
|
materializeBusinessAdminRoutes,
|
|
5
5
|
} from "./business-routes";
|
|
6
|
+
import type { AdminUiModule } from "./admin-ui-composition";
|
|
6
7
|
|
|
7
8
|
describe("project-owned 业务路由接缝", () => {
|
|
8
|
-
test("
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
key: "case.record",
|
|
12
|
-
path: "/cases",
|
|
13
|
-
name: "case-records",
|
|
14
|
-
title: "案件",
|
|
15
|
-
component: {},
|
|
16
|
-
featureKey: "case.record",
|
|
17
|
-
permissionKey: "case.record.read",
|
|
18
|
-
},
|
|
19
|
-
]);
|
|
9
|
+
test("只用 routeKey 和隔离后的 lazy loader 绑定 Manifest 路由", async () => {
|
|
10
|
+
const load = async () => ({ default: {} });
|
|
11
|
+
const [route] = materializeBusinessAdminRoutes([adminUiModule(load)]);
|
|
20
12
|
|
|
21
13
|
expect(route).toMatchObject({
|
|
22
14
|
path: "/cases",
|
|
23
|
-
name: "case
|
|
15
|
+
name: "case.record",
|
|
16
|
+
component: expect.any(Function),
|
|
24
17
|
meta: {
|
|
25
18
|
title: "案件",
|
|
26
19
|
layout: "admin",
|
|
@@ -29,32 +22,21 @@ describe("project-owned 业务路由接缝", () => {
|
|
|
29
22
|
permissionKey: "case.record.read",
|
|
30
23
|
},
|
|
31
24
|
});
|
|
25
|
+
expect(
|
|
26
|
+
await (route!.component as AdminUiModule["pages"][number]["load"])(),
|
|
27
|
+
).toEqual({ default: {} });
|
|
32
28
|
});
|
|
33
29
|
|
|
34
|
-
test("
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
},
|
|
45
|
-
]),
|
|
46
|
-
).toThrow("featureKey 不能为空");
|
|
47
|
-
expect(() =>
|
|
48
|
-
materializeBusinessAdminRoutes([
|
|
49
|
-
registration("/cases"),
|
|
50
|
-
registration("/other"),
|
|
51
|
-
]),
|
|
52
|
-
).toThrow("业务 Web Route Key 重复");
|
|
53
|
-
expect(() =>
|
|
54
|
-
materializeBusinessAdminRoutes([
|
|
55
|
-
registration("/:pathMatch(.*)*"),
|
|
56
|
-
]),
|
|
57
|
-
).toThrow("不能注册兜底路径");
|
|
30
|
+
test("authenticated 路由不附加 Feature 与 Permission Guard", () => {
|
|
31
|
+
const module = adminUiModule(
|
|
32
|
+
async () => ({ default: {} }),
|
|
33
|
+
"authenticated",
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const [route] = materializeBusinessAdminRoutes([module]);
|
|
37
|
+
|
|
38
|
+
expect(route?.meta?.featureKey).toBeUndefined();
|
|
39
|
+
expect(route?.meta?.permissionKey).toBeUndefined();
|
|
58
40
|
});
|
|
59
41
|
|
|
60
42
|
test("组合时拒绝与平台路由重复的路径或名称", () => {
|
|
@@ -73,13 +55,27 @@ describe("project-owned 业务路由接缝", () => {
|
|
|
73
55
|
});
|
|
74
56
|
});
|
|
75
57
|
|
|
76
|
-
function
|
|
58
|
+
function adminUiModule(
|
|
59
|
+
load: AdminUiModule["pages"][number]["load"],
|
|
60
|
+
directAccess: "guarded" | "authenticated" = "guarded",
|
|
61
|
+
): AdminUiModule {
|
|
77
62
|
return {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
63
|
+
hostRange: "^0.1.0",
|
|
64
|
+
manifest: {
|
|
65
|
+
name: "case",
|
|
66
|
+
version: "1.2.3",
|
|
67
|
+
adminRoutes: [
|
|
68
|
+
{
|
|
69
|
+
key: "case.record",
|
|
70
|
+
path: "/cases",
|
|
71
|
+
title: "案件",
|
|
72
|
+
surface: "sidebar",
|
|
73
|
+
directAccess,
|
|
74
|
+
featureKey: "case.record",
|
|
75
|
+
permissionKey: "case.record.read",
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
pages: [{ routeKey: "case.record", load }],
|
|
84
80
|
};
|
|
85
81
|
}
|
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
type RouterHistory,
|
|
5
5
|
type RouteRecordRaw,
|
|
6
6
|
} from "vue-router";
|
|
7
|
+
import type { Component } from "vue";
|
|
7
8
|
import DashboardPage from "@/pages/dashboard/DashboardPage.vue";
|
|
8
9
|
import HomePage from "@/app/home/HomePage.vue";
|
|
9
|
-
import { installedBusinessAdminRoutes } from "@/app/business-routes";
|
|
10
10
|
import { loginPageComponent } from "@/app/auth/login-page";
|
|
11
11
|
import { errorPageComponents } from "@/app/error-pages";
|
|
12
12
|
import AccessDeniedPage from "@/pages/AccessDeniedPage.vue";
|
|
@@ -29,7 +29,6 @@ import PersonalSettingsPage from "@/pages/profile/PersonalSettingsPage.vue";
|
|
|
29
29
|
// @windy-module system.configuration begin
|
|
30
30
|
import ModuleConfigurationPage from "@/pages/configuration/ModuleConfigurationPage.vue";
|
|
31
31
|
// @windy-module system.configuration end
|
|
32
|
-
import type { SystemResourceKey } from "@/pages/system/resource-config";
|
|
33
32
|
import { useAccessSnapshot } from "@/composables/useAccessSnapshot";
|
|
34
33
|
import {
|
|
35
34
|
useAuthSession,
|
|
@@ -50,6 +49,12 @@ import {
|
|
|
50
49
|
manifestAdminRoute,
|
|
51
50
|
validateWebModuleComposition,
|
|
52
51
|
} from "./manifest-routes";
|
|
52
|
+
import {
|
|
53
|
+
platformAdminRouteBindings,
|
|
54
|
+
type PlatformAdminPageKey,
|
|
55
|
+
} from "./platform-admin-route-bindings";
|
|
56
|
+
import { reservedWebRoutePaths } from "./reserved-route-paths";
|
|
57
|
+
import { webAdminUiComposition } from "./web-composition-host";
|
|
53
58
|
|
|
54
59
|
export interface RouterAccessProvider {
|
|
55
60
|
refresh(): Promise<WebAccessSnapshot>;
|
|
@@ -66,46 +71,70 @@ export interface RouterAuthProvider {
|
|
|
66
71
|
clear(): void;
|
|
67
72
|
}
|
|
68
73
|
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
const platformAdminPageComponents: Partial<
|
|
75
|
+
Record<PlatformAdminPageKey, Component>
|
|
76
|
+
> = {
|
|
77
|
+
resource: SystemResourceRoutePage,
|
|
78
|
+
profile: PersonalSettingsPage,
|
|
79
|
+
// @windy-module system.settings begin
|
|
80
|
+
settings: GlobalSettingsPage,
|
|
81
|
+
// @windy-module system.settings end
|
|
82
|
+
// @windy-module system.license begin
|
|
83
|
+
license: LicensePage,
|
|
84
|
+
// @windy-module system.license end
|
|
85
|
+
// @windy-module system.notification begin
|
|
86
|
+
notification: SystemNotificationsPage,
|
|
87
|
+
// @windy-module system.notification end
|
|
88
|
+
// @windy-module system.operations begin
|
|
89
|
+
operations: SystemOperationsPage,
|
|
90
|
+
// @windy-module system.operations end
|
|
91
|
+
// @windy-module system.configuration begin
|
|
92
|
+
configuration: ModuleConfigurationPage,
|
|
93
|
+
// @windy-module system.configuration end
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const platformAdminRoutes = platformAdminRouteBindings.map((binding) => {
|
|
97
|
+
const component = platformAdminPageComponents[binding.page];
|
|
98
|
+
if (!component) {
|
|
99
|
+
throw new Error(`平台 Admin 页面未绑定:${binding.routeKey}`);
|
|
100
|
+
}
|
|
101
|
+
return manifestAdminRoute(binding.routeKey, {
|
|
102
|
+
component,
|
|
103
|
+
name: binding.name,
|
|
104
|
+
...(binding.resourceKey
|
|
105
|
+
? { props: { resourceKey: binding.resourceKey } }
|
|
106
|
+
: {}),
|
|
76
107
|
});
|
|
108
|
+
});
|
|
77
109
|
|
|
78
|
-
|
|
110
|
+
const declaredRoutes: RouteRecordRaw[] = [
|
|
79
111
|
{
|
|
80
|
-
path:
|
|
112
|
+
path: reservedWebRoutePaths.login,
|
|
81
113
|
name: "login",
|
|
82
114
|
component: loginPageComponent,
|
|
83
115
|
meta: { title: "登录", layout: "auth", public: true, guestOnly: true },
|
|
84
116
|
},
|
|
85
117
|
{
|
|
86
|
-
path:
|
|
118
|
+
path: reservedWebRoutePaths.changePassword,
|
|
87
119
|
name: "change-password",
|
|
88
120
|
component: ChangePasswordPage,
|
|
89
121
|
meta: { title: "修改密码", layout: "auth" },
|
|
90
122
|
},
|
|
91
123
|
{
|
|
92
|
-
path:
|
|
124
|
+
path: reservedWebRoutePaths.home,
|
|
93
125
|
name: "home",
|
|
94
126
|
component: HomePage,
|
|
95
127
|
meta: { title: "首页", layout: "app" },
|
|
96
128
|
},
|
|
97
129
|
{
|
|
98
|
-
path:
|
|
130
|
+
path: reservedWebRoutePaths.adminHome,
|
|
99
131
|
name: "admin-home",
|
|
100
132
|
component: DashboardPage,
|
|
101
133
|
meta: { title: "工作台", layout: "admin" },
|
|
102
134
|
},
|
|
103
|
-
|
|
104
|
-
manifestAdminRoute("system.settings", { component: GlobalSettingsPage }),
|
|
105
|
-
// @windy-module system.settings end
|
|
106
|
-
manifestAdminRoute("system.profile", { component: PersonalSettingsPage }),
|
|
135
|
+
...platformAdminRoutes,
|
|
107
136
|
{
|
|
108
|
-
path:
|
|
137
|
+
path: reservedWebRoutePaths.accessDenied,
|
|
109
138
|
name: "access-denied",
|
|
110
139
|
component: AccessDeniedPage,
|
|
111
140
|
props: (route) => ({
|
|
@@ -115,7 +144,7 @@ export const routes: RouteRecordRaw[] = [
|
|
|
115
144
|
meta: { title: "无法访问", layout: "auth" },
|
|
116
145
|
},
|
|
117
146
|
{
|
|
118
|
-
path:
|
|
147
|
+
path: reservedWebRoutePaths.serviceUnavailable,
|
|
119
148
|
name: "service-unavailable",
|
|
120
149
|
component: ServiceUnavailablePage,
|
|
121
150
|
props: (route) => ({
|
|
@@ -125,7 +154,7 @@ export const routes: RouteRecordRaw[] = [
|
|
|
125
154
|
meta: { title: "服务暂不可用", layout: "auth", public: true },
|
|
126
155
|
},
|
|
127
156
|
{
|
|
128
|
-
path:
|
|
157
|
+
path: reservedWebRoutePaths.internalError,
|
|
129
158
|
name: "internal-error",
|
|
130
159
|
component: errorPageComponents.internal,
|
|
131
160
|
props: (route) => ({
|
|
@@ -141,7 +170,7 @@ export const routes: RouteRecordRaw[] = [
|
|
|
141
170
|
},
|
|
142
171
|
},
|
|
143
172
|
{
|
|
144
|
-
path:
|
|
173
|
+
path: reservedWebRoutePaths.maintenance,
|
|
145
174
|
name: "maintenance",
|
|
146
175
|
component: errorPageComponents.maintenance,
|
|
147
176
|
props: (route) => ({
|
|
@@ -157,7 +186,7 @@ export const routes: RouteRecordRaw[] = [
|
|
|
157
186
|
},
|
|
158
187
|
},
|
|
159
188
|
{
|
|
160
|
-
path:
|
|
189
|
+
path: reservedWebRoutePaths.networkError,
|
|
161
190
|
name: "network-error",
|
|
162
191
|
component: errorPageComponents.network,
|
|
163
192
|
props: (route) => ({
|
|
@@ -172,37 +201,9 @@ export const routes: RouteRecordRaw[] = [
|
|
|
172
201
|
errorPage: true,
|
|
173
202
|
},
|
|
174
203
|
},
|
|
175
|
-
|
|
176
|
-
systemRoute("system.role", "roles"),
|
|
177
|
-
systemRoute("system.department", "departments"),
|
|
178
|
-
systemRoute("system.menu", "menus"),
|
|
179
|
-
systemRoute("system.dictionary", "dictionaries"),
|
|
180
|
-
systemRoute("system.feature", "features"),
|
|
181
|
-
// @windy-module system.license begin
|
|
182
|
-
manifestAdminRoute("system.license", {
|
|
183
|
-
name: "runtime-license",
|
|
184
|
-
component: LicensePage,
|
|
185
|
-
}),
|
|
186
|
-
// @windy-module system.license end
|
|
187
|
-
// @windy-module system.audit begin
|
|
188
|
-
systemRoute("system.audit", "auditLogs"),
|
|
189
|
-
// @windy-module system.audit end
|
|
190
|
-
// @windy-module system.notification begin
|
|
191
|
-
manifestAdminRoute("system.notification", {
|
|
192
|
-
component: SystemNotificationsPage,
|
|
193
|
-
}),
|
|
194
|
-
// @windy-module system.notification end
|
|
195
|
-
// @windy-module system.operations begin
|
|
196
|
-
manifestAdminRoute("system.ops", { component: SystemOperationsPage }),
|
|
197
|
-
// @windy-module system.operations end
|
|
198
|
-
// @windy-module system.configuration begin
|
|
199
|
-
manifestAdminRoute("system.config", {
|
|
200
|
-
component: ModuleConfigurationPage,
|
|
201
|
-
}),
|
|
202
|
-
// @windy-module system.configuration end
|
|
203
|
-
...materializeBusinessAdminRoutes(installedBusinessAdminRoutes),
|
|
204
|
+
...materializeBusinessAdminRoutes(webAdminUiComposition.modules),
|
|
204
205
|
{
|
|
205
|
-
path:
|
|
206
|
+
path: reservedWebRoutePaths.notFound,
|
|
206
207
|
name: "not-found",
|
|
207
208
|
component: errorPageComponents.notFound,
|
|
208
209
|
props: (route) => ({
|
|
@@ -218,8 +219,17 @@ export const routes: RouteRecordRaw[] = [
|
|
|
218
219
|
},
|
|
219
220
|
];
|
|
220
221
|
|
|
222
|
+
validateWebModuleComposition(declaredRoutes);
|
|
223
|
+
|
|
224
|
+
export const routes = declaredRoutes.filter((route) => {
|
|
225
|
+
const routeKey = route.meta?.manifestRouteKey;
|
|
226
|
+
return (
|
|
227
|
+
typeof routeKey !== "string" ||
|
|
228
|
+
webAdminUiComposition.activePlatformRouteKeys.has(routeKey)
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
|
|
221
232
|
assertNoRouteCollisions(routes);
|
|
222
|
-
validateWebModuleComposition(routes);
|
|
223
233
|
|
|
224
234
|
export function createAppRouter(
|
|
225
235
|
history: RouterHistory = createWebHistory(),
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
validateModuleComposition,
|
|
2
|
+
systemAdminUiManifests,
|
|
4
3
|
type ModuleAdminRouteDefinition,
|
|
5
|
-
} from "@southwind-ai/modules";
|
|
4
|
+
} from "@southwind-ai/modules/admin-ui";
|
|
6
5
|
import type { Component } from "vue";
|
|
7
6
|
import type { RouteRecordRaw } from "vue-router";
|
|
8
7
|
|
|
9
|
-
export const webModuleManifests =
|
|
8
|
+
export const webModuleManifests = systemAdminUiManifests();
|
|
10
9
|
|
|
11
10
|
const adminRouteByKey = new Map(
|
|
12
11
|
webModuleManifests.flatMap((module) =>
|
|
@@ -44,12 +43,33 @@ export function manifestAdminRoute(
|
|
|
44
43
|
export function validateWebModuleComposition(
|
|
45
44
|
routes: readonly RouteRecordRaw[],
|
|
46
45
|
): void {
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
validatePlatformAdminRouteKeys(
|
|
47
|
+
routes.flatMap((route) => {
|
|
49
48
|
const key = route.meta?.manifestRouteKey;
|
|
50
49
|
return typeof key === "string" ? [key] : [];
|
|
51
50
|
}),
|
|
52
|
-
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function validatePlatformAdminRouteKeys(
|
|
55
|
+
implementedKeys: readonly string[],
|
|
56
|
+
): void {
|
|
57
|
+
const declared = new Set(adminRouteByKey.keys());
|
|
58
|
+
const implemented = new Set<string>();
|
|
59
|
+
for (const key of implementedKeys) {
|
|
60
|
+
if (!declared.has(key)) {
|
|
61
|
+
throw new Error(`Admin Route 实现未在窄 Manifest 声明:${key}`);
|
|
62
|
+
}
|
|
63
|
+
if (implemented.has(key)) {
|
|
64
|
+
throw new Error(`Admin Route Handler Key 重复:${key}`);
|
|
65
|
+
}
|
|
66
|
+
implemented.add(key);
|
|
67
|
+
}
|
|
68
|
+
for (const key of declared) {
|
|
69
|
+
if (!implemented.has(key)) {
|
|
70
|
+
throw new Error(`Admin Route ${key} 缺少 Handler`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
53
73
|
}
|
|
54
74
|
|
|
55
75
|
function findAdminRoute(key: string): ModuleAdminRouteDefinition {
|
|
@@ -47,7 +47,15 @@ describe("Manifest 菜单与 Router 契约", () => {
|
|
|
47
47
|
expect(definitions.some(({ path }) => path === "/admin/profile")).toBe(
|
|
48
48
|
true,
|
|
49
49
|
);
|
|
50
|
-
expect(definitions.some(({ path }) => path === "/work-orders")).toBe(
|
|
51
|
-
|
|
50
|
+
expect(definitions.some(({ path }) => path === "/admin/work-orders")).toBe(
|
|
51
|
+
false,
|
|
52
|
+
);
|
|
53
|
+
// @windy-module work-order begin
|
|
54
|
+
expect(
|
|
55
|
+
routes.find(
|
|
56
|
+
({ meta }) => meta?.businessRouteKey === "work-order.ticket.list",
|
|
57
|
+
)?.path,
|
|
58
|
+
).toBe("/admin/work-orders");
|
|
59
|
+
// @windy-module work-order end
|
|
52
60
|
});
|
|
53
61
|
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { SystemResourceKey } from "@/pages/system/resource-config";
|
|
2
|
+
|
|
3
|
+
export type PlatformAdminPageKey =
|
|
4
|
+
| "resource"
|
|
5
|
+
| "settings"
|
|
6
|
+
| "profile"
|
|
7
|
+
| "license"
|
|
8
|
+
| "notification"
|
|
9
|
+
| "operations"
|
|
10
|
+
| "configuration";
|
|
11
|
+
|
|
12
|
+
export interface PlatformAdminRouteBinding {
|
|
13
|
+
readonly routeKey: string;
|
|
14
|
+
readonly page: PlatformAdminPageKey;
|
|
15
|
+
readonly resourceKey?: SystemResourceKey;
|
|
16
|
+
readonly name?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const platformAdminRouteBindings: readonly PlatformAdminRouteBinding[] =
|
|
20
|
+
[
|
|
21
|
+
{ routeKey: "system.profile", page: "profile" },
|
|
22
|
+
// @windy-module system.settings begin
|
|
23
|
+
{ routeKey: "system.settings", page: "settings" },
|
|
24
|
+
// @windy-module system.settings end
|
|
25
|
+
{ routeKey: "system.user", page: "resource", resourceKey: "users" },
|
|
26
|
+
{ routeKey: "system.role", page: "resource", resourceKey: "roles" },
|
|
27
|
+
{
|
|
28
|
+
routeKey: "system.department",
|
|
29
|
+
page: "resource",
|
|
30
|
+
resourceKey: "departments",
|
|
31
|
+
},
|
|
32
|
+
{ routeKey: "system.menu", page: "resource", resourceKey: "menus" },
|
|
33
|
+
{
|
|
34
|
+
routeKey: "system.dictionary",
|
|
35
|
+
page: "resource",
|
|
36
|
+
resourceKey: "dictionaries",
|
|
37
|
+
},
|
|
38
|
+
{ routeKey: "system.feature", page: "resource", resourceKey: "features" },
|
|
39
|
+
// @windy-module system.license begin
|
|
40
|
+
{
|
|
41
|
+
routeKey: "system.license",
|
|
42
|
+
page: "license",
|
|
43
|
+
name: "runtime-license",
|
|
44
|
+
},
|
|
45
|
+
// @windy-module system.license end
|
|
46
|
+
// @windy-module system.audit begin
|
|
47
|
+
{ routeKey: "system.audit", page: "resource", resourceKey: "auditLogs" },
|
|
48
|
+
// @windy-module system.audit end
|
|
49
|
+
// @windy-module system.notification begin
|
|
50
|
+
{ routeKey: "system.notification", page: "notification" },
|
|
51
|
+
// @windy-module system.notification end
|
|
52
|
+
// @windy-module system.operations begin
|
|
53
|
+
{ routeKey: "system.ops", page: "operations" },
|
|
54
|
+
// @windy-module system.operations end
|
|
55
|
+
// @windy-module system.configuration begin
|
|
56
|
+
{ routeKey: "system.config", page: "configuration" },
|
|
57
|
+
// @windy-module system.configuration end
|
|
58
|
+
];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const reservedWebRoutePaths = {
|
|
2
|
+
login: "/login",
|
|
3
|
+
changePassword: "/change-password",
|
|
4
|
+
home: "/",
|
|
5
|
+
adminHome: "/admin",
|
|
6
|
+
accessDenied: "/access-denied",
|
|
7
|
+
serviceUnavailable: "/service-unavailable",
|
|
8
|
+
internalError: "/error/internal",
|
|
9
|
+
maintenance: "/error/maintenance",
|
|
10
|
+
networkError: "/error/network",
|
|
11
|
+
notFound: "/:pathMatch(.*)*",
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
export const reservedWebRoutePathList = Object.values(reservedWebRoutePaths);
|
|
@@ -2,6 +2,7 @@ import { describe, expect, test } from "vitest";
|
|
|
2
2
|
import type { RouteRecordRaw } from "vue-router";
|
|
3
3
|
import routerSource from "./index.ts?raw";
|
|
4
4
|
import fixtureSource from "./router-test-fixtures.ts?raw";
|
|
5
|
+
import compositionSource from "../app/web-composition.ts?raw";
|
|
5
6
|
import { collectRouteAccess } from "./router-test-fixtures";
|
|
6
7
|
|
|
7
8
|
describe("Router shared-scaffold 中性契约", () => {
|
|
@@ -25,16 +26,21 @@ describe("Router shared-scaffold 中性契约", () => {
|
|
|
25
26
|
expect(routerSource).toContain("component: errorPageComponents.network");
|
|
26
27
|
});
|
|
27
28
|
|
|
28
|
-
test("平台路由组合 project-owned
|
|
29
|
+
test("平台路由组合 project-owned Admin UI 声明且保持在兜底页之前", () => {
|
|
29
30
|
expect(routerSource).toContain(
|
|
30
|
-
'import {
|
|
31
|
+
'import { webAdminUiComposition } from "./web-composition-host";',
|
|
31
32
|
);
|
|
32
33
|
const businessIndex = routerSource.indexOf(
|
|
33
|
-
"...materializeBusinessAdminRoutes(
|
|
34
|
+
"...materializeBusinessAdminRoutes(webAdminUiComposition.modules)",
|
|
35
|
+
);
|
|
36
|
+
const fallbackIndex = routerSource.indexOf(
|
|
37
|
+
"path: reservedWebRoutePaths.notFound",
|
|
34
38
|
);
|
|
35
|
-
const fallbackIndex = routerSource.indexOf('path: "/:pathMatch(.*)*"');
|
|
36
39
|
expect(businessIndex).toBeGreaterThan(0);
|
|
37
40
|
expect(businessIndex).toBeLessThan(fallbackIndex);
|
|
41
|
+
expect(compositionSource).toContain("defineWebComposition");
|
|
42
|
+
expect(compositionSource).not.toContain("featureKey");
|
|
43
|
+
expect(compositionSource).not.toContain("permissionKey");
|
|
38
44
|
});
|
|
39
45
|
|
|
40
46
|
test("测试快照只从项目注入路由递归派生访问键", () => {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
systemAdminUiManifests,
|
|
3
|
+
validateProjectModuleAssembly,
|
|
4
|
+
} from "@southwind-ai/modules/admin-ui";
|
|
5
|
+
import { installedAdminUiModules } from "virtual:windy-admin-ui-modules";
|
|
6
|
+
import moduleAssembly from "../../../../windy.modules.json";
|
|
7
|
+
import { webComposition } from "../app/web-composition";
|
|
8
|
+
import { resolveAdminUiComposition } from "./admin-ui-composition";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Host 对项目组合声明的唯一解析入口。
|
|
12
|
+
*
|
|
13
|
+
* Router、Navigation 和 Vite 构建门禁必须消费同一结果,避免运行时与构建时规则漂移。
|
|
14
|
+
*/
|
|
15
|
+
export const webAdminUiComposition = resolveAdminUiComposition({
|
|
16
|
+
declaration: webComposition,
|
|
17
|
+
assembly: validateProjectModuleAssembly(moduleAssembly),
|
|
18
|
+
platformModules: systemAdminUiManifests(),
|
|
19
|
+
installedModules: installedAdminUiModules,
|
|
20
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
WorkOrderListQuery,
|
|
3
|
+
WorkOrderPage,
|
|
4
|
+
} from "@southwind-ai/example-work-order/contract";
|
|
5
|
+
import { apiRequest } from "./http";
|
|
6
|
+
|
|
7
|
+
export function listWorkOrders(query: WorkOrderListQuery = {}) {
|
|
8
|
+
const params = new URLSearchParams();
|
|
9
|
+
if (query.page !== undefined) params.set("page", String(query.page));
|
|
10
|
+
if (query.pageSize !== undefined) {
|
|
11
|
+
params.set("pageSize", String(query.pageSize));
|
|
12
|
+
}
|
|
13
|
+
if (query.q?.trim()) params.set("q", query.q.trim());
|
|
14
|
+
const search = params.toString();
|
|
15
|
+
return apiRequest<WorkOrderPage>(`/work-orders${search ? `?${search}` : ""}`);
|
|
16
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const mocks = vi.hoisted(() => ({
|
|
4
|
+
request: vi.fn(),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
vi.mock("./http", () => ({ apiRequest: mocks.request }));
|
|
8
|
+
|
|
9
|
+
import { listWorkOrders } from "./work-order-api";
|
|
10
|
+
|
|
11
|
+
describe("示例工单 API", () => {
|
|
12
|
+
beforeEach(() => mocks.request.mockReset());
|
|
13
|
+
|
|
14
|
+
test("列表查询保留分页与搜索条件", () => {
|
|
15
|
+
listWorkOrders({ page: 2, pageSize: 20, q: "登录" });
|
|
16
|
+
|
|
17
|
+
expect(mocks.request).toHaveBeenCalledWith(
|
|
18
|
+
"/work-orders?page=2&pageSize=20&q=%E7%99%BB%E5%BD%95",
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
});
|