create-windy 0.3.18 → 0.4.0
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/agent-server/src/southwind_agent_server/adk_debug.py +38 -5
- package/template/apps/agent-server/tests/test_adk_debug_result_polling.py +139 -0
- 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/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/layout/AppSidebar.vue +8 -1
- 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/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/agent-runtime.md +2 -0
- package/template/docs/platform/navigation-registry.md +5 -4
- package/template/package.json +1 -1
- package/template/packages/config/package.json +1 -1
- package/template/packages/database/package.json +1 -1
- 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/rbac.ts +2 -0
- package/template/apps/web/src/app/business-routes.ts +0 -22
|
@@ -33,9 +33,14 @@ describe("示例工单后台任务", () => {
|
|
|
33
33
|
workOrderDataScopeBinding,
|
|
34
34
|
),
|
|
35
35
|
});
|
|
36
|
+
const definitions = createModuleTaskDefinitions(
|
|
37
|
+
hosts,
|
|
38
|
+
createFoundationSnapshot().features,
|
|
39
|
+
);
|
|
40
|
+
expect(definitions[0]?.auditAction).toBe("task.execute");
|
|
36
41
|
const scheduler = new SchedulerService(
|
|
37
42
|
new InMemorySchedulerRepository(),
|
|
38
|
-
|
|
43
|
+
definitions,
|
|
39
44
|
runtime,
|
|
40
45
|
);
|
|
41
46
|
await scheduler.initialize();
|
|
@@ -4,6 +4,7 @@ FROM oven/bun:1.3.14-alpine AS deps
|
|
|
4
4
|
WORKDIR /app
|
|
5
5
|
|
|
6
6
|
COPY package.json bun.lock ./
|
|
7
|
+
COPY windy.modules.json windy.modules.json
|
|
7
8
|
# windy-copy:deps begin
|
|
8
9
|
COPY apps/license/package.json apps/license/package.json
|
|
9
10
|
COPY apps/server/package.json apps/server/package.json
|
|
@@ -27,9 +28,13 @@ RUN --mount=type=cache,id=windy-bun-1.3.14,target=/root/.bun/install/cache,shari
|
|
|
27
28
|
FROM deps AS build
|
|
28
29
|
# windy-copy:build begin
|
|
29
30
|
COPY apps/web apps/web
|
|
31
|
+
COPY packages/example-work-order packages/example-work-order
|
|
32
|
+
COPY packages/jobs packages/jobs
|
|
30
33
|
COPY packages/license-sdk packages/license-sdk
|
|
31
34
|
COPY packages/modules packages/modules
|
|
35
|
+
COPY packages/server-sdk packages/server-sdk
|
|
32
36
|
COPY packages/shared packages/shared
|
|
37
|
+
COPY packages/storage packages/storage
|
|
33
38
|
# windy-copy:build end
|
|
34
39
|
RUN bun run --cwd apps/web build
|
|
35
40
|
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"@tanstack/vue-form": "^1.33.0",
|
|
19
19
|
"@tanstack/vue-table": "^8.21.3",
|
|
20
20
|
"@vueuse/core": "^14.3.0",
|
|
21
|
+
"@southwind-ai/example-work-order": "workspace:*",
|
|
21
22
|
"@southwind-ai/license": "workspace:*",
|
|
22
23
|
"@southwind-ai/shared": "workspace:*",
|
|
23
24
|
"class-variance-authority": "^0.7.1",
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import moduleAssembly from "../../../windy.modules.json";
|
|
2
|
+
import {
|
|
3
|
+
systemAdminUiManifests,
|
|
4
|
+
validateProjectModuleAssembly,
|
|
5
|
+
} from "@southwind-ai/modules/admin-ui";
|
|
6
|
+
import {
|
|
7
|
+
projectAdminUiManifest,
|
|
8
|
+
type AdminUiManifestSource,
|
|
9
|
+
} from "@southwind-ai/modules";
|
|
10
|
+
import { webComposition } from "../src/app/web-composition";
|
|
11
|
+
import {
|
|
12
|
+
resolveAdminUiComposition,
|
|
13
|
+
validateAdminRoutePathCollisions,
|
|
14
|
+
type AdminUiModule,
|
|
15
|
+
} from "../src/router/admin-ui-composition";
|
|
16
|
+
import { validatePlatformAdminRouteKeys } from "../src/router/manifest-routes";
|
|
17
|
+
import { platformAdminRouteBindings } from "../src/router/platform-admin-route-bindings";
|
|
18
|
+
import { reservedWebRoutePathList } from "../src/router/reserved-route-paths";
|
|
19
|
+
|
|
20
|
+
const assembly = validateProjectModuleAssembly(moduleAssembly);
|
|
21
|
+
const installedModules = await Promise.all(
|
|
22
|
+
assembly.modules
|
|
23
|
+
.filter(
|
|
24
|
+
(
|
|
25
|
+
entry,
|
|
26
|
+
): entry is typeof entry & {
|
|
27
|
+
readonly package: string;
|
|
28
|
+
} => entry.enabled && entry.package !== null,
|
|
29
|
+
)
|
|
30
|
+
.map(async (entry) => {
|
|
31
|
+
const [adminLoaded, manifestLoaded]: readonly [unknown, unknown] =
|
|
32
|
+
await Promise.all([
|
|
33
|
+
import(`${entry.package}/admin`),
|
|
34
|
+
import(`${entry.package}/manifest`),
|
|
35
|
+
]);
|
|
36
|
+
if (!isAdminUiModuleExport(adminLoaded)) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Module ${entry.name} 缺少 windyAdminUiModule 标准导出`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (!isModuleManifestExport(manifestLoaded)) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Module ${entry.name} 缺少 windyModuleManifest 标准导出`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (adminLoaded.windyAdminUiModule.manifest.name !== entry.name) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Module package ${entry.package} 导出了错误 Admin UI:${adminLoaded.windyAdminUiModule.manifest.name}`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const expectedAdminManifest = projectAdminUiManifest(
|
|
52
|
+
manifestLoaded.windyModuleManifest,
|
|
53
|
+
);
|
|
54
|
+
if (
|
|
55
|
+
JSON.stringify(adminLoaded.windyAdminUiModule.manifest) !==
|
|
56
|
+
JSON.stringify(expectedAdminManifest)
|
|
57
|
+
) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Module ${entry.name} 的 ./admin 与 ./manifest 投影不一致`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return adminLoaded.windyAdminUiModule;
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const composition = resolveAdminUiComposition({
|
|
67
|
+
declaration: webComposition,
|
|
68
|
+
assembly,
|
|
69
|
+
platformModules: systemAdminUiManifests(),
|
|
70
|
+
installedModules,
|
|
71
|
+
});
|
|
72
|
+
validateAdminRoutePathCollisions(composition, reservedWebRoutePathList);
|
|
73
|
+
validatePlatformAdminRouteKeys(
|
|
74
|
+
platformAdminRouteBindings.map(({ routeKey }) => routeKey),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
function isAdminUiModuleExport(
|
|
78
|
+
value: unknown,
|
|
79
|
+
): value is { readonly windyAdminUiModule: AdminUiModule } {
|
|
80
|
+
if (!isRecord(value) || !isRecord(value.windyAdminUiModule)) return false;
|
|
81
|
+
const module = value.windyAdminUiModule;
|
|
82
|
+
return (
|
|
83
|
+
isRecord(module.manifest) &&
|
|
84
|
+
typeof module.manifest.name === "string" &&
|
|
85
|
+
typeof module.manifest.version === "string" &&
|
|
86
|
+
Array.isArray(module.manifest.adminRoutes) &&
|
|
87
|
+
typeof module.hostRange === "string" &&
|
|
88
|
+
Array.isArray(module.pages)
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isModuleManifestExport(
|
|
93
|
+
value: unknown,
|
|
94
|
+
): value is { readonly windyModuleManifest: AdminUiManifestSource } {
|
|
95
|
+
if (!isRecord(value) || !isRecord(value.windyModuleManifest)) return false;
|
|
96
|
+
const manifest = value.windyModuleManifest;
|
|
97
|
+
return (
|
|
98
|
+
typeof manifest.name === "string" &&
|
|
99
|
+
typeof manifest.version === "string" &&
|
|
100
|
+
Array.isArray(manifest.adminRoutes)
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
105
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
106
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineWebComposition } from "../router/admin-ui-composition";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 客户拥有的 Admin UI 覆盖声明。
|
|
5
|
+
*
|
|
6
|
+
* 模块的窄 Manifest 与默认页面来自 package 标准 `./admin` 导出;这里只覆盖客户
|
|
7
|
+
* 自己接管的页面 loader 与呈现偏好,不复制路径、标题、Feature 或 Permission。
|
|
8
|
+
*/
|
|
9
|
+
export const webComposition = defineWebComposition({
|
|
10
|
+
overrides: [
|
|
11
|
+
// @windy-module work-order begin
|
|
12
|
+
{
|
|
13
|
+
moduleName: "work-order",
|
|
14
|
+
pages: [
|
|
15
|
+
{
|
|
16
|
+
routeKey: "work-order.ticket.list",
|
|
17
|
+
load: () => import("../pages/work-orders/WorkOrderListPage.vue"),
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
// @windy-module work-order end
|
|
22
|
+
],
|
|
23
|
+
presentation: [],
|
|
24
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { webComposition } from "./web-composition";
|
|
3
|
+
|
|
4
|
+
describe("project-owned Web 组合声明", () => {
|
|
5
|
+
test("示例工单只覆盖 moduleName 与 lazy page loader", () => {
|
|
6
|
+
const registration = webComposition.overrides.find(
|
|
7
|
+
({ moduleName }) => moduleName === "work-order",
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
expect(registration?.moduleName).toBe("work-order");
|
|
11
|
+
expect(registration?.pages).toHaveLength(1);
|
|
12
|
+
expect(registration?.pages[0]?.routeKey).toBe("work-order.ticket.list");
|
|
13
|
+
expect(registration?.pages[0]?.load).toBeTypeOf("function");
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -40,6 +40,7 @@ import { useAccessSnapshot } from "@/composables/useAccessSnapshot";
|
|
|
40
40
|
import { useLicenseRuntime } from "@/composables/useLicenseRuntime";
|
|
41
41
|
// @windy-module system.license end
|
|
42
42
|
import { usePlatformSettings } from "@/composables/usePlatformSettings";
|
|
43
|
+
import { webAdminUiComposition } from "@/router/web-composition-host";
|
|
43
44
|
import { authorizedSidebarMenus } from "./navigation";
|
|
44
45
|
|
|
45
46
|
interface NavigationGroup {
|
|
@@ -74,7 +75,13 @@ const license = useLicenseRuntime();
|
|
|
74
75
|
const platform = usePlatformSettings();
|
|
75
76
|
const openGroups = reactive<Record<string, boolean>>(loadOpenGroups());
|
|
76
77
|
const authorizedMenus = computed(() =>
|
|
77
|
-
access.snapshot.value
|
|
78
|
+
access.snapshot.value
|
|
79
|
+
? authorizedSidebarMenus(
|
|
80
|
+
access.snapshot.value,
|
|
81
|
+
webAdminUiComposition.presentation,
|
|
82
|
+
webAdminUiComposition,
|
|
83
|
+
)
|
|
84
|
+
: [],
|
|
78
85
|
);
|
|
79
86
|
const settingsMenu = computed(() =>
|
|
80
87
|
findMenu(authorizedMenus.value, "system.settings"),
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
import { Input } from "@/components/ui/input";
|
|
24
24
|
import { useAccessSnapshot } from "@/composables/useAccessSnapshot";
|
|
25
25
|
import { useGlobalSearch } from "@/composables/useGlobalSearch";
|
|
26
|
+
import { webAdminUiComposition } from "@/router/web-composition-host";
|
|
26
27
|
import GlobalSearchResults from "./GlobalSearchResults.vue";
|
|
27
28
|
import { authorizedSidebarMenus } from "./navigation";
|
|
28
29
|
|
|
@@ -42,7 +43,13 @@ const businessSearch = useGlobalSearch(query);
|
|
|
42
43
|
|
|
43
44
|
const items = computed(() =>
|
|
44
45
|
access.snapshot.value
|
|
45
|
-
? flattenMenus(
|
|
46
|
+
? flattenMenus(
|
|
47
|
+
authorizedSidebarMenus(
|
|
48
|
+
access.snapshot.value,
|
|
49
|
+
webAdminUiComposition.presentation,
|
|
50
|
+
webAdminUiComposition,
|
|
51
|
+
),
|
|
52
|
+
)
|
|
46
53
|
: [],
|
|
47
54
|
);
|
|
48
55
|
const results = computed(() => {
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
MenuNode,
|
|
6
6
|
PermissionDefinition,
|
|
7
7
|
} from "@southwind-ai/shared";
|
|
8
|
+
import type { AdminRoutePresentation } from "../router/admin-ui-composition";
|
|
8
9
|
|
|
9
10
|
export interface WebAccessActor {
|
|
10
11
|
id: string;
|
|
@@ -47,6 +48,11 @@ export interface WebAccessDecision {
|
|
|
47
48
|
reason: "allowed" | "missing-permission" | "feature-disabled";
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
export interface AdminRouteActivation {
|
|
52
|
+
readonly knownRouteKeys: ReadonlySet<string>;
|
|
53
|
+
readonly visibleRouteKeys: ReadonlySet<string>;
|
|
54
|
+
}
|
|
55
|
+
|
|
50
56
|
export const dashboardMenu: MenuNode = {
|
|
51
57
|
key: "dashboard",
|
|
52
58
|
title: "工作台",
|
|
@@ -80,9 +86,11 @@ export function evaluateWebAccess(
|
|
|
80
86
|
|
|
81
87
|
export function authorizedSidebarMenus(
|
|
82
88
|
snapshot: WebAccessSnapshot,
|
|
89
|
+
presentation: ReadonlyMap<string, AdminRoutePresentation> = new Map(),
|
|
90
|
+
activation?: AdminRouteActivation,
|
|
83
91
|
): MenuNode[] {
|
|
84
92
|
return [dashboardMenu, ...snapshot.menus]
|
|
85
|
-
.map((menu) => authorizeMenu(menu, snapshot))
|
|
93
|
+
.map((menu) => authorizeMenu(menu, snapshot, presentation, activation))
|
|
86
94
|
.filter((menu): menu is MenuNode => Boolean(menu))
|
|
87
95
|
.sort((left, right) => left.order - right.order);
|
|
88
96
|
}
|
|
@@ -94,14 +102,31 @@ export function canToggleFeatureFlags(snapshot: WebAccessSnapshot): boolean {
|
|
|
94
102
|
function authorizeMenu(
|
|
95
103
|
menu: MenuNode,
|
|
96
104
|
snapshot: WebAccessSnapshot,
|
|
105
|
+
presentation: ReadonlyMap<string, AdminRoutePresentation>,
|
|
106
|
+
activation?: AdminRouteActivation,
|
|
97
107
|
): MenuNode | undefined {
|
|
98
108
|
if (!menu.visible) return undefined;
|
|
109
|
+
if (
|
|
110
|
+
activation?.knownRouteKeys.has(menu.key) &&
|
|
111
|
+
!activation.visibleRouteKeys.has(menu.key)
|
|
112
|
+
) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
const override = presentation.get(menu.key);
|
|
116
|
+
if (override?.hidden) return undefined;
|
|
99
117
|
const children = menu.children
|
|
100
|
-
?.map((child) => authorizeMenu(child, snapshot))
|
|
101
|
-
.filter((child): child is MenuNode => Boolean(child))
|
|
118
|
+
?.map((child) => authorizeMenu(child, snapshot, presentation, activation))
|
|
119
|
+
.filter((child): child is MenuNode => Boolean(child))
|
|
120
|
+
.sort((left, right) => left.order - right.order);
|
|
102
121
|
const access = evaluateWebAccess(snapshot, menu);
|
|
103
122
|
if (!access.allowed || (menu.type === "catalog" && children?.length === 0)) {
|
|
104
123
|
return undefined;
|
|
105
124
|
}
|
|
106
|
-
return {
|
|
125
|
+
return {
|
|
126
|
+
...menu,
|
|
127
|
+
...(override?.title === undefined ? {} : { title: override.title }),
|
|
128
|
+
...(override?.icon === undefined ? {} : { icon: override.icon }),
|
|
129
|
+
...(override?.order === undefined ? {} : { order: override.order }),
|
|
130
|
+
children,
|
|
131
|
+
};
|
|
107
132
|
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
evaluateWebAccess,
|
|
7
7
|
type WebAccessSnapshot,
|
|
8
8
|
} from "./navigation";
|
|
9
|
+
import type { AdminRoutePresentation } from "../router/admin-ui-composition";
|
|
9
10
|
|
|
10
11
|
describe("Web 访问快照", () => {
|
|
11
12
|
test("菜单同时受服务端权限和 Feature 有效态约束", () => {
|
|
@@ -45,6 +46,56 @@ describe("Web 访问快照", () => {
|
|
|
45
46
|
"system.audit",
|
|
46
47
|
);
|
|
47
48
|
});
|
|
49
|
+
|
|
50
|
+
test("客户可按稳定 Route Key 隐藏、排序或替换菜单呈现", () => {
|
|
51
|
+
const snapshot = accessSnapshot();
|
|
52
|
+
snapshot.menus[0]!.children!.push({
|
|
53
|
+
key: "system.role",
|
|
54
|
+
title: "角色管理",
|
|
55
|
+
type: "menu",
|
|
56
|
+
order: 20,
|
|
57
|
+
visible: true,
|
|
58
|
+
path: "/admin/system/roles",
|
|
59
|
+
});
|
|
60
|
+
const presentation = new Map<string, AdminRoutePresentation>([
|
|
61
|
+
[
|
|
62
|
+
"system.audit",
|
|
63
|
+
{
|
|
64
|
+
routeKey: "system.audit",
|
|
65
|
+
title: "操作留痕",
|
|
66
|
+
icon: "scroll-text",
|
|
67
|
+
order: 30,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
[
|
|
71
|
+
"system.role",
|
|
72
|
+
{
|
|
73
|
+
routeKey: "system.role",
|
|
74
|
+
hidden: true,
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
const menus = authorizedSidebarMenus(snapshot, presentation);
|
|
80
|
+
const audit = menus[1]?.children?.[0];
|
|
81
|
+
|
|
82
|
+
expect(audit).toMatchObject({
|
|
83
|
+
key: "system.audit",
|
|
84
|
+
title: "操作留痕",
|
|
85
|
+
icon: "scroll-text",
|
|
86
|
+
order: 30,
|
|
87
|
+
});
|
|
88
|
+
expect(flattenKeys(menus)).not.toContain("system.role");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("根装配清单关闭 Admin 呈现后不保留失效菜单", () => {
|
|
92
|
+
const menus = authorizedSidebarMenus(accessSnapshot(), new Map(), {
|
|
93
|
+
knownRouteKeys: new Set(["system.audit"]),
|
|
94
|
+
visibleRouteKeys: new Set(),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(flattenKeys(menus)).not.toContain("system.audit");
|
|
98
|
+
});
|
|
48
99
|
});
|
|
49
100
|
|
|
50
101
|
function accessSnapshot(): WebAccessSnapshot {
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
authorizedSidebarMenus,
|
|
6
6
|
type WebAccessSnapshot,
|
|
7
7
|
} from "@/layout/navigation";
|
|
8
|
+
import { webAdminUiComposition } from "@/router/web-composition-host";
|
|
8
9
|
|
|
9
10
|
export interface DashboardMetric {
|
|
10
11
|
label: string;
|
|
@@ -32,7 +33,11 @@ export function buildDashboardModel(
|
|
|
32
33
|
): DashboardModel {
|
|
33
34
|
const features = snapshot?.features || [];
|
|
34
35
|
const availableMenuCount = snapshot
|
|
35
|
-
? authorizedSidebarMenus(
|
|
36
|
+
? authorizedSidebarMenus(
|
|
37
|
+
snapshot,
|
|
38
|
+
webAdminUiComposition.presentation,
|
|
39
|
+
webAdminUiComposition,
|
|
40
|
+
)
|
|
36
41
|
.flatMap((menu) => (menu.children?.length ? menu.children : [menu]))
|
|
37
42
|
.filter((menu) => menu.key !== "dashboard" && Boolean(menu.path)).length
|
|
38
43
|
: 0;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type {
|
|
3
|
+
WorkOrder,
|
|
4
|
+
WorkOrderPriority,
|
|
5
|
+
WorkOrderWorkflowStatus,
|
|
6
|
+
} from "@southwind-ai/example-work-order/contract";
|
|
7
|
+
import { DEFAULT_PAGE_SIZE } from "@southwind-ai/shared";
|
|
8
|
+
import { RefreshCwIcon, SearchIcon } from "@lucide/vue";
|
|
9
|
+
import { onMounted, shallowRef } from "vue";
|
|
10
|
+
import DataPagination from "@/components/common/DataPagination.vue";
|
|
11
|
+
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
|
12
|
+
import { Badge } from "@/components/ui/badge";
|
|
13
|
+
import { Button } from "@/components/ui/button";
|
|
14
|
+
import {
|
|
15
|
+
Card,
|
|
16
|
+
CardContent,
|
|
17
|
+
CardDescription,
|
|
18
|
+
CardHeader,
|
|
19
|
+
CardTitle,
|
|
20
|
+
} from "@/components/ui/card";
|
|
21
|
+
import { Input } from "@/components/ui/input";
|
|
22
|
+
import {
|
|
23
|
+
Table,
|
|
24
|
+
TableBody,
|
|
25
|
+
TableCell,
|
|
26
|
+
TableEmpty,
|
|
27
|
+
TableHead,
|
|
28
|
+
TableHeader,
|
|
29
|
+
TableRow,
|
|
30
|
+
} from "@/components/ui/table";
|
|
31
|
+
import { formatDateTime } from "@/lib/date-time";
|
|
32
|
+
import { listWorkOrders } from "@/services/work-order-api";
|
|
33
|
+
|
|
34
|
+
const items = shallowRef<WorkOrder[]>([]);
|
|
35
|
+
const page = shallowRef(1);
|
|
36
|
+
const pageSize = shallowRef(DEFAULT_PAGE_SIZE);
|
|
37
|
+
const total = shallowRef(0);
|
|
38
|
+
const query = shallowRef("");
|
|
39
|
+
const loading = shallowRef(false);
|
|
40
|
+
const error = shallowRef("");
|
|
41
|
+
|
|
42
|
+
onMounted(() => void load());
|
|
43
|
+
|
|
44
|
+
async function load() {
|
|
45
|
+
if (loading.value) return;
|
|
46
|
+
loading.value = true;
|
|
47
|
+
error.value = "";
|
|
48
|
+
try {
|
|
49
|
+
const result = await listWorkOrders({
|
|
50
|
+
page: page.value,
|
|
51
|
+
pageSize: pageSize.value,
|
|
52
|
+
q: query.value,
|
|
53
|
+
});
|
|
54
|
+
items.value = result.items;
|
|
55
|
+
page.value = result.page;
|
|
56
|
+
pageSize.value = result.pageSize;
|
|
57
|
+
total.value = result.total;
|
|
58
|
+
} catch (currentError) {
|
|
59
|
+
error.value =
|
|
60
|
+
currentError instanceof Error ? currentError.message : "工单加载失败";
|
|
61
|
+
} finally {
|
|
62
|
+
loading.value = false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function search() {
|
|
67
|
+
page.value = 1;
|
|
68
|
+
void load();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function setPage(value: number) {
|
|
72
|
+
page.value = value;
|
|
73
|
+
void load();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function setPageSize(value: number) {
|
|
77
|
+
pageSize.value = value;
|
|
78
|
+
page.value = 1;
|
|
79
|
+
void load();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const priorityLabels: Record<WorkOrderPriority, string> = {
|
|
83
|
+
low: "低",
|
|
84
|
+
medium: "中",
|
|
85
|
+
high: "高",
|
|
86
|
+
};
|
|
87
|
+
const workflowLabels: Record<WorkOrderWorkflowStatus, string> = {
|
|
88
|
+
open: "待处理",
|
|
89
|
+
"in-progress": "处理中",
|
|
90
|
+
resolved: "已解决",
|
|
91
|
+
};
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
<template>
|
|
95
|
+
<main class="space-y-5">
|
|
96
|
+
<header class="space-y-2">
|
|
97
|
+
<h1 class="text-2xl font-semibold text-foreground">工单管理</h1>
|
|
98
|
+
<p class="text-sm text-muted-foreground">
|
|
99
|
+
示例业务模块提供的工单列表,入口、权限和功能开关均来自 Module Manifest。
|
|
100
|
+
</p>
|
|
101
|
+
</header>
|
|
102
|
+
|
|
103
|
+
<Alert v-if="error" variant="destructive">
|
|
104
|
+
<AlertTitle>无法加载工单</AlertTitle>
|
|
105
|
+
<AlertDescription>{{ error }}</AlertDescription>
|
|
106
|
+
</Alert>
|
|
107
|
+
|
|
108
|
+
<Card>
|
|
109
|
+
<CardHeader>
|
|
110
|
+
<CardTitle>工单列表</CardTitle>
|
|
111
|
+
<CardDescription>
|
|
112
|
+
按当前账号的数据范围查看工单,可使用标题或描述搜索。
|
|
113
|
+
</CardDescription>
|
|
114
|
+
</CardHeader>
|
|
115
|
+
<CardContent class="space-y-4">
|
|
116
|
+
<form class="flex flex-col gap-2 sm:flex-row" @submit.prevent="search">
|
|
117
|
+
<Input
|
|
118
|
+
v-model="query"
|
|
119
|
+
aria-label="搜索工单"
|
|
120
|
+
placeholder="搜索标题或描述"
|
|
121
|
+
class="sm:max-w-sm"
|
|
122
|
+
/>
|
|
123
|
+
<Button type="submit" :disabled="loading">
|
|
124
|
+
<SearchIcon data-icon="inline-start" />
|
|
125
|
+
搜索
|
|
126
|
+
</Button>
|
|
127
|
+
<Button
|
|
128
|
+
type="button"
|
|
129
|
+
variant="outline"
|
|
130
|
+
:disabled="loading"
|
|
131
|
+
@click="load"
|
|
132
|
+
>
|
|
133
|
+
<RefreshCwIcon
|
|
134
|
+
data-icon="inline-start"
|
|
135
|
+
:class="{ 'animate-spin': loading }"
|
|
136
|
+
/>
|
|
137
|
+
刷新
|
|
138
|
+
</Button>
|
|
139
|
+
</form>
|
|
140
|
+
|
|
141
|
+
<div class="overflow-hidden rounded-md border">
|
|
142
|
+
<Table>
|
|
143
|
+
<TableHeader>
|
|
144
|
+
<TableRow>
|
|
145
|
+
<TableHead>工单</TableHead>
|
|
146
|
+
<TableHead>优先级</TableHead>
|
|
147
|
+
<TableHead>进度</TableHead>
|
|
148
|
+
<TableHead>负责人</TableHead>
|
|
149
|
+
<TableHead>最近更新</TableHead>
|
|
150
|
+
</TableRow>
|
|
151
|
+
</TableHeader>
|
|
152
|
+
<TableBody>
|
|
153
|
+
<TableEmpty v-if="!loading && items.length === 0" :colspan="5">
|
|
154
|
+
暂无符合条件的工单
|
|
155
|
+
</TableEmpty>
|
|
156
|
+
<TableRow v-for="item in items" :key="item.id">
|
|
157
|
+
<TableCell>
|
|
158
|
+
<div class="space-y-1">
|
|
159
|
+
<p class="font-medium text-foreground">{{ item.title }}</p>
|
|
160
|
+
<p class="line-clamp-2 text-xs text-muted-foreground">
|
|
161
|
+
{{ item.description }}
|
|
162
|
+
</p>
|
|
163
|
+
</div>
|
|
164
|
+
</TableCell>
|
|
165
|
+
<TableCell>
|
|
166
|
+
<Badge
|
|
167
|
+
:variant="
|
|
168
|
+
item.priority === 'high' ? 'destructive' : 'outline'
|
|
169
|
+
"
|
|
170
|
+
>
|
|
171
|
+
{{ priorityLabels[item.priority] }}
|
|
172
|
+
</Badge>
|
|
173
|
+
</TableCell>
|
|
174
|
+
<TableCell>
|
|
175
|
+
<Badge
|
|
176
|
+
:variant="
|
|
177
|
+
item.workflowStatus === 'resolved'
|
|
178
|
+
? 'secondary'
|
|
179
|
+
: 'default'
|
|
180
|
+
"
|
|
181
|
+
>
|
|
182
|
+
{{ workflowLabels[item.workflowStatus] }}
|
|
183
|
+
</Badge>
|
|
184
|
+
</TableCell>
|
|
185
|
+
<TableCell>{{ item.ownerId }}</TableCell>
|
|
186
|
+
<TableCell>{{ formatDateTime(item.updatedAt) }}</TableCell>
|
|
187
|
+
</TableRow>
|
|
188
|
+
</TableBody>
|
|
189
|
+
</Table>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<DataPagination
|
|
193
|
+
:page="page"
|
|
194
|
+
:page-size="pageSize"
|
|
195
|
+
:total="total"
|
|
196
|
+
@update:page="setPage"
|
|
197
|
+
@update:page-size="setPageSize"
|
|
198
|
+
/>
|
|
199
|
+
</CardContent>
|
|
200
|
+
</Card>
|
|
201
|
+
</main>
|
|
202
|
+
</template>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { flushPromises, mount } from "@vue/test-utils";
|
|
2
|
+
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
3
|
+
import WorkOrderListPage from "./WorkOrderListPage.vue";
|
|
4
|
+
|
|
5
|
+
const mocks = vi.hoisted(() => ({
|
|
6
|
+
list: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
vi.mock("@/services/work-order-api", () => ({
|
|
10
|
+
listWorkOrders: mocks.list,
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
describe("WorkOrderListPage", () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
mocks.list.mockReset();
|
|
16
|
+
mocks.list.mockResolvedValue({
|
|
17
|
+
items: [
|
|
18
|
+
{
|
|
19
|
+
id: "ticket_1",
|
|
20
|
+
title: "登录异常",
|
|
21
|
+
description: "用户无法进入工作台",
|
|
22
|
+
priority: "high",
|
|
23
|
+
workflowStatus: "in-progress",
|
|
24
|
+
ownerId: "user_1",
|
|
25
|
+
status: "active",
|
|
26
|
+
createdAt: "2026-07-29T01:00:00.000Z",
|
|
27
|
+
createdBy: "user_1",
|
|
28
|
+
updatedAt: "2026-07-29T02:00:00.000Z",
|
|
29
|
+
updatedBy: "user_1",
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
page: 1,
|
|
33
|
+
pageSize: 10,
|
|
34
|
+
total: 1,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("加载并呈现当前数据范围内的工单", async () => {
|
|
39
|
+
const wrapper = mount(WorkOrderListPage);
|
|
40
|
+
await flushPromises();
|
|
41
|
+
|
|
42
|
+
expect(mocks.list).toHaveBeenCalledWith({
|
|
43
|
+
page: 1,
|
|
44
|
+
pageSize: 10,
|
|
45
|
+
q: "",
|
|
46
|
+
});
|
|
47
|
+
expect(wrapper.text()).toContain("登录异常");
|
|
48
|
+
expect(wrapper.text()).toContain("高");
|
|
49
|
+
expect(wrapper.text()).toContain("处理中");
|
|
50
|
+
});
|
|
51
|
+
});
|