name: 设计主题系统 description: 为多站点/多租户 CMS 设计 tokens 和主题化架构。包括 CSS 变量、Tailwind 和设计系统集成。 argument-hint: [–format tokens|css-vars|tailwind|all] [–multi-tenant] allowed-tools: Read, Glob, Grep, Task, Skill, AskUserQuestion
设计主题系统命令
设计一个带有设计 tokens 和多站点支持的全面主题化架构。
用法
/cms:design-theme-system --format tokens
/cms:design-theme-system --format css-vars --multi-tenant
/cms:design-theme-system --format tailwind
/cms:design-theme-system --format all
格式选项
- tokens: 设计 token JSON 模式
- css-vars: CSS 自定义属性
- tailwind: Tailwind CSS 配置
- all: 完整主题系统
工作流程
步骤 1: 解析参数
从命令中提取格式和多租户选项。
步骤 2: 收集需求
使用 AskUserQuestion 来理解:
- 需要主题化的站点/租户数量是多少?
- 需要什么级别的定制?
- 是否有品牌指南需要遵循?
- 使用什么前端框架?
步骤 3: 调用技能
调用相关技能:
design-token-management- Token 架构multi-site-theming- 多租户模式
步骤 4: 设计 Token 模式
Token 层次结构:
tokens:
# 原始 tokens(原始值)
primitive:
colors:
blue:
50: "#eff6ff"
100: "#dbeafe"
500: "#3b82f6"
600: "#2563eb"
900: "#1e3a8a"
gray:
50: "#f9fafb"
100: "#f3f4f6"
500: "#6b7280"
900: "#111827"
success: "#22c55e"
warning: "#f59e0b"
error: "#ef4444"
spacing:
0: "0"
1: "0.25rem"
2: "0.5rem"
4: "1rem"
8: "2rem"
16: "4rem"
typography:
font_families:
sans: "Inter, system-ui, sans-serif"
serif: "Merriweather, Georgia, serif"
mono: "JetBrains Mono, monospace"
font_sizes:
xs: "0.75rem"
sm: "0.875rem"
base: "1rem"
lg: "1.125rem"
xl: "1.25rem"
2xl: "1.5rem"
4xl: "2.25rem"
font_weights:
normal: 400
medium: 500
semibold: 600
bold: 700
radii:
none: "0"
sm: "0.125rem"
md: "0.375rem"
lg: "0.5rem"
full: "9999px"
# 语义 tokens(目的驱动)
semantic:
colors:
background:
primary: "{primitive.colors.gray.50}"
secondary: "{primitive.colors.gray.100}"
inverse: "{primitive.colors.gray.900}"
text:
primary: "{primitive.colors.gray.900}"
secondary: "{primitive.colors.gray.500}"
inverse: "{primitive.colors.gray.50}"
brand:
primary: "{primitive.colors.blue.600}"
primary_hover: "{primitive.colors.blue.700}"
secondary: "{primitive.colors.blue.100}"
feedback:
success: "{primitive.colors.success}"
warning: "{primitive.colors.warning}"
error: "{primitive.colors.error}"
border:
default: "{primitive.colors.gray.200}"
focus: "{primitive.colors.blue.500}"
spacing:
content_padding: "{primitive.spacing.4}"
section_gap: "{primitive.spacing.8}"
container_max: "1280px"
typography:
body:
family: "{primitive.typography.font_families.sans}"
size: "{primitive.typography.font_sizes.base}"
weight: "{primitive.typography.font_weights.normal}"
line_height: "1.5"
heading:
family: "{primitive.typography.font_families.sans}"
weight: "{primitive.typography.font_weights.bold}"
# 组件 tokens
component:
button:
primary:
background: "{semantic.colors.brand.primary}"
text: "{semantic.colors.text.inverse}"
border_radius: "{primitive.radii.md}"
padding_x: "{primitive.spacing.4}"
padding_y: "{primitive.spacing.2}"
font_weight: "{primitive.typography.font_weights.medium}"
secondary:
background: "transparent"
text: "{semantic.colors.brand.primary}"
border: "1px solid {semantic.colors.brand.primary}"
card:
background: "{semantic.colors.background.primary}"
border: "1px solid {semantic.colors.border.default}"
border_radius: "{primitive.radii.lg}"
padding: "{primitive.spacing.4}"
shadow: "0 1px 3px rgba(0,0,0,0.1)"
input:
background: "{semantic.colors.background.primary}"
border: "1px solid {semantic.colors.border.default}"
border_radius: "{primitive.radii.md}"
padding: "{primitive.spacing.2} {primitive.spacing.4}"
focus_ring: "0 0 0 2px {semantic.colors.border.focus}"
步骤 5: 生成 CSS 变量
CSS 输出:
/* 基础主题(浅色模式) */
:root {
/* 原始颜色 */
--color-blue-50: #eff6ff;
--color-blue-500: #3b82f6;
--color-blue-600: #2563eb;
--color-gray-50: #f9fafb;
--color-gray-500: #6b7280;
--color-gray-900: #111827;
/* 语义颜色 */
--color-bg-primary: var(--color-gray-50);
--color-bg-secondary: var(--color-gray-100);
--color-text-primary: var(--color-gray-900);
--color-text-secondary: var(--color-gray-500);
--color-brand-primary: var(--color-blue-600);
--color-border-default: var(--color-gray-200);
/* 排版 */
--font-family-sans: 'Inter', system-ui, sans-serif;
--font-size-base: 1rem;
--font-weight-normal: 400;
--font-weight-bold: 700;
/* 间距 */
--spacing-1: 0.25rem;
--spacing-2: 0.5rem;
--spacing-4: 1rem;
--spacing-8: 2rem;
/* 组件 tokens */
--button-primary-bg: var(--color-brand-primary);
--button-primary-text: white;
--button-radius: var(--radius-md);
--card-bg: var(--color-bg-primary);
--card-border: 1px solid var(--color-border-default);
--card-radius: var(--radius-lg);
}
/* 深色模式 */
:root[data-theme="dark"],
.dark {
--color-bg-primary: var(--color-gray-900);
--color-bg-secondary: var(--color-gray-800);
--color-text-primary: var(--color-gray-50);
--color-text-secondary: var(--color-gray-400);
--color-border-default: var(--color-gray-700);
}
/* 品牌覆盖示例 */
:root[data-brand="acme"] {
--color-brand-primary: #ff6b35;
--color-brand-primary-hover: #e85a2a;
--font-family-sans: 'Poppins', sans-serif;
}
步骤 6: 生成 Tailwind 配置
Tailwind 配置:
// tailwind.config.js
const tokens = require('./tokens.json');
module.exports = {
content: ['./src/**/*.{html,js,jsx,ts,tsx,razor}'],
theme: {
colors: {
transparent: 'transparent',
current: 'currentColor',
// 映射 CSS 变量以实现运行时主题化
brand: {
primary: 'var(--color-brand-primary)',
'primary-hover': 'var(--color-brand-primary-hover)',
secondary: 'var(--color-brand-secondary)',
},
bg: {
primary: 'var(--color-bg-primary)',
secondary: 'var(--color-bg-secondary)',
inverse: 'var(--color-bg-inverse)',
},
text: {
primary: 'var(--color-text-primary)',
secondary: 'var(--color-text-secondary)',
inverse: 'var(--color-text-inverse)',
},
border: {
DEFAULT: 'var(--color-border-default)',
focus: 'var(--color-border-focus)',
},
// 非主题颜色的静态调色板
...tokens.primitive.colors,
},
fontFamily: {
sans: 'var(--font-family-sans)',
serif: 'var(--font-family-serif)',
mono: 'var(--font-family-mono)',
},
extend: {
spacing: tokens.primitive.spacing,
borderRadius: tokens.primitive.radii,
},
},
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/typography'),
],
};
步骤 7: 多租户主题解析
主题解析服务:
public class ThemeService
{
private readonly ITenantResolver _tenantResolver;
private readonly IThemeRepository _themeRepository;
private readonly IMemoryCache _cache;
public async Task<ThemeConfiguration> GetThemeAsync()
{
var tenant = await _tenantResolver.ResolveAsync();
var cacheKey = $"theme:{tenant.Id}";
return await _cache.GetOrCreateAsync(cacheKey, async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
// 加载主题层次结构
var baseTheme = await _themeRepository.GetBaseThemeAsync();
var brandTheme = await _themeRepository.GetBrandThemeAsync(tenant.BrandId);
var siteTheme = await _themeRepository.GetSiteThemeAsync(tenant.SiteId);
// 合并主题,优先级:站点 > 品牌 > 基础
return MergeThemes(baseTheme, brandTheme, siteTheme);
});
}
public string GenerateCssVariables(ThemeConfiguration theme)
{
var sb = new StringBuilder();
sb.AppendLine(":root {");
foreach (var token in theme.FlattenTokens())
{
sb.AppendLine($" --{token.Key}: {token.Value};");
}
sb.AppendLine("}");
return sb.ToString();
}
}
主题 API 端点:
[HttpGet("theme.css")]
[ResponseCache(Duration = 300, VaryByHeader = "Host")]
public async Task<IActionResult> GetThemeCss()
{
var theme = await _themeService.GetThemeAsync();
var css = _themeService.GenerateCssVariables(theme);
return Content(css, "text/css");
}
步骤 8: 主题编辑器 UI
主题自定义 API:
theme_editor:
sections:
- name: 颜色
fields:
- key: brand_primary
label: 主品牌颜色
type: color
path: semantic.colors.brand.primary
- key: brand_secondary
label: 辅助颜色
type: color
path: semantic.colors.brand.secondary
- name: 排版
fields:
- key: font_family
label: 主要字体
type: font_picker
path: primitive.typography.font_families.sans
- key: heading_font
label: 标题字体
type: font_picker
path: semantic.typography.heading.family
- name: 布局
fields:
- key: border_radius
label: 圆角半径
type: slider
min: 0
max: 24
path: primitive.radii.md
preview:
components: [Button, Card, Input, Typography]
live_update: true
相关技能
design-token-management- Token 架构multi-site-theming- 多租户模式