name: design-page-builder description: 设计一个模块化的页面组合系统,包含组件、槽位和布局区域。支持基于块的编辑。 argument-hint: [–pattern components|slots|hybrid] [–framework blazor|react|vue] allowed-tools: Read, Glob, Grep, Task, Skill, AskUserQuestion
设计页面构建器命令
设计一个用于块状内容编辑的模块化页面组合系统。
使用方法
/cms:design-page-builder --pattern components
/cms:design-page-builder --pattern slots --framework blazor
/cms:design-page-builder --pattern hybrid
组合模式
- components: 可重用的组件库
- slots: 基于命名槽位的布局
- hybrid: 基于槽位模板内的组件
工作流程
步骤 1: 解析参数
从命令中提取模式类型和框架。
步骤 2: 收集需求
使用 AskUserQuestion 来理解:
- 编辑需要什么程度的灵活性?
- 是否有特定的布局要求?
- 组件是否应该可嵌套?
- 需要什么预览功能?
步骤 3: 调用技能
调用相关技能:
page-structure-design- 页面模板content-type-modeling- 组件内容类型
步骤 4: 设计组件库
组件定义:
components:
# 布局组件
- name: Section
category: Layout
description: 全宽区域,带有背景选项
props:
background_color: string
background_image: MediaField
padding: enum[none, small, medium, large]
width: enum[full, contained, narrow]
slots:
- name: content
allowed: [Hero, Grid, TextBlock, ImageGallery]
- name: Grid
category: Layout
description: 响应式网格容器
props:
columns: enum[2, 3, 4]
gap: enum[small, medium, large]
mobile_stack: boolean
slots:
- name: items
allowed: [Card, Feature, Testimonial]
min: 1
max: 12
# 内容组件
- name: Hero
category: Content
description: 带有标题和 CTA 的英雄横幅
props:
headline: TextField
subheadline: TextField
background_image: MediaField
background_video: MediaField
overlay_opacity: number
text_alignment: enum[left, center, right]
height: enum[small, medium, large, full]
slots:
- name: cta
allowed: [Button, ButtonGroup]
max: 1
- name: TextBlock
category: Content
description: 富文本内容块
props:
content: HtmlField
alignment: enum[left, center, right, justify]
- name: Card
category: Content
description: 带有图像和文本的内容卡片
props:
image: MediaField
title: TextField
description: TextField
link: LinkField
- name: ImageGallery
category: Media
description: 响应式图像画廊
props:
images: MediaField[]
layout: enum[grid, masonry, carousel]
columns: number
lightbox: boolean
- name: Testimonial
category: Social Proof
description: 客户推荐语
props:
quote: TextField
author: TextField
role: TextField
avatar: MediaField
rating: number
- name: CallToAction
category: Conversion
description: 带有表单或按钮的 CTA 部分
props:
headline: TextField
description: TextField
button_text: TextField
button_url: LinkField
form: ContentPickerField[Form]
步骤 5: 设计页面模板
带有槽位的模板:
templates:
- name: Homepage
description: 主要登录页面模板
slots:
- name: hero
label: 英雄区域
allowed: [Hero]
required: true
- name: featured
label: 特色内容
allowed: [Section, Grid]
max: 3
- name: main
label: 主要内容
allowed: [Section, TextBlock, ImageGallery, Grid]
- name: testimonials
label: 推荐语
allowed: [Testimonial, Grid]
- name: cta
label: 行动号召
allowed: [CallToAction]
- name: ProductLanding
description: 产品登录页面
slots:
- name: hero
allowed: [Hero]
required: true
- name: features
allowed: [Grid, Section]
- name: pricing
allowed: [PricingTable]
- name: faq
allowed: [Accordion]
- name: cta
allowed: [CallToAction]
- name: BlogPost
description: 博客文章模板
slots:
- name: header
allowed: [ArticleHeader]
required: true
- name: content
allowed: [TextBlock, ImageGallery, CodeBlock, Quote]
- name: author
allowed: [AuthorBio]
- name: related
allowed: [RelatedPosts]
步骤 6: 生成数据模型
EF Core 模型:
public class Page
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Slug { get; set; }
public string TemplateId { get; set; }
public PageStatus Status { get; set; }
// JSON 列用于灵活内容
public PageContent Content { get; set; }
}
[Owned]
public class PageContent
{
public Dictionary<string, List<ComponentInstance>> Slots { get; set; }
}
[Owned]
public class ComponentInstance
{
public string Id { get; set; }
public string ComponentType { get; set; }
public Dictionary<string, object> Props { get; set; }
public Dictionary<string, List<ComponentInstance>> NestedSlots { get; set; }
}
// DbContext 配置
modelBuilder.Entity<Page>()
.OwnsOne(p => p.Content, cb =>
{
cb.ToJson();
});
步骤 7: 生成 Blazor 组件(如果框架=blazor)
组件渲染器:
@* ComponentRenderer.razor *@
@inject IComponentRegistry Registry
@foreach (var component in Components)
{
<DynamicComponent
Type="@Registry.GetComponentType(component.ComponentType)"
Parameters="@GetParameters(component)" />
}
@code {
[Parameter] public List<ComponentInstance> Components { get; set; }
private Dictionary<string, object> GetParameters(ComponentInstance instance)
{
var parameters = new Dictionary<string, object>(instance.Props);
if (instance.NestedSlots?.Any() == true)
{
parameters["NestedSlots"] = instance.NestedSlots;
}
return parameters;
}
}
英雄组件:
@* Components/Hero.razor *@
<section class="hero @HeightClass" style="@BackgroundStyle">
@if (OverlayOpacity > 0)
{
<div class="hero-overlay" style="opacity: @OverlayOpacity"></div>
}
<div class="hero-content @AlignmentClass">
<h1>@Headline</h1>
@if (!string.IsNullOrEmpty(Subheadline))
{
<p class="hero-subheadline">@Subheadline</p>
}
@if (NestedSlots?.ContainsKey("cta") == true)
{
<div class="hero-cta">
<ComponentRenderer Components="@NestedSlots["cta"]" />
</div>
}
</div>
</section>
@code {
[Parameter] public string Headline { get; set; }
[Parameter] public string Subheadline { get; set; }
[Parameter] public string BackgroundImage { get; set; }
[Parameter] public decimal OverlayOpacity { get; set; }
[Parameter] public string TextAlignment { get; set; } = "center";
[Parameter] public string Height { get; set; } = "medium";
[Parameter] public Dictionary<string, List<ComponentInstance>> NestedSlots { get; set; }
private string HeightClass => $"hero--{Height}";
private string AlignmentClass => $"text-{TextAlignment}";
private string BackgroundStyle =>
!string.IsNullOrEmpty(BackgroundImage)
? $"background-image: url({BackgroundImage})"
: "";
}
编辑器体验
设计页面构建器编辑器:
editor:
features:
- drag_drop: true
- inline_editing: true
- live_preview: true
- responsive_preview: [mobile, tablet, desktop]
- undo_redo: true
- component_search: true
- keyboard_shortcuts: true
sidebar:
- components_panel
- settings_panel
- layers_panel
toolbar:
- save
- preview
- publish
- device_toggle
- undo
- redo
相关技能
page-structure-design- 页面模板content-type-modeling- 组件类型dynamic-schema-design- JSON 列存储