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
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { flushPromises, mount } from "@vue/test-utils";
|
|
2
|
+
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
3
|
+
import WatermarkSettingsSection from "./WatermarkSettingsSection.vue";
|
|
4
|
+
|
|
5
|
+
const state = vi.hoisted(() => {
|
|
6
|
+
const actor = {
|
|
7
|
+
id: "user_settings",
|
|
8
|
+
type: "user" as const,
|
|
9
|
+
name: "张三",
|
|
10
|
+
username: "zhangsan",
|
|
11
|
+
displayName: "张三",
|
|
12
|
+
email: "zs@example.com",
|
|
13
|
+
roleCodes: ["admin"],
|
|
14
|
+
permissionKeys: ["system.watermark.read", "system.watermark.manage"],
|
|
15
|
+
};
|
|
16
|
+
return {
|
|
17
|
+
actor: { value: actor },
|
|
18
|
+
access: {
|
|
19
|
+
value: {
|
|
20
|
+
actor,
|
|
21
|
+
menus: [],
|
|
22
|
+
permissions: [],
|
|
23
|
+
features: [
|
|
24
|
+
{
|
|
25
|
+
key: "system.watermark",
|
|
26
|
+
enabled: true,
|
|
27
|
+
visible: "visible" as const,
|
|
28
|
+
allowed: true,
|
|
29
|
+
reason: "enabled",
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
policy: {
|
|
35
|
+
value: {
|
|
36
|
+
enabled: true,
|
|
37
|
+
businessPagesEnabled: true,
|
|
38
|
+
density: "medium" as const,
|
|
39
|
+
contentFields: ["username", "displayName"] as const,
|
|
40
|
+
customContent: "",
|
|
41
|
+
opacityPercent: 8,
|
|
42
|
+
fontSize: 14,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
loadManaged: vi.fn(),
|
|
46
|
+
save: vi.fn(),
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
vi.mock("@/composables/useAccessSnapshot", () => ({
|
|
51
|
+
useAccessSnapshot: () => ({
|
|
52
|
+
snapshot: state.access,
|
|
53
|
+
ensureLoaded: vi.fn(async () => state.access.value),
|
|
54
|
+
}),
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
vi.mock("@/composables/useAuthSession", () => ({
|
|
58
|
+
useAuthSession: () => ({ actor: state.actor }),
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
vi.mock("@/composables/useAppToast", () => ({
|
|
62
|
+
useAppToast: () => ({
|
|
63
|
+
success: vi.fn(),
|
|
64
|
+
error: vi.fn(),
|
|
65
|
+
}),
|
|
66
|
+
}));
|
|
67
|
+
|
|
68
|
+
vi.mock("@/composables/useWatermarkSettings", () => ({
|
|
69
|
+
useWatermarkSettings: () => ({
|
|
70
|
+
policy: state.policy,
|
|
71
|
+
loading: { value: false },
|
|
72
|
+
loadManaged: state.loadManaged,
|
|
73
|
+
save: state.save,
|
|
74
|
+
}),
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
beforeEach(() => {
|
|
78
|
+
state.loadManaged.mockReset();
|
|
79
|
+
state.loadManaged.mockResolvedValue(state.policy.value);
|
|
80
|
+
state.save.mockReset();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("水印设置表单", () => {
|
|
84
|
+
test("只呈现一个开关且外观草稿立即更新预览", async () => {
|
|
85
|
+
const wrapper = mount(WatermarkSettingsSection);
|
|
86
|
+
await flushPromises();
|
|
87
|
+
|
|
88
|
+
expect(wrapper.text()).not.toContain("覆盖业务页面");
|
|
89
|
+
expect(wrapper.findAll('[aria-label="显示页面水印"]')).toHaveLength(1);
|
|
90
|
+
|
|
91
|
+
await wrapper.get('input[aria-label="水印不透明度"]').setValue("20");
|
|
92
|
+
await wrapper.get('input[aria-label="水印字体大小"]').setValue("24");
|
|
93
|
+
|
|
94
|
+
const previewStyle =
|
|
95
|
+
wrapper
|
|
96
|
+
.get('[data-testid="watermark-preview-pattern"]')
|
|
97
|
+
.attributes("style") ?? "";
|
|
98
|
+
expect(previewStyle).toContain("opacity: 0.2");
|
|
99
|
+
expect(decodeURIComponent(previewStyle)).toContain('font-size="24"');
|
|
100
|
+
expect(state.save).not.toHaveBeenCalled();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("任意勾选身份字段后立即进入预览内容", async () => {
|
|
104
|
+
const wrapper = mount(WatermarkSettingsSection);
|
|
105
|
+
await flushPromises();
|
|
106
|
+
|
|
107
|
+
await wrapper.get('[aria-label="邮箱"]').trigger("click");
|
|
108
|
+
|
|
109
|
+
const previewStyle =
|
|
110
|
+
wrapper
|
|
111
|
+
.get('[data-testid="watermark-preview-pattern"]')
|
|
112
|
+
.attributes("style") ?? "";
|
|
113
|
+
expect(decodeURIComponent(previewStyle)).toContain("zs@example.com");
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { defineComponent, h, type Component } from "vue";
|
|
2
|
+
import type { AdminPageBinding } from "./admin-ui-composition";
|
|
3
|
+
|
|
4
|
+
export function isolateAdminPageLoader(
|
|
5
|
+
moduleName: string,
|
|
6
|
+
routeKey: string,
|
|
7
|
+
load: AdminPageBinding["load"],
|
|
8
|
+
): AdminPageBinding["load"] {
|
|
9
|
+
return async () => {
|
|
10
|
+
try {
|
|
11
|
+
const loaded = await load();
|
|
12
|
+
if (!loaded?.default) {
|
|
13
|
+
throw new Error("模块页面缺少 default export");
|
|
14
|
+
}
|
|
15
|
+
return loaded;
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.error("Admin Extension 页面加载失败", {
|
|
18
|
+
moduleName,
|
|
19
|
+
routeKey,
|
|
20
|
+
error,
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
default: failureComponent(moduleName, routeKey),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function failureComponent(moduleName: string, routeKey: string): Component {
|
|
30
|
+
return defineComponent({
|
|
31
|
+
name: "AdminExtensionLoadFailure",
|
|
32
|
+
setup() {
|
|
33
|
+
return () =>
|
|
34
|
+
h("main", { class: "space-y-3 p-6" }, [
|
|
35
|
+
h("h1", { class: "text-xl font-semibold" }, "模块页面暂不可用"),
|
|
36
|
+
h(
|
|
37
|
+
"p",
|
|
38
|
+
{ class: "text-sm text-muted-foreground" },
|
|
39
|
+
`模块 ${moduleName} 的页面 ${routeKey} 加载失败。其他管理功能仍可继续使用。`,
|
|
40
|
+
),
|
|
41
|
+
]);
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from "vitest";
|
|
2
|
+
import { isolateAdminPageLoader } from "./admin-extension-loader";
|
|
3
|
+
|
|
4
|
+
describe("Admin Extension 页面隔离", () => {
|
|
5
|
+
test("正常 loader 原样返回页面组件", async () => {
|
|
6
|
+
const page = { name: "CasePage" };
|
|
7
|
+
const load = isolateAdminPageLoader("case", "case.record", async () => ({
|
|
8
|
+
default: page,
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
expect(await load()).toEqual({ default: page });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("loader 失败时返回 Admin 壳内故障页而不是抛出", async () => {
|
|
15
|
+
const error = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
16
|
+
const load = isolateAdminPageLoader("case", "case.record", async () => {
|
|
17
|
+
throw new Error("secret stack");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const result = await load();
|
|
21
|
+
|
|
22
|
+
expect(result.default).toMatchObject({
|
|
23
|
+
name: "AdminExtensionLoadFailure",
|
|
24
|
+
});
|
|
25
|
+
expect(error).toHaveBeenCalledOnce();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("loader 缺少 default export 时同样隔离为故障页", async () => {
|
|
29
|
+
const error = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
30
|
+
const load = isolateAdminPageLoader(
|
|
31
|
+
"case",
|
|
32
|
+
"case.record",
|
|
33
|
+
async () => ({}) as never,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const result = await load();
|
|
37
|
+
|
|
38
|
+
expect(result.default).toMatchObject({
|
|
39
|
+
name: "AdminExtensionLoadFailure",
|
|
40
|
+
});
|
|
41
|
+
expect(error).toHaveBeenCalledWith(
|
|
42
|
+
"Admin Extension 页面加载失败",
|
|
43
|
+
expect.objectContaining({
|
|
44
|
+
moduleName: "case",
|
|
45
|
+
routeKey: "case.record",
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface AdminUiCompositionValidationPluginOptions {
|
|
2
|
+
readonly validate?: () => void;
|
|
3
|
+
readonly validatorEntry?: string;
|
|
4
|
+
readonly runValidator?: (entry: string) => number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface AdminUiCompositionValidationPlugin {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly apply: "build";
|
|
10
|
+
buildStart(): void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function adminUiCompositionValidationPlugin(
|
|
14
|
+
options: AdminUiCompositionValidationPluginOptions,
|
|
15
|
+
): AdminUiCompositionValidationPlugin {
|
|
16
|
+
return {
|
|
17
|
+
name: "windy-admin-ui-composition-validation",
|
|
18
|
+
apply: "build",
|
|
19
|
+
buildStart() {
|
|
20
|
+
if (options.validate) {
|
|
21
|
+
options.validate();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (!options.validatorEntry || !options.runValidator) {
|
|
25
|
+
throw new Error("Admin UI 组合构建门禁缺少 validatorEntry 或执行器");
|
|
26
|
+
}
|
|
27
|
+
const exitCode = options.runValidator(options.validatorEntry);
|
|
28
|
+
if (exitCode !== 0) {
|
|
29
|
+
throw new Error(`Admin UI 组合构建校验失败(exit ${exitCode})`);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { validateProjectModuleAssembly } from "@southwind-ai/modules/admin-ui";
|
|
3
|
+
import { adminUiCompositionValidationPlugin } from "./admin-ui-composition-plugin";
|
|
4
|
+
|
|
5
|
+
describe("Admin UI 组合构建门禁", () => {
|
|
6
|
+
test("Vite buildStart 使用运行时同一解析器并 fail closed", () => {
|
|
7
|
+
const plugin = adminUiCompositionValidationPlugin({
|
|
8
|
+
validate: () =>
|
|
9
|
+
validateProjectModuleAssembly({ schemaVersion: 2, modules: [] }),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
expect(typeof plugin.buildStart).toBe("function");
|
|
13
|
+
expect(() => {
|
|
14
|
+
if (typeof plugin.buildStart !== "function") {
|
|
15
|
+
throw new Error("buildStart 未注册");
|
|
16
|
+
}
|
|
17
|
+
plugin.buildStart.call(undefined as never);
|
|
18
|
+
}).toThrow("schemaVersion 不受支持");
|
|
19
|
+
});
|
|
20
|
+
});
|