create-windy 0.6.7 → 0.6.9
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 +2 -2
- package/dist/cli.js +158 -95
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/README.md +4 -2
- package/template/apps/agent-server/README.md +4 -4
- package/template/apps/server/src/data-access/drizzle-predicate.ts +16 -6
- package/template/apps/server/src/runtime-development.ts +1 -0
- package/template/apps/web/src/layout/AppSidebar.vue +1 -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/docs/architecture/ai-egress.md +1 -1
- package/template/docs/architecture/ai-runtime-readiness.md +2 -2
- package/template/docs/architecture/ai-runtime.md +15 -15
- package/template/docs/architecture/module-secret-resolution.md +4 -4
- package/template/docs/architecture/report-staging.md +1 -1
- package/template/docs/development/business-web-routes.md +3 -1
- package/template/docs/development/custom-login-page.md +3 -3
- package/template/docs/development/ui-component-gallery.md +44 -0
- package/template/docs/license/license-offline-activation-code.md +2 -2
- package/template/docs/operations/incremental-delivery.md +10 -9
- package/template/docs/platform/agent-runtime.md +4 -4
- package/template/docs/platform/document-ai-runtime.md +3 -3
- package/template/docs/platform/speech-ai-runtime.md +1 -1
- package/template/docs/platform/vision-ai-runtime.md +1 -1
- package/template/docs/product/built-in-roles.md +9 -1
- 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 +1 -1
- package/template/packages/modules/src/system-features.ts +5 -0
- package/template/packages/modules/src/system-module-catalog.ts +2 -1
- package/template/packages/modules/src/system-modules.ts +11 -0
- package/template/packages/modules/src/system-permissions.ts +2 -0
- package/template/packages/modules/src/system-ui-components.test.ts +36 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "vitest";
|
|
2
|
+
import { mount, type VueWrapper } from "@vue/test-utils";
|
|
3
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
4
|
+
import { buildComponentCatalog, componentCatalog } from "./component-catalog";
|
|
5
|
+
import UiComponentGallery from "./components/UiComponentGallery.vue";
|
|
6
|
+
|
|
7
|
+
let wrapper: VueWrapper | undefined;
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
wrapper?.unmount();
|
|
11
|
+
wrapper = undefined;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe("组件总览页", () => {
|
|
15
|
+
test("组件清单与 components/ui 实际目录保持一致", () => {
|
|
16
|
+
const componentDirectories = Object.keys(
|
|
17
|
+
import.meta.glob("../../components/ui/*/index.ts"),
|
|
18
|
+
)
|
|
19
|
+
.map((path) => path.split("/").at(-2))
|
|
20
|
+
.filter((name): name is string => Boolean(name))
|
|
21
|
+
.sort();
|
|
22
|
+
|
|
23
|
+
expect(componentCatalog.map(({ id }) => id).sort()).toEqual(
|
|
24
|
+
componentDirectories,
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("展示 components/ui 扫描到的全部组件族", () => {
|
|
29
|
+
wrapper = mountGallery();
|
|
30
|
+
|
|
31
|
+
expect(wrapper.findAll("[data-component-id]")).toHaveLength(
|
|
32
|
+
componentCatalog.length,
|
|
33
|
+
);
|
|
34
|
+
expect(
|
|
35
|
+
wrapper
|
|
36
|
+
.findAll("[data-component-id]")
|
|
37
|
+
.map((node) => node.attributes("data-component-id")),
|
|
38
|
+
).toEqual(componentCatalog.map(({ id }) => id));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("自动登记下游项目新增的组件入口与具名导出", () => {
|
|
42
|
+
const catalog = buildComponentCatalog({
|
|
43
|
+
"../../components/ui/button/index.ts": { Button: {} },
|
|
44
|
+
"../../components/ui/radio-group/index.ts": {
|
|
45
|
+
RadioGroup: {},
|
|
46
|
+
RadioGroupItem: {},
|
|
47
|
+
},
|
|
48
|
+
"../../components/ui/textarea/index.ts": { Textarea: {} },
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(
|
|
52
|
+
catalog.map(({ id, category, exports }) => ({
|
|
53
|
+
id,
|
|
54
|
+
category,
|
|
55
|
+
exports,
|
|
56
|
+
})),
|
|
57
|
+
).toEqual([
|
|
58
|
+
{ id: "button", category: "foundation", exports: ["Button"] },
|
|
59
|
+
{
|
|
60
|
+
id: "radio-group",
|
|
61
|
+
category: "project",
|
|
62
|
+
exports: ["RadioGroup", "RadioGroupItem"],
|
|
63
|
+
},
|
|
64
|
+
{ id: "textarea", category: "project", exports: ["Textarea"] },
|
|
65
|
+
]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("支持按关键词和分类筛选", async () => {
|
|
69
|
+
wrapper = mountGallery();
|
|
70
|
+
|
|
71
|
+
await wrapper.get('input[aria-label="搜索组件"]').setValue("消息通知");
|
|
72
|
+
expect(wrapper.findAll("[data-component-id]")).toHaveLength(1);
|
|
73
|
+
expect(
|
|
74
|
+
wrapper.get("[data-component-id]").attributes("data-component-id"),
|
|
75
|
+
).toBe("sonner");
|
|
76
|
+
|
|
77
|
+
await wrapper.get('input[aria-label="搜索组件"]').setValue("");
|
|
78
|
+
await wrapper.get('[data-category-id="foundation"]').trigger("click");
|
|
79
|
+
expect(wrapper.findAll("[data-component-id]")).toHaveLength(7);
|
|
80
|
+
expect(wrapper.text()).toContain(
|
|
81
|
+
`当前显示 7 / ${componentCatalog.length} 个组件族`,
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
function mountGallery() {
|
|
87
|
+
return mount(
|
|
88
|
+
{
|
|
89
|
+
components: { TooltipProvider, UiComponentGallery },
|
|
90
|
+
template: "<TooltipProvider><UiComponentGallery /></TooltipProvider>",
|
|
91
|
+
},
|
|
92
|
+
{ attachTo: document.body },
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
export type ComponentCategoryId =
|
|
2
|
+
| "foundation"
|
|
3
|
+
| "navigation"
|
|
4
|
+
| "feedback"
|
|
5
|
+
| "data"
|
|
6
|
+
| "project";
|
|
7
|
+
|
|
8
|
+
export type ComponentId = string;
|
|
9
|
+
|
|
10
|
+
export interface ComponentCategory {
|
|
11
|
+
id: ComponentCategoryId;
|
|
12
|
+
label: string;
|
|
13
|
+
description: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ComponentCatalogItem {
|
|
17
|
+
id: ComponentId;
|
|
18
|
+
name: string;
|
|
19
|
+
label: string;
|
|
20
|
+
description: string;
|
|
21
|
+
category: ComponentCategoryId;
|
|
22
|
+
exportPath: string;
|
|
23
|
+
exports: readonly string[];
|
|
24
|
+
keywords: readonly string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const componentCategories: readonly ComponentCategory[] = [
|
|
28
|
+
{
|
|
29
|
+
id: "foundation",
|
|
30
|
+
label: "基础交互",
|
|
31
|
+
description: "输入、选择、状态与内容骨架。",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "navigation",
|
|
35
|
+
label: "导航组织",
|
|
36
|
+
description: "路径、页签、菜单与应用侧栏。",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "feedback",
|
|
40
|
+
label: "反馈浮层",
|
|
41
|
+
description: "提示、确认、抽屉与即时反馈。",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "data",
|
|
45
|
+
label: "数据呈现",
|
|
46
|
+
description: "卡片、选项与结构化表格。",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: "project",
|
|
50
|
+
label: "项目扩展",
|
|
51
|
+
description: "当前项目在 components/ui 中新增的组件封装。",
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
const knownComponentCatalog: readonly ComponentCatalogItem[] = [
|
|
56
|
+
item("button", "Button", "按钮", "触发主要、次要与危险操作。", "foundation", [
|
|
57
|
+
"操作",
|
|
58
|
+
"动作",
|
|
59
|
+
]),
|
|
60
|
+
item("badge", "Badge", "徽标", "标记状态、类别与轻量属性。", "foundation", [
|
|
61
|
+
"标签",
|
|
62
|
+
"状态",
|
|
63
|
+
]),
|
|
64
|
+
item("input", "Input", "输入框", "采集单行文本与搜索条件。", "foundation", [
|
|
65
|
+
"表单",
|
|
66
|
+
"搜索",
|
|
67
|
+
]),
|
|
68
|
+
item(
|
|
69
|
+
"checkbox",
|
|
70
|
+
"Checkbox",
|
|
71
|
+
"复选框",
|
|
72
|
+
"选择一个或多个独立选项。",
|
|
73
|
+
"foundation",
|
|
74
|
+
["表单", "多选"],
|
|
75
|
+
),
|
|
76
|
+
item("switch", "Switch", "开关", "即时切换布尔配置状态。", "foundation", [
|
|
77
|
+
"表单",
|
|
78
|
+
"启用",
|
|
79
|
+
]),
|
|
80
|
+
item(
|
|
81
|
+
"separator",
|
|
82
|
+
"Separator",
|
|
83
|
+
"分隔线",
|
|
84
|
+
"区分同一表面中的内容层级。",
|
|
85
|
+
"foundation",
|
|
86
|
+
["布局", "边界"],
|
|
87
|
+
),
|
|
88
|
+
item(
|
|
89
|
+
"skeleton",
|
|
90
|
+
"Skeleton",
|
|
91
|
+
"骨架屏",
|
|
92
|
+
"数据加载期间保留内容结构。",
|
|
93
|
+
"foundation",
|
|
94
|
+
["加载", "占位"],
|
|
95
|
+
),
|
|
96
|
+
item(
|
|
97
|
+
"breadcrumb",
|
|
98
|
+
"Breadcrumb",
|
|
99
|
+
"面包屑",
|
|
100
|
+
"表达当前位置与上级路径。",
|
|
101
|
+
"navigation",
|
|
102
|
+
["路径", "层级"],
|
|
103
|
+
),
|
|
104
|
+
item("tabs", "Tabs", "页签", "在同一页面切换并列内容。", "navigation", [
|
|
105
|
+
"切换",
|
|
106
|
+
"分组",
|
|
107
|
+
]),
|
|
108
|
+
item(
|
|
109
|
+
"dropdown-menu",
|
|
110
|
+
"DropdownMenu",
|
|
111
|
+
"下拉菜单",
|
|
112
|
+
"承载紧凑的操作与选项集合。",
|
|
113
|
+
"navigation",
|
|
114
|
+
["菜单", "操作"],
|
|
115
|
+
),
|
|
116
|
+
item(
|
|
117
|
+
"sidebar",
|
|
118
|
+
"Sidebar",
|
|
119
|
+
"侧边栏",
|
|
120
|
+
"组织应用级导航与折叠状态。",
|
|
121
|
+
"navigation",
|
|
122
|
+
["导航", "应用壳"],
|
|
123
|
+
),
|
|
124
|
+
item("alert", "Alert", "提示条", "在内容流中呈现重要信息。", "feedback", [
|
|
125
|
+
"警告",
|
|
126
|
+
"信息",
|
|
127
|
+
]),
|
|
128
|
+
item("dialog", "Dialog", "对话框", "聚焦需要确认或补充的任务。", "feedback", [
|
|
129
|
+
"弹窗",
|
|
130
|
+
"确认",
|
|
131
|
+
]),
|
|
132
|
+
item("sheet", "Sheet", "抽屉", "在不中断上下文时展示侧向详情。", "feedback", [
|
|
133
|
+
"侧滑",
|
|
134
|
+
"详情",
|
|
135
|
+
]),
|
|
136
|
+
item(
|
|
137
|
+
"sonner",
|
|
138
|
+
"Sonner",
|
|
139
|
+
"消息通知",
|
|
140
|
+
"反馈短时、非阻塞的操作结果。",
|
|
141
|
+
"feedback",
|
|
142
|
+
["toast", "提示"],
|
|
143
|
+
),
|
|
144
|
+
item(
|
|
145
|
+
"tooltip",
|
|
146
|
+
"Tooltip",
|
|
147
|
+
"文字提示",
|
|
148
|
+
"解释图标按钮或陌生控件。",
|
|
149
|
+
"feedback",
|
|
150
|
+
["悬浮", "说明"],
|
|
151
|
+
),
|
|
152
|
+
item("card", "Card", "卡片", "组合标题、内容与局部操作。", "data", [
|
|
153
|
+
"容器",
|
|
154
|
+
"面板",
|
|
155
|
+
]),
|
|
156
|
+
item("select", "Select", "选择器", "从有限选项中选择一个值。", "data", [
|
|
157
|
+
"表单",
|
|
158
|
+
"单选",
|
|
159
|
+
]),
|
|
160
|
+
item("table", "Table", "表格", "呈现可比较的结构化记录。", "data", [
|
|
161
|
+
"列表",
|
|
162
|
+
"数据",
|
|
163
|
+
]),
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
type DiscoveredComponentModules = Readonly<
|
|
167
|
+
Record<string, Readonly<Record<string, unknown>>>
|
|
168
|
+
>;
|
|
169
|
+
|
|
170
|
+
const discoveredComponentModules = import.meta.glob<
|
|
171
|
+
Readonly<Record<string, unknown>>
|
|
172
|
+
>("../../components/ui/*/index.ts", { eager: true });
|
|
173
|
+
|
|
174
|
+
export const componentCatalog = buildComponentCatalog(
|
|
175
|
+
discoveredComponentModules,
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
export const componentCatalogById = Object.fromEntries(
|
|
179
|
+
componentCatalog.map((component) => [component.id, component]),
|
|
180
|
+
) as Readonly<Record<ComponentId, ComponentCatalogItem>>;
|
|
181
|
+
|
|
182
|
+
function item(
|
|
183
|
+
id: ComponentId,
|
|
184
|
+
name: string,
|
|
185
|
+
label: string,
|
|
186
|
+
description: string,
|
|
187
|
+
category: ComponentCategoryId,
|
|
188
|
+
keywords: readonly string[],
|
|
189
|
+
): ComponentCatalogItem {
|
|
190
|
+
return {
|
|
191
|
+
id,
|
|
192
|
+
name,
|
|
193
|
+
label,
|
|
194
|
+
description,
|
|
195
|
+
category,
|
|
196
|
+
exportPath: `@/components/ui/${id}`,
|
|
197
|
+
exports: [name],
|
|
198
|
+
keywords,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function buildComponentCatalog(
|
|
203
|
+
modules: DiscoveredComponentModules,
|
|
204
|
+
): readonly ComponentCatalogItem[] {
|
|
205
|
+
const knownById = new Map(
|
|
206
|
+
knownComponentCatalog.map((component) => [component.id, component]),
|
|
207
|
+
);
|
|
208
|
+
return Object.entries(modules)
|
|
209
|
+
.map(([path, module]) => {
|
|
210
|
+
const id = componentIdFromPath(path);
|
|
211
|
+
const exports = Object.keys(module).sort();
|
|
212
|
+
const known = knownById.get(id);
|
|
213
|
+
if (known) return { ...known, exports };
|
|
214
|
+
return {
|
|
215
|
+
id,
|
|
216
|
+
name: exports.join(" · ") || id,
|
|
217
|
+
label: id,
|
|
218
|
+
description: exports.length
|
|
219
|
+
? `项目封装,导出 ${exports.join("、")}。`
|
|
220
|
+
: "项目封装,当前入口未声明具名导出。",
|
|
221
|
+
category: "project",
|
|
222
|
+
exportPath: `@/components/ui/${id}`,
|
|
223
|
+
exports,
|
|
224
|
+
keywords: ["项目", "扩展", ...exports],
|
|
225
|
+
} satisfies ComponentCatalogItem;
|
|
226
|
+
})
|
|
227
|
+
.sort(compareCatalogItems);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function componentIdFromPath(path: string) {
|
|
231
|
+
const id = path.split("/").at(-2);
|
|
232
|
+
if (!id) throw new Error(`无法识别组件入口:${path}`);
|
|
233
|
+
return id;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function compareCatalogItems(
|
|
237
|
+
left: ComponentCatalogItem,
|
|
238
|
+
right: ComponentCatalogItem,
|
|
239
|
+
) {
|
|
240
|
+
const leftIndex = knownComponentCatalog.findIndex(({ id }) => id === left.id);
|
|
241
|
+
const rightIndex = knownComponentCatalog.findIndex(
|
|
242
|
+
({ id }) => id === right.id,
|
|
243
|
+
);
|
|
244
|
+
if (leftIndex >= 0 && rightIndex >= 0) return leftIndex - rightIndex;
|
|
245
|
+
if (leftIndex >= 0) return -1;
|
|
246
|
+
if (rightIndex >= 0) return 1;
|
|
247
|
+
return left.id.localeCompare(right.id);
|
|
248
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { ComponentCatalogItem } from "../component-catalog";
|
|
3
|
+
|
|
4
|
+
defineProps<{
|
|
5
|
+
item: ComponentCatalogItem;
|
|
6
|
+
}>();
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<article
|
|
11
|
+
:id="`component-${item.id}`"
|
|
12
|
+
:data-component-id="item.id"
|
|
13
|
+
class="component-preview overflow-hidden rounded-xl border bg-card text-card-foreground"
|
|
14
|
+
>
|
|
15
|
+
<header class="border-b px-4 py-3">
|
|
16
|
+
<div class="flex flex-wrap items-baseline justify-between gap-2">
|
|
17
|
+
<div class="flex items-baseline gap-2">
|
|
18
|
+
<h3 class="font-semibold">{{ item.label }}</h3>
|
|
19
|
+
<code class="text-xs text-muted-foreground">{{ item.name }}</code>
|
|
20
|
+
</div>
|
|
21
|
+
<code class="text-[11px] text-muted-foreground">{{
|
|
22
|
+
item.exportPath
|
|
23
|
+
}}</code>
|
|
24
|
+
</div>
|
|
25
|
+
<p class="mt-1 text-sm leading-5 text-muted-foreground">
|
|
26
|
+
{{ item.description }}
|
|
27
|
+
</p>
|
|
28
|
+
</header>
|
|
29
|
+
<div class="preview-body min-h-32 bg-muted/20 p-5">
|
|
30
|
+
<slot />
|
|
31
|
+
</div>
|
|
32
|
+
</article>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<style scoped>
|
|
36
|
+
.component-preview {
|
|
37
|
+
container-type: inline-size;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.preview-body {
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@container (max-width: 28rem) {
|
|
46
|
+
.preview-body {
|
|
47
|
+
align-items: stretch;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
</style>
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ArrowUpRightIcon } from "@lucide/vue";
|
|
3
|
+
import { shallowRef } from "vue";
|
|
4
|
+
import { Badge } from "@/components/ui/badge";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import {
|
|
7
|
+
Card,
|
|
8
|
+
CardAction,
|
|
9
|
+
CardContent,
|
|
10
|
+
CardDescription,
|
|
11
|
+
CardFooter,
|
|
12
|
+
CardHeader,
|
|
13
|
+
CardTitle,
|
|
14
|
+
} from "@/components/ui/card";
|
|
15
|
+
import {
|
|
16
|
+
Select,
|
|
17
|
+
SelectContent,
|
|
18
|
+
SelectGroup,
|
|
19
|
+
SelectItem,
|
|
20
|
+
SelectLabel,
|
|
21
|
+
SelectSeparator,
|
|
22
|
+
SelectTrigger,
|
|
23
|
+
SelectValue,
|
|
24
|
+
} from "@/components/ui/select";
|
|
25
|
+
import {
|
|
26
|
+
Table,
|
|
27
|
+
TableBody,
|
|
28
|
+
TableCaption,
|
|
29
|
+
TableCell,
|
|
30
|
+
TableHead,
|
|
31
|
+
TableHeader,
|
|
32
|
+
TableRow,
|
|
33
|
+
} from "@/components/ui/table";
|
|
34
|
+
import { componentCatalogById, type ComponentId } from "../component-catalog";
|
|
35
|
+
import ComponentPreviewCard from "./ComponentPreviewCard.vue";
|
|
36
|
+
|
|
37
|
+
const props = defineProps<{
|
|
38
|
+
visibleIds: readonly ComponentId[];
|
|
39
|
+
}>();
|
|
40
|
+
|
|
41
|
+
const density = shallowRef("comfortable");
|
|
42
|
+
|
|
43
|
+
function isVisible(id: ComponentId) {
|
|
44
|
+
return props.visibleIds.includes(id);
|
|
45
|
+
}
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<template>
|
|
49
|
+
<ComponentPreviewCard
|
|
50
|
+
v-if="isVisible('card')"
|
|
51
|
+
:item="componentCatalogById.card"
|
|
52
|
+
>
|
|
53
|
+
<Card class="w-full max-w-sm">
|
|
54
|
+
<CardHeader>
|
|
55
|
+
<CardTitle>组件健康度</CardTitle>
|
|
56
|
+
<CardDescription>最近一次静态检查已通过。</CardDescription>
|
|
57
|
+
<CardAction><Badge variant="outline">稳定</Badge></CardAction>
|
|
58
|
+
</CardHeader>
|
|
59
|
+
<CardContent>
|
|
60
|
+
<p class="text-2xl font-semibold tabular-nums">19 / 19</p>
|
|
61
|
+
<p class="mt-1 text-xs text-muted-foreground">组件族已登记</p>
|
|
62
|
+
</CardContent>
|
|
63
|
+
<CardFooter class="justify-end">
|
|
64
|
+
<Button variant="ghost" size="sm">查看记录<ArrowUpRightIcon /></Button>
|
|
65
|
+
</CardFooter>
|
|
66
|
+
</Card>
|
|
67
|
+
</ComponentPreviewCard>
|
|
68
|
+
|
|
69
|
+
<ComponentPreviewCard
|
|
70
|
+
v-if="isVisible('select')"
|
|
71
|
+
:item="componentCatalogById.select"
|
|
72
|
+
>
|
|
73
|
+
<div class="grid gap-2 text-sm">
|
|
74
|
+
<label class="font-medium" for="component-density">预览密度</label>
|
|
75
|
+
<Select v-model="density">
|
|
76
|
+
<SelectTrigger id="component-density" class="w-52">
|
|
77
|
+
<SelectValue placeholder="选择密度" />
|
|
78
|
+
</SelectTrigger>
|
|
79
|
+
<SelectContent>
|
|
80
|
+
<SelectGroup>
|
|
81
|
+
<SelectLabel>常用</SelectLabel>
|
|
82
|
+
<SelectItem value="comfortable">舒适</SelectItem>
|
|
83
|
+
<SelectItem value="compact">紧凑</SelectItem>
|
|
84
|
+
</SelectGroup>
|
|
85
|
+
<SelectSeparator />
|
|
86
|
+
<SelectItem value="large">宽松</SelectItem>
|
|
87
|
+
</SelectContent>
|
|
88
|
+
</Select>
|
|
89
|
+
<span class="text-xs text-muted-foreground">当前值:{{ density }}</span>
|
|
90
|
+
</div>
|
|
91
|
+
</ComponentPreviewCard>
|
|
92
|
+
|
|
93
|
+
<ComponentPreviewCard
|
|
94
|
+
v-if="isVisible('table')"
|
|
95
|
+
:item="componentCatalogById.table"
|
|
96
|
+
>
|
|
97
|
+
<Table>
|
|
98
|
+
<TableCaption>基础组件登记状态</TableCaption>
|
|
99
|
+
<TableHeader>
|
|
100
|
+
<TableRow>
|
|
101
|
+
<TableHead>组件</TableHead>
|
|
102
|
+
<TableHead>分类</TableHead>
|
|
103
|
+
<TableHead class="text-right">状态</TableHead>
|
|
104
|
+
</TableRow>
|
|
105
|
+
</TableHeader>
|
|
106
|
+
<TableBody>
|
|
107
|
+
<TableRow>
|
|
108
|
+
<TableCell class="font-medium">Button</TableCell>
|
|
109
|
+
<TableCell>基础交互</TableCell>
|
|
110
|
+
<TableCell class="text-right"
|
|
111
|
+
><Badge variant="outline">稳定</Badge></TableCell
|
|
112
|
+
>
|
|
113
|
+
</TableRow>
|
|
114
|
+
<TableRow>
|
|
115
|
+
<TableCell class="font-medium">Dialog</TableCell>
|
|
116
|
+
<TableCell>反馈浮层</TableCell>
|
|
117
|
+
<TableCell class="text-right"
|
|
118
|
+
><Badge variant="outline">稳定</Badge></TableCell
|
|
119
|
+
>
|
|
120
|
+
</TableRow>
|
|
121
|
+
<TableRow>
|
|
122
|
+
<TableCell class="font-medium">Table</TableCell>
|
|
123
|
+
<TableCell>数据呈现</TableCell>
|
|
124
|
+
<TableCell class="text-right"
|
|
125
|
+
><Badge variant="outline">稳定</Badge></TableCell
|
|
126
|
+
>
|
|
127
|
+
</TableRow>
|
|
128
|
+
</TableBody>
|
|
129
|
+
</Table>
|
|
130
|
+
</ComponentPreviewCard>
|
|
131
|
+
</template>
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { BellRingIcon, InfoIcon, TriangleAlertIcon } from "@lucide/vue";
|
|
3
|
+
import {
|
|
4
|
+
Alert,
|
|
5
|
+
AlertAction,
|
|
6
|
+
AlertDescription,
|
|
7
|
+
AlertTitle,
|
|
8
|
+
} from "@/components/ui/alert";
|
|
9
|
+
import { Button } from "@/components/ui/button";
|
|
10
|
+
import {
|
|
11
|
+
Dialog,
|
|
12
|
+
DialogClose,
|
|
13
|
+
DialogContent,
|
|
14
|
+
DialogDescription,
|
|
15
|
+
DialogFooter,
|
|
16
|
+
DialogHeader,
|
|
17
|
+
DialogTitle,
|
|
18
|
+
DialogTrigger,
|
|
19
|
+
} from "@/components/ui/dialog";
|
|
20
|
+
import {
|
|
21
|
+
Sheet,
|
|
22
|
+
SheetClose,
|
|
23
|
+
SheetContent,
|
|
24
|
+
SheetDescription,
|
|
25
|
+
SheetFooter,
|
|
26
|
+
SheetHeader,
|
|
27
|
+
SheetTitle,
|
|
28
|
+
SheetTrigger,
|
|
29
|
+
} from "@/components/ui/sheet";
|
|
30
|
+
import {
|
|
31
|
+
Tooltip,
|
|
32
|
+
TooltipContent,
|
|
33
|
+
TooltipTrigger,
|
|
34
|
+
} from "@/components/ui/tooltip";
|
|
35
|
+
import { useAppToast } from "@/composables/useAppToast";
|
|
36
|
+
import { componentCatalogById, type ComponentId } from "../component-catalog";
|
|
37
|
+
import ComponentPreviewCard from "./ComponentPreviewCard.vue";
|
|
38
|
+
|
|
39
|
+
const props = defineProps<{
|
|
40
|
+
visibleIds: readonly ComponentId[];
|
|
41
|
+
}>();
|
|
42
|
+
|
|
43
|
+
const toast = useAppToast();
|
|
44
|
+
|
|
45
|
+
function isVisible(id: ComponentId) {
|
|
46
|
+
return props.visibleIds.includes(id);
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<template>
|
|
51
|
+
<ComponentPreviewCard
|
|
52
|
+
v-if="isVisible('alert')"
|
|
53
|
+
:item="componentCatalogById.alert"
|
|
54
|
+
>
|
|
55
|
+
<div class="grid w-full gap-3">
|
|
56
|
+
<Alert>
|
|
57
|
+
<InfoIcon />
|
|
58
|
+
<AlertTitle>组件目录已同步</AlertTitle>
|
|
59
|
+
<AlertDescription>当前页面覆盖 19 个基础组件族。</AlertDescription>
|
|
60
|
+
<AlertAction>
|
|
61
|
+
<Button variant="ghost" size="xs">查看</Button>
|
|
62
|
+
</AlertAction>
|
|
63
|
+
</Alert>
|
|
64
|
+
<Alert variant="destructive">
|
|
65
|
+
<TriangleAlertIcon />
|
|
66
|
+
<AlertTitle>示例加载失败</AlertTitle>
|
|
67
|
+
<AlertDescription>请检查组件导出与依赖是否完整。</AlertDescription>
|
|
68
|
+
</Alert>
|
|
69
|
+
</div>
|
|
70
|
+
</ComponentPreviewCard>
|
|
71
|
+
|
|
72
|
+
<ComponentPreviewCard
|
|
73
|
+
v-if="isVisible('dialog')"
|
|
74
|
+
:item="componentCatalogById.dialog"
|
|
75
|
+
>
|
|
76
|
+
<Dialog>
|
|
77
|
+
<DialogTrigger as-child>
|
|
78
|
+
<Button variant="outline">打开确认对话框</Button>
|
|
79
|
+
</DialogTrigger>
|
|
80
|
+
<DialogContent>
|
|
81
|
+
<DialogHeader>
|
|
82
|
+
<DialogTitle>发布组件变更?</DialogTitle>
|
|
83
|
+
<DialogDescription>
|
|
84
|
+
发布后,使用该组件的页面会在下次构建时获得更新。
|
|
85
|
+
</DialogDescription>
|
|
86
|
+
</DialogHeader>
|
|
87
|
+
<DialogFooter>
|
|
88
|
+
<DialogClose as-child>
|
|
89
|
+
<Button variant="outline">取消</Button>
|
|
90
|
+
</DialogClose>
|
|
91
|
+
<DialogClose as-child>
|
|
92
|
+
<Button>确认发布</Button>
|
|
93
|
+
</DialogClose>
|
|
94
|
+
</DialogFooter>
|
|
95
|
+
</DialogContent>
|
|
96
|
+
</Dialog>
|
|
97
|
+
</ComponentPreviewCard>
|
|
98
|
+
|
|
99
|
+
<ComponentPreviewCard
|
|
100
|
+
v-if="isVisible('sheet')"
|
|
101
|
+
:item="componentCatalogById.sheet"
|
|
102
|
+
>
|
|
103
|
+
<Sheet>
|
|
104
|
+
<SheetTrigger as-child>
|
|
105
|
+
<Button variant="outline">查看组件详情</Button>
|
|
106
|
+
</SheetTrigger>
|
|
107
|
+
<SheetContent>
|
|
108
|
+
<SheetHeader>
|
|
109
|
+
<SheetTitle>Button 组件</SheetTitle>
|
|
110
|
+
<SheetDescription>
|
|
111
|
+
用于触发明确操作,支持多种语义和尺寸。
|
|
112
|
+
</SheetDescription>
|
|
113
|
+
</SheetHeader>
|
|
114
|
+
<div class="grid gap-3 px-4 text-sm">
|
|
115
|
+
<div class="rounded-lg border p-3">
|
|
116
|
+
<span class="text-muted-foreground">导出路径</span>
|
|
117
|
+
<code class="mt-1 block">@/components/ui/button</code>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
<SheetFooter>
|
|
121
|
+
<SheetClose as-child>
|
|
122
|
+
<Button>完成</Button>
|
|
123
|
+
</SheetClose>
|
|
124
|
+
</SheetFooter>
|
|
125
|
+
</SheetContent>
|
|
126
|
+
</Sheet>
|
|
127
|
+
</ComponentPreviewCard>
|
|
128
|
+
|
|
129
|
+
<ComponentPreviewCard
|
|
130
|
+
v-if="isVisible('sonner')"
|
|
131
|
+
:item="componentCatalogById.sonner"
|
|
132
|
+
>
|
|
133
|
+
<Button
|
|
134
|
+
variant="outline"
|
|
135
|
+
@click="
|
|
136
|
+
toast.success('组件状态已更新', {
|
|
137
|
+
description: 'Toast 使用全局 Toaster 呈现。',
|
|
138
|
+
})
|
|
139
|
+
"
|
|
140
|
+
>
|
|
141
|
+
<BellRingIcon />触发消息通知
|
|
142
|
+
</Button>
|
|
143
|
+
</ComponentPreviewCard>
|
|
144
|
+
|
|
145
|
+
<ComponentPreviewCard
|
|
146
|
+
v-if="isVisible('tooltip')"
|
|
147
|
+
:item="componentCatalogById.tooltip"
|
|
148
|
+
>
|
|
149
|
+
<Tooltip>
|
|
150
|
+
<TooltipTrigger as-child>
|
|
151
|
+
<Button variant="outline" size="icon" aria-label="查看组件提示">
|
|
152
|
+
<InfoIcon />
|
|
153
|
+
</Button>
|
|
154
|
+
</TooltipTrigger>
|
|
155
|
+
<TooltipContent>这是一个零延迟文字提示</TooltipContent>
|
|
156
|
+
</Tooltip>
|
|
157
|
+
</ComponentPreviewCard>
|
|
158
|
+
</template>
|