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.
Files changed (234) hide show
  1. package/README.md +28 -13
  2. package/dist/cli.js +788 -280
  3. package/package.json +1 -1
  4. package/template/.agents/skills/windy-business-module/SKILL.md +34 -10
  5. package/template/.windy-template.json +2 -2
  6. package/template/AGENTS.md +18 -8
  7. package/template/README.md +32 -22
  8. package/template/apps/server/Dockerfile +1 -0
  9. package/template/apps/server/package.json +1 -0
  10. package/template/apps/server/scripts/validate-module-composition-once.ts +4 -0
  11. package/template/apps/server/scripts/validate-module-composition.ts +28 -0
  12. package/template/apps/server/src/agent/data-scope-routes.test.ts +20 -5
  13. package/template/apps/server/src/agent/routes.test.ts +20 -7
  14. package/template/apps/server/src/agent/routes.ts +14 -0
  15. package/template/apps/server/src/agent/runtime-bootstrap.ts +22 -6
  16. package/template/apps/server/src/agent/sandbox-worker.ts +2 -2
  17. package/template/apps/server/src/agent/unavailable-routes.ts +33 -0
  18. package/template/apps/server/src/app/server-composition.ts +56 -0
  19. package/template/apps/server/src/application-services.ts +2 -0
  20. package/template/apps/server/src/bulk-data/service.ts +14 -0
  21. package/template/apps/server/src/data-governance/export/authorization-port.ts +1 -1
  22. package/template/apps/server/src/data-governance/export/postgres-api-fixture.ts +18 -2
  23. package/template/apps/server/src/data-governance/export/postgres-recovery.integration.test.ts +10 -9
  24. package/template/apps/server/src/data-governance/export/routes-test-fixture.ts +6 -8
  25. package/template/apps/server/src/data-governance/user-plaintext-routes.test.ts +4 -2
  26. package/template/apps/server/src/feature/routes.authorization.test.ts +26 -1
  27. package/template/apps/server/src/feature/routes.ts +33 -15
  28. package/template/apps/server/src/foundation-activation.test.ts +60 -0
  29. package/template/apps/server/src/foundation.ts +64 -5
  30. package/template/apps/server/src/guards.test.ts +23 -0
  31. package/template/apps/server/src/guards.ts +12 -0
  32. package/template/apps/server/src/index.ts +28 -34
  33. package/template/apps/server/src/installed-business-modules.ts +13 -24
  34. package/template/apps/server/src/jobs/legacy-adapter.integration.test.ts +1 -0
  35. package/template/apps/server/src/jobs/legacy-adapter.test.ts +106 -0
  36. package/template/apps/server/src/jobs/legacy-service-runtime.ts +40 -0
  37. package/template/apps/server/src/license/activation.test.ts +30 -0
  38. package/template/apps/server/src/license/activation.ts +26 -0
  39. package/template/apps/server/src/license/restriction.test.ts +16 -0
  40. package/template/apps/server/src/license/routes-offline-code.test.ts +6 -2
  41. package/template/apps/server/src/license/routes.ts +20 -4
  42. package/template/apps/server/src/license/runtime-service.test.ts +10 -2
  43. package/template/apps/server/src/module-bootstrap.ts +14 -23
  44. package/template/apps/server/src/module-composition.test.ts +43 -2
  45. package/template/apps/server/src/module-composition.ts +29 -0
  46. package/template/apps/server/src/module-host/durable-context-guard.test.ts +172 -0
  47. package/template/apps/server/src/module-host/durable-context-unavailable.test.ts +50 -1
  48. package/template/apps/server/src/module-host/durable-context.test.ts +42 -25
  49. package/template/apps/server/src/module-host/durable-context.ts +42 -2
  50. package/template/apps/server/src/module-host/durable-handler-registration.ts +96 -5
  51. package/template/apps/server/src/module-host/implementation-completeness.test.ts +185 -0
  52. package/template/apps/server/src/module-host/implementation-completeness.ts +132 -0
  53. package/template/apps/server/src/module-host/initialize-contracts.ts +14 -0
  54. package/template/apps/server/src/module-host/initialize.test.ts +78 -2
  55. package/template/apps/server/src/module-host/initialize.ts +15 -4
  56. package/template/apps/server/src/module-host/project-attachments.test.ts +92 -0
  57. package/template/apps/server/src/module-host/project-attachments.ts +58 -0
  58. package/template/apps/server/src/module-host/project-composition-package.test.ts +186 -0
  59. package/template/apps/server/src/module-host/project-composition.test.ts +260 -0
  60. package/template/apps/server/src/module-host/project-composition.ts +311 -0
  61. package/template/apps/server/src/module-host/request-context.ts +3 -0
  62. package/template/apps/server/src/module-host/route-declaration.ts +15 -6
  63. package/template/apps/server/src/module-host/route-installer.test.ts +15 -2
  64. package/template/apps/server/src/module-host/route-installer.ts +12 -2
  65. package/template/apps/server/src/module-host/route-registration.test.ts +13 -4
  66. package/template/apps/server/src/module-host/runtime-composition-compatibility.test.ts +63 -0
  67. package/template/apps/server/src/module-host/runtime-composition-compatibility.ts +46 -0
  68. package/template/apps/server/src/module-host/runtime-composition.ts +34 -0
  69. package/template/apps/server/src/module-host/search-provider-registration.test.ts +64 -0
  70. package/template/apps/server/src/module-host/search-provider-registration.ts +35 -0
  71. package/template/apps/server/src/module-host/static-route-authorization.test.ts +99 -0
  72. package/template/apps/server/src/module-host/static-route-authorization.ts +90 -0
  73. package/template/apps/server/src/module-host/task-definitions.ts +1 -0
  74. package/template/apps/server/src/module-host/user-directory-composition.test.ts +4 -1
  75. package/template/apps/server/src/module-host.test.ts +2 -0
  76. package/template/apps/server/src/module-preflight.test.ts +289 -0
  77. package/template/apps/server/src/module-preflight.ts +96 -0
  78. package/template/apps/server/src/module-runtime-validation.ts +1 -0
  79. package/template/apps/server/src/platform-app-bootstrap.ts +50 -0
  80. package/template/apps/server/src/platform-http-runtime.ts +32 -7
  81. package/template/apps/server/src/route-guards.test.ts +22 -0
  82. package/template/apps/server/src/route-guards.ts +10 -3
  83. package/template/apps/server/src/runtime-audit-query.ts +50 -0
  84. package/template/apps/server/src/runtime-feature.test.ts +46 -0
  85. package/template/apps/server/src/runtime-feature.ts +5 -0
  86. package/template/apps/server/src/runtime.ts +10 -49
  87. package/template/apps/server/src/scheduler/audit-action.test.ts +49 -0
  88. package/template/apps/server/src/scheduler/audit.ts +1 -1
  89. package/template/apps/server/src/scheduler/execution-recorder.ts +10 -5
  90. package/template/apps/server/src/scheduler/recovery-service.test.ts +4 -3
  91. package/template/apps/server/src/scheduler/service-idle.test.ts +4 -3
  92. package/template/apps/server/src/scheduler/service.test.ts +1 -0
  93. package/template/apps/server/src/scheduler/service.ts +18 -21
  94. package/template/apps/server/src/scheduler/types.ts +5 -1
  95. package/template/apps/server/src/settings/drizzle-watermark-repository.integration.test.ts +4 -0
  96. package/template/apps/server/src/settings/drizzle-watermark-repository.ts +11 -3
  97. package/template/apps/server/src/settings/watermark-routes.test.ts +54 -1
  98. package/template/apps/server/src/settings/watermark-routes.ts +29 -1
  99. package/template/apps/server/src/startup-composition-validation.test.ts +35 -0
  100. package/template/apps/server/src/startup-composition-validation.ts +54 -0
  101. package/template/apps/server/src/startup-preflight.test.ts +36 -0
  102. package/template/apps/server/src/startup-preflight.ts +37 -0
  103. package/template/apps/server/src/system/audit-routes.test.ts +9 -3
  104. package/template/apps/server/src/system/department-routes.test.ts +5 -3
  105. package/template/apps/server/src/system/feature-routes.test.ts +4 -3
  106. package/template/apps/server/src/system/interface-catalog.test.ts +5 -3
  107. package/template/apps/server/src/system/interface-catalog.ts +3 -0
  108. package/template/apps/server/src/system/menu-routes.test.ts +5 -3
  109. package/template/apps/server/src/system/route-test-runtime.ts +24 -0
  110. package/template/apps/server/src/system/routes-validation.test.ts +7 -3
  111. package/template/apps/server/src/system/routes.test.ts +21 -11
  112. package/template/apps/server/src/system/routes.ts +11 -2
  113. package/template/apps/server/src/work-order/foundation-modules.test.ts +6 -4
  114. package/template/apps/server/src/work-order/routes.test.ts +8 -0
  115. package/template/apps/server/src/work-order/task.test.ts +6 -1
  116. package/template/apps/web/Dockerfile +5 -0
  117. package/template/apps/web/package.json +1 -0
  118. package/template/apps/web/scripts/validate-admin-ui-composition.ts +106 -0
  119. package/template/apps/web/src/app/web-composition.ts +24 -0
  120. package/template/apps/web/src/app/web-composition.webtest.ts +15 -0
  121. package/template/apps/web/src/composables/useWatermarkSettings.webtest.ts +14 -0
  122. package/template/apps/web/src/composables/watermark-settings.ts +40 -11
  123. package/template/apps/web/src/composables/watermark-settings.webtest.ts +19 -6
  124. package/template/apps/web/src/layout/AppSidebar.vue +8 -1
  125. package/template/apps/web/src/layout/GlobalWatermark.layering.webtest.ts +2 -0
  126. package/template/apps/web/src/layout/GlobalWatermark.vue +4 -8
  127. package/template/apps/web/src/layout/GlobalWatermark.webtest.ts +12 -5
  128. package/template/apps/web/src/layout/NavigationSearchDialog.vue +8 -1
  129. package/template/apps/web/src/layout/navigation.ts +29 -4
  130. package/template/apps/web/src/layout/navigation.webtest.ts +51 -0
  131. package/template/apps/web/src/pages/dashboard/dashboard-presenter.ts +6 -1
  132. package/template/apps/web/src/pages/settings/WatermarkPreview.vue +72 -0
  133. package/template/apps/web/src/pages/settings/WatermarkPreview.webtest.ts +55 -0
  134. package/template/apps/web/src/pages/settings/WatermarkSettingsSection.vue +80 -24
  135. package/template/apps/web/src/pages/settings/WatermarkSettingsSection.webtest.ts +115 -0
  136. package/template/apps/web/src/pages/work-orders/WorkOrderListPage.vue +202 -0
  137. package/template/apps/web/src/pages/work-orders/WorkOrderListPage.webtest.ts +51 -0
  138. package/template/apps/web/src/router/admin-extension-loader.ts +44 -0
  139. package/template/apps/web/src/router/admin-extension-loader.webtest.ts +49 -0
  140. package/template/apps/web/src/router/admin-ui-composition-plugin.ts +33 -0
  141. package/template/apps/web/src/router/admin-ui-composition-plugin.webtest.ts +20 -0
  142. package/template/apps/web/src/router/admin-ui-composition.ts +313 -0
  143. package/template/apps/web/src/router/admin-ui-composition.webtest.ts +225 -0
  144. package/template/apps/web/src/router/admin-ui-module-registry-plugin.ts +55 -0
  145. package/template/apps/web/src/router/admin-ui-module-registry-plugin.webtest.ts +46 -0
  146. package/template/apps/web/src/router/business-routes.ts +32 -45
  147. package/template/apps/web/src/router/business-routes.webtest.ts +40 -44
  148. package/template/apps/web/src/router/index.ts +64 -54
  149. package/template/apps/web/src/router/manifest-routes.ts +27 -7
  150. package/template/apps/web/src/router/manifest-routes.webtest.ts +10 -2
  151. package/template/apps/web/src/router/platform-admin-route-bindings.ts +58 -0
  152. package/template/apps/web/src/router/reserved-route-paths.ts +14 -0
  153. package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +10 -4
  154. package/template/apps/web/src/router/web-composition-host.ts +20 -0
  155. package/template/apps/web/src/services/work-order-api.ts +16 -0
  156. package/template/apps/web/src/services/work-order-api.webtest.ts +21 -0
  157. package/template/apps/web/src/virtual-admin-ui-modules.d.ts +5 -0
  158. package/template/apps/web/vite.config.ts +22 -1
  159. package/template/docs/architecture/migration-bundle.md +10 -7
  160. package/template/docs/architecture/module-manifest-composition.md +85 -3
  161. package/template/docs/architecture/search-provider-protocol.md +6 -1
  162. package/template/docs/architecture/server-module-interface.md +70 -7
  163. package/template/docs/development/business-web-routes.md +53 -50
  164. package/template/docs/development/module-lifecycle.md +48 -54
  165. package/template/docs/platform/navigation-registry.md +5 -4
  166. package/template/docs/platform/platform-shell-settings.md +15 -10
  167. package/template/docs/platform/web-system-crud.md +1 -1
  168. package/template/package.json +1 -1
  169. package/template/packages/config/package.json +1 -1
  170. package/template/packages/database/drizzle/0044_sweet_tarantula.sql +6 -0
  171. package/template/packages/database/drizzle/meta/0044_snapshot.json +8349 -0
  172. package/template/packages/database/drizzle/meta/_journal.json +7 -0
  173. package/template/packages/database/package.json +1 -1
  174. package/template/packages/database/src/schema/platform-settings.ts +3 -0
  175. package/template/packages/example-work-order/package.json +6 -0
  176. package/template/packages/example-work-order/src/admin-page.ts +78 -0
  177. package/template/packages/example-work-order/src/admin.ts +35 -0
  178. package/template/packages/example-work-order/src/manifest.test.ts +62 -5
  179. package/template/packages/example-work-order/src/manifest.ts +54 -7
  180. package/template/packages/example-work-order/src/migration-bundle.ts +8 -0
  181. package/template/packages/example-work-order/src/server-module.ts +2 -2
  182. package/template/packages/jobs/package.json +1 -1
  183. package/template/packages/modules/index.ts +4 -0
  184. package/template/packages/modules/package.json +3 -2
  185. package/template/packages/modules/src/admin-ui-entry.ts +8 -0
  186. package/template/packages/modules/src/admin-ui-manifest.test.ts +52 -0
  187. package/template/packages/modules/src/admin-ui-manifest.ts +53 -0
  188. package/template/packages/modules/src/capability-audit-validation.test.ts +127 -0
  189. package/template/packages/modules/src/capability-audit-validation.ts +38 -0
  190. package/template/packages/modules/src/capability-guard-validation.ts +148 -0
  191. package/template/packages/modules/src/capability-key-validation.test.ts +150 -0
  192. package/template/packages/modules/src/composition-fixtures.test-support.ts +7 -5
  193. package/template/packages/modules/src/composition.test.ts +74 -9
  194. package/template/packages/modules/src/composition.ts +8 -0
  195. package/template/packages/modules/src/manifest.ts +1 -0
  196. package/template/packages/modules/src/migration-bundle-validation.test.ts +23 -0
  197. package/template/packages/modules/src/module-host-range.test.ts +57 -0
  198. package/template/packages/modules/src/module-host-range.ts +125 -0
  199. package/template/packages/modules/src/project-module-assembly.test.ts +133 -0
  200. package/template/packages/modules/src/project-module-assembly.ts +126 -0
  201. package/template/packages/modules/src/system-admin-routes.ts +6 -5
  202. package/template/packages/modules/src/system-admin-ui-catalog.ts +72 -0
  203. package/template/packages/modules/src/system-agent-tools.ts +2 -2
  204. package/template/packages/modules/src/system-api-binding.ts +43 -9
  205. package/template/packages/modules/src/system-api-control-plane.ts +202 -0
  206. package/template/packages/modules/src/system-api-data-governance.ts +4 -2
  207. package/template/packages/modules/src/system-api-operational.ts +57 -8
  208. package/template/packages/modules/src/system-api-permissions.ts +115 -21
  209. package/template/packages/modules/src/system-api-watermark.ts +3 -0
  210. package/template/packages/modules/src/system-audit-actions.ts +7 -0
  211. package/template/packages/modules/src/system-data-governance.ts +5 -2
  212. package/template/packages/modules/src/system-features.ts +31 -0
  213. package/template/packages/modules/src/system-governed-export-bindings.ts +8 -4
  214. package/template/packages/modules/src/system-module-catalog.test.ts +39 -4
  215. package/template/packages/modules/src/system-module-catalog.ts +68 -7
  216. package/template/packages/modules/src/system-module-tasks.ts +8 -1
  217. package/template/packages/modules/src/system-modules-access.test.ts +4 -0
  218. package/template/packages/modules/src/system-modules.test.ts +10 -2
  219. package/template/packages/modules/src/system-modules.ts +6 -5
  220. package/template/packages/modules/src/system-permissions.ts +4 -0
  221. package/template/packages/modules/user/module.ts +19 -2
  222. package/template/packages/server-sdk/index.ts +8 -0
  223. package/template/packages/server-sdk/package.json +2 -1
  224. package/template/packages/server-sdk/src/durable-handler.ts +16 -1
  225. package/template/packages/server-sdk/src/module-adapter.test.ts +44 -0
  226. package/template/packages/server-sdk/src/module-adapter.ts +59 -0
  227. package/template/packages/server-sdk/src/route-definition.ts +3 -2
  228. package/template/packages/shared/package.json +1 -1
  229. package/template/packages/shared/src/audit.test.ts +7 -0
  230. package/template/packages/shared/src/audit.ts +16 -0
  231. package/template/packages/shared/src/migration-bundle-validation.ts +9 -11
  232. package/template/packages/shared/src/platform-settings.ts +17 -0
  233. package/template/packages/shared/src/rbac.ts +2 -0
  234. 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
+ });