create-windy 0.6.8 → 0.6.10
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 +4 -1
- package/dist/cli.js +1012 -329
- package/package.json +1 -1
- package/template/.windy-template.json +3 -3
- package/template/README.md +8 -2
- package/template/apps/agent-server/src/southwind_agent_server/contracts.py +2 -0
- package/template/apps/agent-server/tests/test_multimodal.py +30 -0
- package/template/apps/agent-server/tests/test_postgres_store.py +1 -0
- package/template/apps/agent-server/tests/test_sqlite_store.py +1 -0
- package/template/apps/server/src/agent/remote-runtime.test.ts +1 -0
- package/template/apps/server/src/agent/run-audit.ts +1 -0
- package/template/apps/server/src/agent/run-contracts.ts +1 -0
- package/template/apps/server/src/agent/run-facade.ts +2 -2
- package/template/apps/server/src/agent/run-operation-registry.ts +7 -0
- package/template/apps/server/src/agent/run-output-schema.ts +15 -0
- package/template/apps/server/src/agent/run-release.ts +11 -0
- package/template/apps/server/src/agent/supervisor-output-schema.test.ts +95 -0
- package/template/apps/server/src/agent/vision-input.ts +37 -2
- package/template/apps/server/src/license/pairing-http.ts +138 -0
- package/template/apps/server/src/license/pairing-routes.test.ts +282 -0
- package/template/apps/server/src/license/pairing-routes.ts +257 -0
- package/template/apps/server/src/license/pairing-service.test.ts +320 -0
- package/template/apps/server/src/license/pairing-service.ts +321 -0
- package/template/apps/server/src/license/restriction.test.ts +11 -0
- package/template/apps/server/src/license/restriction.ts +5 -0
- package/template/apps/server/src/license/routes.ts +8 -1
- package/template/apps/server/src/license/runtime-service.ts +20 -0
- package/template/apps/server/src/runtime-development.ts +1 -0
- package/template/apps/web/package.json +6 -4
- package/template/apps/web/src/components/license/LicensePairingCard.vue +177 -0
- package/template/apps/web/src/components/license/LicensePairingCard.webtest.ts +113 -0
- package/template/apps/web/src/composables/useLicensePairing.ts +168 -0
- package/template/apps/web/src/composables/useLicenseRuntime.ts +11 -0
- package/template/apps/web/src/layout/AppSidebar.vue +1 -0
- package/template/apps/web/src/pages/license/LicensePage.vue +8 -0
- package/template/apps/web/src/pages/ui/UiComponentPage.vue +7 -0
- package/template/apps/web/src/pages/ui/UiComponentPage.webtest.ts +94 -0
- package/template/apps/web/src/pages/ui/component-catalog.ts +248 -0
- package/template/apps/web/src/pages/ui/components/ComponentPreviewCard.vue +50 -0
- package/template/apps/web/src/pages/ui/components/DataComponentShowcases.vue +131 -0
- package/template/apps/web/src/pages/ui/components/FeedbackComponentShowcases.vue +158 -0
- package/template/apps/web/src/pages/ui/components/FoundationComponentShowcases.vue +129 -0
- package/template/apps/web/src/pages/ui/components/NavigationComponentShowcases.vue +155 -0
- package/template/apps/web/src/pages/ui/components/ProjectComponentShowcases.vue +31 -0
- package/template/apps/web/src/pages/ui/components/UiComponentGallery.vue +238 -0
- package/template/apps/web/src/router/index.ts +2 -0
- package/template/apps/web/src/router/manifest-routes.webtest.ts +1 -0
- package/template/apps/web/src/router/platform-admin-route-bindings.ts +3 -1
- package/template/apps/web/src/services/license-api.ts +60 -0
- package/template/apps/web/src/style.webtest.ts +5 -6
- package/template/docs/architecture/ai-runtime.md +5 -0
- package/template/docs/development/business-web-routes.md +3 -1
- package/template/docs/development/ui-component-gallery.md +44 -0
- package/template/docs/license/license-pairing-activation.md +67 -0
- package/template/docs/operations/incremental-delivery.md +10 -9
- package/template/docs/platform/agent-supervisor-output-contract.md +80 -0
- package/template/packages/ai-client/package.json +1 -1
- package/template/packages/ai-client/src/agent/contracts/final-answer.test.ts +67 -0
- package/template/packages/ai-client/src/agent/contracts/final-answer.ts +53 -0
- package/template/packages/ai-client/src/agent/contracts/index.ts +1 -0
- package/template/packages/modules/index.ts +1 -0
- package/template/packages/modules/package.json +1 -1
- package/template/packages/modules/src/system-admin-routes.ts +8 -0
- package/template/packages/modules/src/system-admin-ui-catalog.ts +6 -2
- package/template/packages/modules/src/system-agent-supervisor.test.ts +48 -0
- package/template/packages/modules/src/system-agent-supervisor.ts +101 -0
- package/template/packages/modules/src/system-api-control-plane.ts +37 -0
- package/template/packages/modules/src/system-features.ts +5 -0
- package/template/packages/modules/src/system-license-pairing.test.ts +44 -0
- package/template/packages/modules/src/system-module-catalog.ts +10 -19
- package/template/packages/modules/src/system-module-menu-selection.ts +15 -0
- package/template/packages/modules/src/system-module-schema-catalog.ts +14 -0
- package/template/packages/modules/src/system-modules.ts +13 -0
- package/template/packages/modules/src/system-permissions.ts +2 -0
- package/template/packages/modules/src/system-ui-components.test.ts +36 -0
- package/template/packages/server-sdk/index.ts +5 -0
- package/template/packages/server-sdk/package.json +3 -1
- package/template/packages/server-sdk/src/ai-operation-registration.ts +4 -0
- package/template/packages/server-sdk/src/system-agent-supervisor.test.ts +87 -0
- package/template/packages/server-sdk/src/system-agent-supervisor.ts +89 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ArrowRightIcon, PlusIcon, Trash2Icon } from "@lucide/vue";
|
|
3
|
+
import { shallowRef } from "vue";
|
|
4
|
+
import { Badge } from "@/components/ui/badge";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { Checkbox } from "@/components/ui/checkbox";
|
|
7
|
+
import { Input } from "@/components/ui/input";
|
|
8
|
+
import { Separator } from "@/components/ui/separator";
|
|
9
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
10
|
+
import { Switch } from "@/components/ui/switch";
|
|
11
|
+
import { componentCatalogById, type ComponentId } from "../component-catalog";
|
|
12
|
+
import ComponentPreviewCard from "./ComponentPreviewCard.vue";
|
|
13
|
+
|
|
14
|
+
const props = defineProps<{
|
|
15
|
+
visibleIds: readonly ComponentId[];
|
|
16
|
+
}>();
|
|
17
|
+
|
|
18
|
+
const query = shallowRef("");
|
|
19
|
+
const selected = shallowRef(true);
|
|
20
|
+
const enabled = shallowRef(true);
|
|
21
|
+
|
|
22
|
+
function isVisible(id: ComponentId) {
|
|
23
|
+
return props.visibleIds.includes(id);
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
<ComponentPreviewCard
|
|
29
|
+
v-if="isVisible('button')"
|
|
30
|
+
:item="componentCatalogById.button"
|
|
31
|
+
>
|
|
32
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
33
|
+
<Button><PlusIcon />新建记录</Button>
|
|
34
|
+
<Button variant="outline">取消</Button>
|
|
35
|
+
<Button variant="secondary">保存草稿</Button>
|
|
36
|
+
<Button variant="destructive"><Trash2Icon />删除</Button>
|
|
37
|
+
<Button variant="link">查看规范<ArrowRightIcon /></Button>
|
|
38
|
+
</div>
|
|
39
|
+
</ComponentPreviewCard>
|
|
40
|
+
|
|
41
|
+
<ComponentPreviewCard
|
|
42
|
+
v-if="isVisible('badge')"
|
|
43
|
+
:item="componentCatalogById.badge"
|
|
44
|
+
>
|
|
45
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
46
|
+
<Badge>已启用</Badge>
|
|
47
|
+
<Badge variant="secondary">草稿</Badge>
|
|
48
|
+
<Badge variant="outline">待处理</Badge>
|
|
49
|
+
<Badge variant="destructive">异常</Badge>
|
|
50
|
+
</div>
|
|
51
|
+
</ComponentPreviewCard>
|
|
52
|
+
|
|
53
|
+
<ComponentPreviewCard
|
|
54
|
+
v-if="isVisible('input')"
|
|
55
|
+
:item="componentCatalogById.input"
|
|
56
|
+
>
|
|
57
|
+
<label class="grid w-full max-w-sm gap-2 text-sm font-medium">
|
|
58
|
+
搜索组件
|
|
59
|
+
<Input v-model="query" placeholder="输入名称或用途" />
|
|
60
|
+
<span class="font-normal text-muted-foreground">
|
|
61
|
+
当前输入:{{ query || "暂无" }}
|
|
62
|
+
</span>
|
|
63
|
+
</label>
|
|
64
|
+
</ComponentPreviewCard>
|
|
65
|
+
|
|
66
|
+
<ComponentPreviewCard
|
|
67
|
+
v-if="isVisible('checkbox')"
|
|
68
|
+
:item="componentCatalogById.checkbox"
|
|
69
|
+
>
|
|
70
|
+
<label class="flex items-center gap-3 text-sm">
|
|
71
|
+
<Checkbox v-model="selected" aria-label="包含已停用组件" />
|
|
72
|
+
<span>
|
|
73
|
+
<span class="block font-medium">包含已停用组件</span>
|
|
74
|
+
<span class="text-muted-foreground">
|
|
75
|
+
{{ selected ? "已包含" : "仅显示可用组件" }}
|
|
76
|
+
</span>
|
|
77
|
+
</span>
|
|
78
|
+
</label>
|
|
79
|
+
</ComponentPreviewCard>
|
|
80
|
+
|
|
81
|
+
<ComponentPreviewCard
|
|
82
|
+
v-if="isVisible('switch')"
|
|
83
|
+
:item="componentCatalogById.switch"
|
|
84
|
+
>
|
|
85
|
+
<div
|
|
86
|
+
class="flex w-full items-center justify-between gap-4 rounded-lg border bg-background p-3"
|
|
87
|
+
>
|
|
88
|
+
<div>
|
|
89
|
+
<p class="text-sm font-medium">组件预览</p>
|
|
90
|
+
<p class="text-xs text-muted-foreground">
|
|
91
|
+
{{ enabled ? "预览已启用" : "预览已暂停" }}
|
|
92
|
+
</p>
|
|
93
|
+
</div>
|
|
94
|
+
<Switch v-model="enabled" aria-label="切换组件预览" />
|
|
95
|
+
</div>
|
|
96
|
+
</ComponentPreviewCard>
|
|
97
|
+
|
|
98
|
+
<ComponentPreviewCard
|
|
99
|
+
v-if="isVisible('separator')"
|
|
100
|
+
:item="componentCatalogById.separator"
|
|
101
|
+
>
|
|
102
|
+
<div class="w-full max-w-sm text-sm">
|
|
103
|
+
<p class="font-medium">组件信息</p>
|
|
104
|
+
<p class="mt-1 text-muted-foreground">由 Windy Web 统一维护。</p>
|
|
105
|
+
<Separator class="my-4" />
|
|
106
|
+
<div class="flex h-5 items-center gap-3">
|
|
107
|
+
<span>稳定</span>
|
|
108
|
+
<Separator orientation="vertical" />
|
|
109
|
+
<span>可访问</span>
|
|
110
|
+
<Separator orientation="vertical" />
|
|
111
|
+
<span>可复用</span>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</ComponentPreviewCard>
|
|
115
|
+
|
|
116
|
+
<ComponentPreviewCard
|
|
117
|
+
v-if="isVisible('skeleton')"
|
|
118
|
+
:item="componentCatalogById.skeleton"
|
|
119
|
+
>
|
|
120
|
+
<div class="flex w-full max-w-sm items-center gap-3">
|
|
121
|
+
<Skeleton class="size-10 rounded-full" />
|
|
122
|
+
<div class="grid flex-1 gap-2">
|
|
123
|
+
<Skeleton class="h-3 w-1/3" />
|
|
124
|
+
<Skeleton class="h-3 w-full" />
|
|
125
|
+
<Skeleton class="h-3 w-4/5" />
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</ComponentPreviewCard>
|
|
129
|
+
</template>
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
BellIcon,
|
|
4
|
+
ChevronDownIcon,
|
|
5
|
+
LayoutDashboardIcon,
|
|
6
|
+
SettingsIcon,
|
|
7
|
+
} from "@lucide/vue";
|
|
8
|
+
import { shallowRef } from "vue";
|
|
9
|
+
import {
|
|
10
|
+
Breadcrumb,
|
|
11
|
+
BreadcrumbItem,
|
|
12
|
+
BreadcrumbLink,
|
|
13
|
+
BreadcrumbList,
|
|
14
|
+
BreadcrumbPage,
|
|
15
|
+
BreadcrumbSeparator,
|
|
16
|
+
} from "@/components/ui/breadcrumb";
|
|
17
|
+
import { Button } from "@/components/ui/button";
|
|
18
|
+
import {
|
|
19
|
+
DropdownMenu,
|
|
20
|
+
DropdownMenuCheckboxItem,
|
|
21
|
+
DropdownMenuContent,
|
|
22
|
+
DropdownMenuItem,
|
|
23
|
+
DropdownMenuLabel,
|
|
24
|
+
DropdownMenuSeparator,
|
|
25
|
+
DropdownMenuShortcut,
|
|
26
|
+
DropdownMenuTrigger,
|
|
27
|
+
} from "@/components/ui/dropdown-menu";
|
|
28
|
+
import {
|
|
29
|
+
SidebarGroup,
|
|
30
|
+
SidebarGroupContent,
|
|
31
|
+
SidebarGroupLabel,
|
|
32
|
+
SidebarMenu,
|
|
33
|
+
SidebarMenuBadge,
|
|
34
|
+
SidebarMenuItem,
|
|
35
|
+
} from "@/components/ui/sidebar";
|
|
36
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
37
|
+
import { componentCatalogById, type ComponentId } from "../component-catalog";
|
|
38
|
+
import ComponentPreviewCard from "./ComponentPreviewCard.vue";
|
|
39
|
+
|
|
40
|
+
const props = defineProps<{
|
|
41
|
+
visibleIds: readonly ComponentId[];
|
|
42
|
+
}>();
|
|
43
|
+
|
|
44
|
+
const compactMode = shallowRef(false);
|
|
45
|
+
|
|
46
|
+
function isVisible(id: ComponentId) {
|
|
47
|
+
return props.visibleIds.includes(id);
|
|
48
|
+
}
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<ComponentPreviewCard
|
|
53
|
+
v-if="isVisible('breadcrumb')"
|
|
54
|
+
:item="componentCatalogById.breadcrumb"
|
|
55
|
+
>
|
|
56
|
+
<Breadcrumb>
|
|
57
|
+
<BreadcrumbList>
|
|
58
|
+
<BreadcrumbItem>
|
|
59
|
+
<BreadcrumbLink href="/admin">工作台</BreadcrumbLink>
|
|
60
|
+
</BreadcrumbItem>
|
|
61
|
+
<BreadcrumbSeparator />
|
|
62
|
+
<BreadcrumbItem>
|
|
63
|
+
<BreadcrumbLink href="/ui/component">UI 组件</BreadcrumbLink>
|
|
64
|
+
</BreadcrumbItem>
|
|
65
|
+
<BreadcrumbSeparator />
|
|
66
|
+
<BreadcrumbItem>
|
|
67
|
+
<BreadcrumbPage>面包屑</BreadcrumbPage>
|
|
68
|
+
</BreadcrumbItem>
|
|
69
|
+
</BreadcrumbList>
|
|
70
|
+
</Breadcrumb>
|
|
71
|
+
</ComponentPreviewCard>
|
|
72
|
+
|
|
73
|
+
<ComponentPreviewCard
|
|
74
|
+
v-if="isVisible('tabs')"
|
|
75
|
+
:item="componentCatalogById.tabs"
|
|
76
|
+
>
|
|
77
|
+
<Tabs default-value="preview" class="w-full">
|
|
78
|
+
<TabsList aria-label="组件页签示例">
|
|
79
|
+
<TabsTrigger value="preview">预览</TabsTrigger>
|
|
80
|
+
<TabsTrigger value="usage">使用说明</TabsTrigger>
|
|
81
|
+
</TabsList>
|
|
82
|
+
<TabsContent
|
|
83
|
+
value="preview"
|
|
84
|
+
class="rounded-lg border bg-background p-3 text-sm"
|
|
85
|
+
>
|
|
86
|
+
在真实主题和间距系统中检查组件。
|
|
87
|
+
</TabsContent>
|
|
88
|
+
<TabsContent
|
|
89
|
+
value="usage"
|
|
90
|
+
class="rounded-lg border bg-background p-3 text-sm"
|
|
91
|
+
>
|
|
92
|
+
从组件目录导入,不复制基础组件源码。
|
|
93
|
+
</TabsContent>
|
|
94
|
+
</Tabs>
|
|
95
|
+
</ComponentPreviewCard>
|
|
96
|
+
|
|
97
|
+
<ComponentPreviewCard
|
|
98
|
+
v-if="isVisible('dropdown-menu')"
|
|
99
|
+
:item="componentCatalogById['dropdown-menu']"
|
|
100
|
+
>
|
|
101
|
+
<DropdownMenu>
|
|
102
|
+
<DropdownMenuTrigger as-child>
|
|
103
|
+
<Button variant="outline">页面操作<ChevronDownIcon /></Button>
|
|
104
|
+
</DropdownMenuTrigger>
|
|
105
|
+
<DropdownMenuContent align="start" class="w-52">
|
|
106
|
+
<DropdownMenuLabel>显示设置</DropdownMenuLabel>
|
|
107
|
+
<DropdownMenuCheckboxItem v-model="compactMode">
|
|
108
|
+
紧凑模式
|
|
109
|
+
</DropdownMenuCheckboxItem>
|
|
110
|
+
<DropdownMenuSeparator />
|
|
111
|
+
<DropdownMenuItem>
|
|
112
|
+
刷新组件
|
|
113
|
+
<DropdownMenuShortcut>⌘R</DropdownMenuShortcut>
|
|
114
|
+
</DropdownMenuItem>
|
|
115
|
+
</DropdownMenuContent>
|
|
116
|
+
</DropdownMenu>
|
|
117
|
+
</ComponentPreviewCard>
|
|
118
|
+
|
|
119
|
+
<ComponentPreviewCard
|
|
120
|
+
v-if="isVisible('sidebar')"
|
|
121
|
+
:item="componentCatalogById.sidebar"
|
|
122
|
+
>
|
|
123
|
+
<div
|
|
124
|
+
class="w-full max-w-xs overflow-hidden rounded-lg border bg-sidebar text-sidebar-foreground"
|
|
125
|
+
>
|
|
126
|
+
<SidebarGroup>
|
|
127
|
+
<SidebarGroupLabel>组件导航</SidebarGroupLabel>
|
|
128
|
+
<SidebarGroupContent>
|
|
129
|
+
<SidebarMenu>
|
|
130
|
+
<SidebarMenuItem>
|
|
131
|
+
<Button
|
|
132
|
+
variant="ghost"
|
|
133
|
+
class="w-full justify-start bg-sidebar-accent"
|
|
134
|
+
>
|
|
135
|
+
<LayoutDashboardIcon />总览
|
|
136
|
+
</Button>
|
|
137
|
+
</SidebarMenuItem>
|
|
138
|
+
<SidebarMenuItem>
|
|
139
|
+
<Button variant="ghost" class="w-full justify-start">
|
|
140
|
+
<SettingsIcon />表单组件
|
|
141
|
+
</Button>
|
|
142
|
+
<SidebarMenuBadge class="top-1.5">7</SidebarMenuBadge>
|
|
143
|
+
</SidebarMenuItem>
|
|
144
|
+
<SidebarMenuItem>
|
|
145
|
+
<Button variant="ghost" class="w-full justify-start">
|
|
146
|
+
<BellIcon />反馈组件
|
|
147
|
+
</Button>
|
|
148
|
+
<SidebarMenuBadge class="top-1.5">5</SidebarMenuBadge>
|
|
149
|
+
</SidebarMenuItem>
|
|
150
|
+
</SidebarMenu>
|
|
151
|
+
</SidebarGroupContent>
|
|
152
|
+
</SidebarGroup>
|
|
153
|
+
</div>
|
|
154
|
+
</ComponentPreviewCard>
|
|
155
|
+
</template>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Badge } from "@/components/ui/badge";
|
|
3
|
+
import type { ComponentCatalogItem } from "../component-catalog";
|
|
4
|
+
import ComponentPreviewCard from "./ComponentPreviewCard.vue";
|
|
5
|
+
|
|
6
|
+
defineProps<{
|
|
7
|
+
items: readonly ComponentCatalogItem[];
|
|
8
|
+
}>();
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<ComponentPreviewCard v-for="item in items" :key="item.id" :item="item">
|
|
13
|
+
<div class="grid w-full gap-3">
|
|
14
|
+
<p class="text-sm text-muted-foreground">
|
|
15
|
+
已从当前项目的组件入口自动发现。交互预览可在项目内按需补充。
|
|
16
|
+
</p>
|
|
17
|
+
<div class="flex flex-wrap gap-2">
|
|
18
|
+
<Badge
|
|
19
|
+
v-for="exportName in item.exports"
|
|
20
|
+
:key="exportName"
|
|
21
|
+
variant="outline"
|
|
22
|
+
>
|
|
23
|
+
{{ exportName }}
|
|
24
|
+
</Badge>
|
|
25
|
+
<Badge v-if="item.exports.length === 0" variant="secondary">
|
|
26
|
+
暂无具名导出
|
|
27
|
+
</Badge>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</ComponentPreviewCard>
|
|
31
|
+
</template>
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { BlocksIcon, SearchIcon, XIcon } from "@lucide/vue";
|
|
3
|
+
import { computed, shallowRef } from "vue";
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
import { Input } from "@/components/ui/input";
|
|
6
|
+
import {
|
|
7
|
+
componentCatalog,
|
|
8
|
+
componentCategories,
|
|
9
|
+
type ComponentCategoryId,
|
|
10
|
+
} from "../component-catalog";
|
|
11
|
+
import DataComponentShowcases from "./DataComponentShowcases.vue";
|
|
12
|
+
import FeedbackComponentShowcases from "./FeedbackComponentShowcases.vue";
|
|
13
|
+
import FoundationComponentShowcases from "./FoundationComponentShowcases.vue";
|
|
14
|
+
import NavigationComponentShowcases from "./NavigationComponentShowcases.vue";
|
|
15
|
+
import ProjectComponentShowcases from "./ProjectComponentShowcases.vue";
|
|
16
|
+
|
|
17
|
+
type CategoryFilter = "all" | ComponentCategoryId;
|
|
18
|
+
|
|
19
|
+
const searchQuery = shallowRef("");
|
|
20
|
+
const activeCategory = shallowRef<CategoryFilter>("all");
|
|
21
|
+
const normalizedQuery = computed(() =>
|
|
22
|
+
searchQuery.value.trim().toLocaleLowerCase(),
|
|
23
|
+
);
|
|
24
|
+
const visibleComponents = computed(() =>
|
|
25
|
+
componentCatalog.filter((component) => {
|
|
26
|
+
if (
|
|
27
|
+
activeCategory.value !== "all" &&
|
|
28
|
+
component.category !== activeCategory.value
|
|
29
|
+
) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (!normalizedQuery.value) return true;
|
|
33
|
+
return [
|
|
34
|
+
component.name,
|
|
35
|
+
component.label,
|
|
36
|
+
component.description,
|
|
37
|
+
component.exportPath,
|
|
38
|
+
...component.keywords,
|
|
39
|
+
].some((value) =>
|
|
40
|
+
value.toLocaleLowerCase().includes(normalizedQuery.value),
|
|
41
|
+
);
|
|
42
|
+
}),
|
|
43
|
+
);
|
|
44
|
+
const visibleIds = computed(() => visibleComponents.value.map(({ id }) => id));
|
|
45
|
+
const visibleProjectComponents = computed(() =>
|
|
46
|
+
visibleComponents.value.filter(({ category }) => category === "project"),
|
|
47
|
+
);
|
|
48
|
+
const visibleCategories = computed(() =>
|
|
49
|
+
componentCategories.filter((category) =>
|
|
50
|
+
visibleComponents.value.some(
|
|
51
|
+
(component) => component.category === category.id,
|
|
52
|
+
),
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
const categoryCounts = Object.fromEntries(
|
|
56
|
+
componentCategories.map((category) => [
|
|
57
|
+
category.id,
|
|
58
|
+
componentCatalog.filter((component) => component.category === category.id)
|
|
59
|
+
.length,
|
|
60
|
+
]),
|
|
61
|
+
) as Readonly<Record<ComponentCategoryId, number>>;
|
|
62
|
+
|
|
63
|
+
function selectCategory(category: CategoryFilter) {
|
|
64
|
+
activeCategory.value = category;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function clearFilters() {
|
|
68
|
+
searchQuery.value = "";
|
|
69
|
+
activeCategory.value = "all";
|
|
70
|
+
}
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<template>
|
|
74
|
+
<div class="mx-auto grid w-full max-w-7xl gap-6">
|
|
75
|
+
<header
|
|
76
|
+
class="overflow-hidden rounded-2xl border bg-card text-card-foreground"
|
|
77
|
+
>
|
|
78
|
+
<div
|
|
79
|
+
class="grid gap-6 p-6 lg:grid-cols-[minmax(0,1fr)_minmax(24rem,0.72fr)] lg:p-8"
|
|
80
|
+
>
|
|
81
|
+
<div class="max-w-2xl">
|
|
82
|
+
<div
|
|
83
|
+
class="mb-4 flex size-11 items-center justify-center rounded-xl bg-primary text-primary-foreground"
|
|
84
|
+
>
|
|
85
|
+
<BlocksIcon class="size-5" />
|
|
86
|
+
</div>
|
|
87
|
+
<p
|
|
88
|
+
class="text-xs font-semibold tracking-[0.18em] text-muted-foreground uppercase"
|
|
89
|
+
>
|
|
90
|
+
Windy UI Registry
|
|
91
|
+
</p>
|
|
92
|
+
<h1 class="mt-2 text-3xl font-semibold tracking-tight">组件总览</h1>
|
|
93
|
+
<p class="mt-3 max-w-xl text-sm leading-6 text-muted-foreground">
|
|
94
|
+
在真实主题、间距和交互上下文中检查 Web 基础组件。每个示例均直接引用
|
|
95
|
+
<code class="rounded bg-muted px-1 py-0.5">components/ui</code>
|
|
96
|
+
中的封装。
|
|
97
|
+
</p>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div class="grid content-start gap-2 rounded-xl border bg-muted/20 p-4">
|
|
101
|
+
<div class="flex items-center justify-between gap-4 border-b pb-3">
|
|
102
|
+
<span class="text-sm font-medium">注册清单</span>
|
|
103
|
+
<code class="text-xs text-muted-foreground">
|
|
104
|
+
{{ componentCatalog.length }} 组件族 ·
|
|
105
|
+
{{ componentCategories.length }} 类
|
|
106
|
+
</code>
|
|
107
|
+
</div>
|
|
108
|
+
<div
|
|
109
|
+
v-for="category in componentCategories"
|
|
110
|
+
:key="category.id"
|
|
111
|
+
class="grid grid-cols-[6rem_1fr_auto] items-center gap-3 py-1 text-xs"
|
|
112
|
+
>
|
|
113
|
+
<code class="text-muted-foreground">{{ category.id }}</code>
|
|
114
|
+
<span>{{ category.label }}</span>
|
|
115
|
+
<span class="tabular-nums text-muted-foreground">
|
|
116
|
+
{{ categoryCounts[category.id] }}
|
|
117
|
+
</span>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</header>
|
|
122
|
+
|
|
123
|
+
<section
|
|
124
|
+
aria-label="组件筛选"
|
|
125
|
+
class="sticky top-0 z-10 rounded-xl border bg-background/95 p-3 shadow-sm backdrop-blur"
|
|
126
|
+
>
|
|
127
|
+
<div
|
|
128
|
+
class="flex flex-col gap-3 xl:flex-row xl:items-center xl:justify-between"
|
|
129
|
+
>
|
|
130
|
+
<div class="relative w-full xl:max-w-sm">
|
|
131
|
+
<SearchIcon
|
|
132
|
+
class="pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground"
|
|
133
|
+
/>
|
|
134
|
+
<Input
|
|
135
|
+
v-model="searchQuery"
|
|
136
|
+
type="search"
|
|
137
|
+
aria-label="搜索组件"
|
|
138
|
+
placeholder="搜索名称、用途或导出路径"
|
|
139
|
+
class="pl-8"
|
|
140
|
+
/>
|
|
141
|
+
</div>
|
|
142
|
+
<div class="flex flex-wrap gap-2" aria-label="按分类筛选">
|
|
143
|
+
<Button
|
|
144
|
+
size="sm"
|
|
145
|
+
:variant="activeCategory === 'all' ? 'default' : 'outline'"
|
|
146
|
+
data-category-id="all"
|
|
147
|
+
@click="selectCategory('all')"
|
|
148
|
+
>
|
|
149
|
+
全部
|
|
150
|
+
<span class="tabular-nums opacity-70">{{
|
|
151
|
+
componentCatalog.length
|
|
152
|
+
}}</span>
|
|
153
|
+
</Button>
|
|
154
|
+
<Button
|
|
155
|
+
v-for="category in componentCategories"
|
|
156
|
+
:key="category.id"
|
|
157
|
+
size="sm"
|
|
158
|
+
:variant="activeCategory === category.id ? 'default' : 'outline'"
|
|
159
|
+
:data-category-id="category.id"
|
|
160
|
+
@click="selectCategory(category.id)"
|
|
161
|
+
>
|
|
162
|
+
{{ category.label }}
|
|
163
|
+
<span class="tabular-nums opacity-70">{{
|
|
164
|
+
categoryCounts[category.id]
|
|
165
|
+
}}</span>
|
|
166
|
+
</Button>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
<p class="mt-2 text-xs text-muted-foreground" aria-live="polite">
|
|
170
|
+
当前显示 {{ visibleComponents.length }} /
|
|
171
|
+
{{ componentCatalog.length }} 个组件族
|
|
172
|
+
</p>
|
|
173
|
+
</section>
|
|
174
|
+
|
|
175
|
+
<template v-if="visibleComponents.length">
|
|
176
|
+
<section
|
|
177
|
+
v-for="category in visibleCategories"
|
|
178
|
+
:key="category.id"
|
|
179
|
+
:aria-labelledby="`category-${category.id}`"
|
|
180
|
+
class="grid gap-4"
|
|
181
|
+
>
|
|
182
|
+
<header
|
|
183
|
+
class="flex flex-wrap items-end justify-between gap-2 border-b pb-3"
|
|
184
|
+
>
|
|
185
|
+
<div>
|
|
186
|
+
<p class="text-xs font-medium text-muted-foreground">
|
|
187
|
+
{{ category.id }}
|
|
188
|
+
</p>
|
|
189
|
+
<h2
|
|
190
|
+
:id="`category-${category.id}`"
|
|
191
|
+
class="mt-1 text-xl font-semibold"
|
|
192
|
+
>
|
|
193
|
+
{{ category.label }}
|
|
194
|
+
</h2>
|
|
195
|
+
</div>
|
|
196
|
+
<p class="text-sm text-muted-foreground">
|
|
197
|
+
{{ category.description }}
|
|
198
|
+
</p>
|
|
199
|
+
</header>
|
|
200
|
+
|
|
201
|
+
<div class="grid items-start gap-4 lg:grid-cols-2">
|
|
202
|
+
<FoundationComponentShowcases
|
|
203
|
+
v-if="category.id === 'foundation'"
|
|
204
|
+
:visible-ids="visibleIds"
|
|
205
|
+
/>
|
|
206
|
+
<NavigationComponentShowcases
|
|
207
|
+
v-else-if="category.id === 'navigation'"
|
|
208
|
+
:visible-ids="visibleIds"
|
|
209
|
+
/>
|
|
210
|
+
<FeedbackComponentShowcases
|
|
211
|
+
v-else-if="category.id === 'feedback'"
|
|
212
|
+
:visible-ids="visibleIds"
|
|
213
|
+
/>
|
|
214
|
+
<DataComponentShowcases
|
|
215
|
+
v-else-if="category.id === 'data'"
|
|
216
|
+
:visible-ids="visibleIds"
|
|
217
|
+
/>
|
|
218
|
+
<ProjectComponentShowcases v-else :items="visibleProjectComponents" />
|
|
219
|
+
</div>
|
|
220
|
+
</section>
|
|
221
|
+
</template>
|
|
222
|
+
|
|
223
|
+
<section
|
|
224
|
+
v-else
|
|
225
|
+
class="grid min-h-64 place-items-center rounded-xl border border-dashed p-8 text-center"
|
|
226
|
+
>
|
|
227
|
+
<div>
|
|
228
|
+
<p class="font-medium">没有匹配的组件</p>
|
|
229
|
+
<p class="mt-2 text-sm text-muted-foreground">
|
|
230
|
+
调整关键词或清除分类后重试。
|
|
231
|
+
</p>
|
|
232
|
+
<Button variant="outline" class="mt-4" @click="clearFilters">
|
|
233
|
+
<XIcon />清除筛选
|
|
234
|
+
</Button>
|
|
235
|
+
</div>
|
|
236
|
+
</section>
|
|
237
|
+
</div>
|
|
238
|
+
</template>
|
|
@@ -29,6 +29,7 @@ import SystemResourceRoutePage from "@/pages/system/SystemResourceRoutePage.vue"
|
|
|
29
29
|
import GlobalSettingsPage from "@/pages/settings/GlobalSettingsPage.vue";
|
|
30
30
|
// @windy-module system.settings end
|
|
31
31
|
import PersonalSettingsPage from "@/pages/profile/PersonalSettingsPage.vue";
|
|
32
|
+
import UiComponentPage from "@/pages/ui/UiComponentPage.vue";
|
|
32
33
|
// @windy-module system.configuration begin
|
|
33
34
|
import ModuleConfigurationPage from "@/pages/configuration/ModuleConfigurationPage.vue";
|
|
34
35
|
// @windy-module system.configuration end
|
|
@@ -84,6 +85,7 @@ const platformAdminPageComponents: Partial<
|
|
|
84
85
|
> = {
|
|
85
86
|
resource: SystemResourceRoutePage,
|
|
86
87
|
profile: PersonalSettingsPage,
|
|
88
|
+
"ui-components": UiComponentPage,
|
|
87
89
|
// @windy-module system.settings begin
|
|
88
90
|
settings: GlobalSettingsPage,
|
|
89
91
|
// @windy-module system.settings end
|
|
@@ -47,6 +47,7 @@ describe("Manifest 菜单与 Router 契约", () => {
|
|
|
47
47
|
expect(definitions.some(({ path }) => path === "/admin/profile")).toBe(
|
|
48
48
|
true,
|
|
49
49
|
);
|
|
50
|
+
expect(definitions.some(({ path }) => path === "/ui/component")).toBe(true);
|
|
50
51
|
expect(definitions.some(({ path }) => path === "/admin/work-orders")).toBe(
|
|
51
52
|
false,
|
|
52
53
|
);
|
|
@@ -8,7 +8,8 @@ export type PlatformAdminPageKey =
|
|
|
8
8
|
| "notification"
|
|
9
9
|
| "operations"
|
|
10
10
|
| "ai-runtime"
|
|
11
|
-
| "configuration"
|
|
11
|
+
| "configuration"
|
|
12
|
+
| "ui-components";
|
|
12
13
|
|
|
13
14
|
export interface PlatformAdminRouteBinding {
|
|
14
15
|
readonly routeKey: string;
|
|
@@ -37,6 +38,7 @@ export const platformAdminRouteBindings: readonly PlatformAdminRouteBinding[] =
|
|
|
37
38
|
resourceKey: "dictionaries",
|
|
38
39
|
},
|
|
39
40
|
{ routeKey: "system.feature", page: "resource", resourceKey: "features" },
|
|
41
|
+
{ routeKey: "system.ui-components", page: "ui-components" },
|
|
40
42
|
// @windy-module system.license begin
|
|
41
43
|
{
|
|
42
44
|
routeKey: "system.license",
|
|
@@ -30,6 +30,34 @@ interface LicenseInstallResponse {
|
|
|
30
30
|
snapshot: PlatformLicenseSnapshot;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
export type LicensePairingSessionStatus =
|
|
34
|
+
| "waiting"
|
|
35
|
+
| "delivered"
|
|
36
|
+
| "expired"
|
|
37
|
+
| "locked"
|
|
38
|
+
| "consumed";
|
|
39
|
+
|
|
40
|
+
export interface LicensePairingSession {
|
|
41
|
+
id: string;
|
|
42
|
+
token: string;
|
|
43
|
+
nonce: string;
|
|
44
|
+
productCode: string;
|
|
45
|
+
machineCode: string;
|
|
46
|
+
expiresAt: string;
|
|
47
|
+
qrPayload: string;
|
|
48
|
+
status: "waiting";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface LicensePairingSessionSnapshot {
|
|
52
|
+
id: string;
|
|
53
|
+
status: LicensePairingSessionStatus;
|
|
54
|
+
expiresAt: string;
|
|
55
|
+
license?: {
|
|
56
|
+
licenseVersion: { key: string; name: string };
|
|
57
|
+
expiresAt: string;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
33
61
|
export function loadPlatformLicense(): Promise<PlatformLicenseSnapshot> {
|
|
34
62
|
return apiRequest<PlatformLicenseSnapshot>("/license/runtime");
|
|
35
63
|
}
|
|
@@ -51,3 +79,35 @@ export function installPlatformActivationCode(
|
|
|
51
79
|
body: JSON.stringify({ activationCode }),
|
|
52
80
|
});
|
|
53
81
|
}
|
|
82
|
+
|
|
83
|
+
export function createPlatformLicensePairingSession(
|
|
84
|
+
callbackOrigin: string,
|
|
85
|
+
): Promise<LicensePairingSession> {
|
|
86
|
+
return apiRequest<LicensePairingSession>(
|
|
87
|
+
"/license/runtime/pairing/sessions",
|
|
88
|
+
{ method: "POST", body: JSON.stringify({ callbackOrigin }) },
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function loadPlatformLicensePairingSession(
|
|
93
|
+
sessionId: string,
|
|
94
|
+
): Promise<LicensePairingSessionSnapshot> {
|
|
95
|
+
return apiRequest<LicensePairingSessionSnapshot>(
|
|
96
|
+
`/license/runtime/pairing/sessions/${encodeURIComponent(sessionId)}`,
|
|
97
|
+
undefined,
|
|
98
|
+
{ suppressFailureHandler: true },
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function confirmPlatformLicensePairing(
|
|
103
|
+
sessionId: string,
|
|
104
|
+
pairingCode: string,
|
|
105
|
+
): Promise<LicenseInstallResponse> {
|
|
106
|
+
return apiRequest<LicenseInstallResponse>(
|
|
107
|
+
`/license/runtime/pairing/sessions/${encodeURIComponent(sessionId)}/confirm`,
|
|
108
|
+
{
|
|
109
|
+
method: "POST",
|
|
110
|
+
body: JSON.stringify({ pairingCode }),
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { describe, expect, test } from "vitest";
|
|
2
|
-
// @ts-expect-error Vitest runs this contract test in Node; the browser bundle omits Node types.
|
|
3
|
-
import { readFileSync } from "node:fs";
|
|
4
|
-
// @ts-expect-error Vitest runs this contract test in Node; the browser bundle omits Node types.
|
|
5
|
-
import { resolve } from "node:path";
|
|
1
|
+
import { describe, expect, test, vi } from "vitest";
|
|
6
2
|
|
|
7
3
|
declare const process: { cwd(): string };
|
|
8
4
|
|
|
9
|
-
const
|
|
5
|
+
const { readFileSync } = await vi.importActual<{
|
|
6
|
+
readFileSync(path: string, encoding: "utf8"): string;
|
|
7
|
+
}>("node:fs");
|
|
8
|
+
const styles = readFileSync(`${process.cwd()}/src/style.css`, "utf8");
|
|
10
9
|
|
|
11
10
|
describe("全局页面背景", () => {
|
|
12
11
|
test("html、body 与应用根节点共同使用主题背景,避免深色滚动露白", () => {
|