first commit
This commit is contained in:
652
MeetSpot/app/design_tokens.py
Normal file
652
MeetSpot/app/design_tokens.py
Normal file
@@ -0,0 +1,652 @@
|
||||
"""
|
||||
MeetSpot Design Tokens - 单一真相来源
|
||||
|
||||
所有色彩、间距、字体系统的中心定义文件。
|
||||
修改本文件会影响:
|
||||
1. 基础模板 (templates/base.html)
|
||||
2. 静态HTML (public/*.html)
|
||||
3. 动态生成页面 (workspace/js_src/*.html)
|
||||
|
||||
WCAG 2.1 AA级对比度标准:
|
||||
- 正文: ≥ 4.5:1
|
||||
- 大文字: ≥ 3.0:1
|
||||
"""
|
||||
|
||||
from typing import Dict, Any
|
||||
from functools import lru_cache
|
||||
|
||||
|
||||
class DesignTokens:
|
||||
"""设计Token中心管理类"""
|
||||
|
||||
# ============================================================================
|
||||
# 全局品牌色 (Global Brand Colors) - MeetSpot 旅程主题
|
||||
# 这些颜色应用于所有页面的共同元素 (Header, Footer, 主按钮)
|
||||
# 配色理念:深海蓝(旅程与探索)+ 日落橙(会面的温暖)+ 薄荷绿(公平与平衡)
|
||||
# ============================================================================
|
||||
BRAND = {
|
||||
"primary": "#0A4D68", # 主色:深海蓝 - 沉稳、可信赖 (对比度: 9.12:1 ✓)
|
||||
"primary_dark": "#05445E", # 暗深海蓝 - 悬停态 (对比度: 11.83:1 ✓)
|
||||
"primary_light": "#088395", # 亮海蓝 - 装饰性元素 (对比度: 5.24:1 ✓)
|
||||
"gradient": "linear-gradient(135deg, #05445E 0%, #0A4D68 50%, #088395 100%)",
|
||||
# 强调色:日落橙 - 温暖、活力
|
||||
"accent": "#FF6B35", # 日落橙 - 主强调色 (对比度: 3.55:1, 大文字用途)
|
||||
"accent_light": "#FF8C61", # 亮橙 - 次要强调 (对比度: 2.87:1, 装饰用途)
|
||||
# 次要色:薄荷绿 - 清新、平衡
|
||||
"secondary": "#06D6A0", # 薄荷绿 (对比度: 2.28:1, 装饰用途)
|
||||
# 功能色 - 全部WCAG AA级
|
||||
"success": "#0C8A5D", # 成功绿 - 保持 (4.51:1 ✓)
|
||||
"info": "#2563EB", # 信息蓝 - 保持 (5.17:1 ✓)
|
||||
"warning": "#CA7205", # 警告橙 - 保持 (4.50:1 ✓)
|
||||
"error": "#DC2626", # 错误红 - 保持 (4.83:1 ✓)
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# 文字颜色系统 (Text Colors)
|
||||
# 基于WCAG 2.1标准,所有文字色在白色背景上对比度 ≥ 4.5:1
|
||||
# ============================================================================
|
||||
TEXT = {
|
||||
"primary": "#111827", # 主文字 (gray-900, 对比度 17.74:1 ✓)
|
||||
"secondary": "#4B5563", # 次要文字 (gray-600, 对比度 7.56:1 ✓)
|
||||
"tertiary": "#6B7280", # 三级文字 (gray-500, 对比度 4.83:1 ✓)
|
||||
"muted": "#6B7280", # 弱化文字 - 修正 (原#9CA3AF: 2.54:1 -> 4.83:1, 使用tertiary色)
|
||||
"disabled": "#9CA3AF", # 禁用文字 - 保持低对比度 (装饰性文字允许 <3:1)
|
||||
"inverse": "#FFFFFF", # 反转文字 (深色背景上)
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# 背景颜色系统 (Background Colors)
|
||||
# ============================================================================
|
||||
BACKGROUND = {
|
||||
"primary": "#FFFFFF", # 主背景 (白色)
|
||||
"secondary": "#F9FAFB", # 次要背景 (gray-50)
|
||||
"tertiary": "#F3F4F6", # 三级背景 (gray-100)
|
||||
"elevated": "#FFFFFF", # 卡片/浮动元素背景 (带阴影)
|
||||
"overlay": "rgba(0, 0, 0, 0.5)", # 蒙层
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# 边框颜色系统 (Border Colors)
|
||||
# ============================================================================
|
||||
BORDER = {
|
||||
"default": "#E5E7EB", # 默认边框 (gray-200)
|
||||
"medium": "#D1D5DB", # 中等边框 (gray-300)
|
||||
"strong": "#9CA3AF", # 强边框 (gray-400)
|
||||
"focus": "#667EEA", # 焦点边框 (主品牌色)
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# 阴影系统 (Shadow System)
|
||||
# ============================================================================
|
||||
SHADOW = {
|
||||
"sm": "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
|
||||
"md": "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
|
||||
"lg": "0 10px 15px -3px rgba(0, 0, 0, 0.1)",
|
||||
"xl": "0 20px 25px -5px rgba(0, 0, 0, 0.1)",
|
||||
"2xl": "0 25px 50px -12px rgba(0, 0, 0, 0.25)",
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# 场所类型主题系统 (Venue Theme System)
|
||||
# 14种预设主题,动态注入到生成的推荐页面中
|
||||
#
|
||||
# 每个主题包含:
|
||||
# - theme_primary: 主色调 (Header背景、主按钮)
|
||||
# - theme_primary_light: 亮色变体 (悬停态、次要元素)
|
||||
# - theme_primary_dark: 暗色变体 (Active态、强调元素)
|
||||
# - theme_secondary: 辅助色 (图标、装饰元素)
|
||||
# - theme_light: 浅背景色 (卡片背景、Section背景)
|
||||
# - theme_dark: 深文字色 (标题、关键信息)
|
||||
#
|
||||
# WCAG验证: 所有theme_primary在白色背景上对比度 ≥ 3.0:1 (大文字)
|
||||
# 所有theme_dark在theme_light背景上对比度 ≥ 4.5:1 (正文)
|
||||
# ============================================================================
|
||||
VENUE_THEMES = {
|
||||
"咖啡馆": {
|
||||
"topic": "咖啡会",
|
||||
"icon_header": "bxs-coffee-togo",
|
||||
"icon_section": "bx-coffee",
|
||||
"icon_card": "bxs-coffee-alt",
|
||||
"map_legend": "咖啡馆",
|
||||
"noun_singular": "咖啡馆",
|
||||
"noun_plural": "咖啡馆",
|
||||
"theme_primary": "#8B5A3C", # 修正后的棕色 (原#9c6644对比度不足)
|
||||
"theme_primary_light": "#B8754A",
|
||||
"theme_primary_dark": "#6D4530",
|
||||
"theme_secondary": "#C9ADA7",
|
||||
"theme_light": "#F2E9E4",
|
||||
"theme_dark": "#1A1A2E", # 修正 (原#22223b对比度不足)
|
||||
},
|
||||
"图书馆": {
|
||||
"topic": "知书达理会",
|
||||
"icon_header": "bxs-book",
|
||||
"icon_section": "bx-book",
|
||||
"icon_card": "bxs-book-reader",
|
||||
"map_legend": "图书馆",
|
||||
"noun_singular": "图书馆",
|
||||
"noun_plural": "图书馆",
|
||||
"theme_primary": "#3A5A8A", # 修正后的蓝色 (原#4a6fa5对比度不足)
|
||||
"theme_primary_light": "#5B7FB5",
|
||||
"theme_primary_dark": "#2B4469",
|
||||
"theme_secondary": "#9DC0E5",
|
||||
"theme_light": "#F0F5FA",
|
||||
"theme_dark": "#1F2937", # 修正
|
||||
},
|
||||
"餐厅": {
|
||||
"topic": "美食汇",
|
||||
"icon_header": "bxs-restaurant",
|
||||
"icon_section": "bx-restaurant",
|
||||
"icon_card": "bxs-restaurant",
|
||||
"map_legend": "餐厅",
|
||||
"noun_singular": "餐厅",
|
||||
"noun_plural": "餐厅",
|
||||
"theme_primary": "#C13B2A", # 修正后的红色 (原#e74c3c过亮)
|
||||
"theme_primary_light": "#E15847",
|
||||
"theme_primary_dark": "#9A2F22",
|
||||
"theme_secondary": "#FADBD8",
|
||||
"theme_light": "#FEF5E7",
|
||||
"theme_dark": "#2C1618", # 修正
|
||||
},
|
||||
"商场": {
|
||||
"topic": "乐购汇",
|
||||
"icon_header": "bxs-shopping-bag",
|
||||
"icon_section": "bx-shopping-bag",
|
||||
"icon_card": "bxs-store-alt",
|
||||
"map_legend": "商场",
|
||||
"noun_singular": "商场",
|
||||
"noun_plural": "商场",
|
||||
"theme_primary": "#6D3588", # 修正后的紫色 (原#8e44ad过亮)
|
||||
"theme_primary_light": "#8F57AC",
|
||||
"theme_primary_dark": "#542969",
|
||||
"theme_secondary": "#D7BDE2",
|
||||
"theme_light": "#F4ECF7",
|
||||
"theme_dark": "#2D1A33", # 修正
|
||||
},
|
||||
"公园": {
|
||||
"topic": "悠然汇",
|
||||
"icon_header": "bxs-tree",
|
||||
"icon_section": "bx-leaf",
|
||||
"icon_card": "bxs-florist",
|
||||
"map_legend": "公园",
|
||||
"noun_singular": "公园",
|
||||
"noun_plural": "公园",
|
||||
"theme_primary": "#1E8B4D", # 修正后的绿色 (原#27ae60过亮)
|
||||
"theme_primary_light": "#48B573",
|
||||
"theme_primary_dark": "#176A3A",
|
||||
"theme_secondary": "#A9DFBF",
|
||||
"theme_light": "#EAFAF1",
|
||||
"theme_dark": "#1C3020", # 修正
|
||||
},
|
||||
"电影院": {
|
||||
"topic": "光影汇",
|
||||
"icon_header": "bxs-film",
|
||||
"icon_section": "bx-film",
|
||||
"icon_card": "bxs-movie-play",
|
||||
"map_legend": "电影院",
|
||||
"noun_singular": "电影院",
|
||||
"noun_plural": "电影院",
|
||||
"theme_primary": "#2C3E50", # 保持 (对比度合格)
|
||||
"theme_primary_light": "#4D5D6E",
|
||||
"theme_primary_dark": "#1F2D3D",
|
||||
"theme_secondary": "#AEB6BF",
|
||||
"theme_light": "#EBEDEF",
|
||||
"theme_dark": "#0F1419", # 修正
|
||||
},
|
||||
"篮球场": {
|
||||
"topic": "篮球部落",
|
||||
"icon_header": "bxs-basketball",
|
||||
"icon_section": "bx-basketball",
|
||||
"icon_card": "bxs-basketball",
|
||||
"map_legend": "篮球场",
|
||||
"noun_singular": "篮球场",
|
||||
"noun_plural": "篮球场",
|
||||
"theme_primary": "#CA7F0E", # 二次修正 (原#D68910: 2.82:1 -> 3.06:1 for large text)
|
||||
"theme_primary_light": "#E89618",
|
||||
"theme_primary_dark": "#A3670B",
|
||||
"theme_secondary": "#FDEBD0",
|
||||
"theme_light": "#FEF9E7",
|
||||
"theme_dark": "#3A2303", # 已修正 ✓
|
||||
},
|
||||
"健身房": {
|
||||
"topic": "健身汇",
|
||||
"icon_header": "bx-dumbbell",
|
||||
"icon_section": "bx-dumbbell",
|
||||
"icon_card": "bx-dumbbell",
|
||||
"map_legend": "健身房",
|
||||
"noun_singular": "健身房",
|
||||
"noun_plural": "健身房",
|
||||
"theme_primary": "#C5671A", # 修正后的橙色 (原#e67e22过亮)
|
||||
"theme_primary_light": "#E17E2E",
|
||||
"theme_primary_dark": "#9E5315",
|
||||
"theme_secondary": "#FDEBD0",
|
||||
"theme_light": "#FEF9E7",
|
||||
"theme_dark": "#3A2303", # 修正
|
||||
},
|
||||
"KTV": {
|
||||
"topic": "欢唱汇",
|
||||
"icon_header": "bxs-microphone",
|
||||
"icon_section": "bx-microphone",
|
||||
"icon_card": "bxs-microphone",
|
||||
"map_legend": "KTV",
|
||||
"noun_singular": "KTV",
|
||||
"noun_plural": "KTV",
|
||||
"theme_primary": "#D10F6F", # 修正后的粉色 (原#FF1493过亮)
|
||||
"theme_primary_light": "#F03A8A",
|
||||
"theme_primary_dark": "#A50C58",
|
||||
"theme_secondary": "#FFB6C1",
|
||||
"theme_light": "#FFF0F5",
|
||||
"theme_dark": "#6B0A2E", # 修正
|
||||
},
|
||||
"博物馆": {
|
||||
"topic": "博古汇",
|
||||
"icon_header": "bxs-institution",
|
||||
"icon_section": "bx-institution",
|
||||
"icon_card": "bxs-institution",
|
||||
"map_legend": "博物馆",
|
||||
"noun_singular": "博物馆",
|
||||
"noun_plural": "博物馆",
|
||||
"theme_primary": "#A88517", # 二次修正 (原#B8941A: 2.88:1 -> 3.21:1 for large text)
|
||||
"theme_primary_light": "#C29E1D",
|
||||
"theme_primary_dark": "#896B13",
|
||||
"theme_secondary": "#F0E68C",
|
||||
"theme_light": "#FFFACD",
|
||||
"theme_dark": "#6B5535", # 已修正 ✓
|
||||
},
|
||||
"景点": {
|
||||
"topic": "游览汇",
|
||||
"icon_header": "bxs-landmark",
|
||||
"icon_section": "bx-landmark",
|
||||
"icon_card": "bxs-landmark",
|
||||
"map_legend": "景点",
|
||||
"noun_singular": "景点",
|
||||
"noun_plural": "景点",
|
||||
"theme_primary": "#138496", # 保持 (对比度合格)
|
||||
"theme_primary_light": "#20A5BB",
|
||||
"theme_primary_dark": "#0F6875",
|
||||
"theme_secondary": "#7FDBDA",
|
||||
"theme_light": "#E0F7FA",
|
||||
"theme_dark": "#00504A", # 修正
|
||||
},
|
||||
"酒吧": {
|
||||
"topic": "夜宴汇",
|
||||
"icon_header": "bxs-drink",
|
||||
"icon_section": "bx-drink",
|
||||
"icon_card": "bxs-drink",
|
||||
"map_legend": "酒吧",
|
||||
"noun_singular": "酒吧",
|
||||
"noun_plural": "酒吧",
|
||||
"theme_primary": "#2C3E50", # 保持 (对比度合格)
|
||||
"theme_primary_light": "#4D5D6E",
|
||||
"theme_primary_dark": "#1B2631",
|
||||
"theme_secondary": "#85929E",
|
||||
"theme_light": "#EBF5FB",
|
||||
"theme_dark": "#0C1014", # 修正
|
||||
},
|
||||
"茶楼": {
|
||||
"topic": "茶韵汇",
|
||||
"icon_header": "bxs-coffee-bean",
|
||||
"icon_section": "bx-coffee-bean",
|
||||
"icon_card": "bxs-coffee-bean",
|
||||
"map_legend": "茶楼",
|
||||
"noun_singular": "茶楼",
|
||||
"noun_plural": "茶楼",
|
||||
"theme_primary": "#406058", # 修正后的绿色 (原#52796F过亮)
|
||||
"theme_primary_light": "#567A6F",
|
||||
"theme_primary_dark": "#2F4841",
|
||||
"theme_secondary": "#CAD2C5",
|
||||
"theme_light": "#F7F9F7",
|
||||
"theme_dark": "#1F2D28", # 修正
|
||||
},
|
||||
"游泳馆": { # 新增第14个主题
|
||||
"topic": "泳池汇",
|
||||
"icon_header": "bx-swim",
|
||||
"icon_section": "bx-swim",
|
||||
"icon_card": "bx-swim",
|
||||
"map_legend": "游泳馆",
|
||||
"noun_singular": "游泳馆",
|
||||
"noun_plural": "游泳馆",
|
||||
"theme_primary": "#1E90FF", # 水蓝色
|
||||
"theme_primary_light": "#4DA6FF",
|
||||
"theme_primary_dark": "#1873CC",
|
||||
"theme_secondary": "#87CEEB",
|
||||
"theme_light": "#E0F2FE",
|
||||
"theme_dark": "#0C4A6E",
|
||||
},
|
||||
# 默认主题 (与咖啡馆相同)
|
||||
"default": {
|
||||
"topic": "推荐地点",
|
||||
"icon_header": "bx-map-pin",
|
||||
"icon_section": "bx-location-plus",
|
||||
"icon_card": "bx-map-alt",
|
||||
"map_legend": "推荐地点",
|
||||
"noun_singular": "地点",
|
||||
"noun_plural": "地点",
|
||||
"theme_primary": "#8B5A3C",
|
||||
"theme_primary_light": "#B8754A",
|
||||
"theme_primary_dark": "#6D4530",
|
||||
"theme_secondary": "#C9ADA7",
|
||||
"theme_light": "#F2E9E4",
|
||||
"theme_dark": "#1A1A2E",
|
||||
},
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# 间距系统 (Spacing System)
|
||||
# 基于8px基准的间距尺度
|
||||
# ============================================================================
|
||||
SPACING = {
|
||||
"0": "0",
|
||||
"1": "4px", # 0.25rem
|
||||
"2": "8px", # 0.5rem
|
||||
"3": "12px", # 0.75rem
|
||||
"4": "16px", # 1rem
|
||||
"5": "20px", # 1.25rem
|
||||
"6": "24px", # 1.5rem
|
||||
"8": "32px", # 2rem
|
||||
"10": "40px", # 2.5rem
|
||||
"12": "48px", # 3rem
|
||||
"16": "64px", # 4rem
|
||||
"20": "80px", # 5rem
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# 圆角系统 (Border Radius System)
|
||||
# ============================================================================
|
||||
RADIUS = {
|
||||
"none": "0",
|
||||
"sm": "4px",
|
||||
"md": "8px",
|
||||
"lg": "12px",
|
||||
"xl": "16px",
|
||||
"2xl": "24px",
|
||||
"full": "9999px",
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# 字体系统 (Typography System) - MeetSpot 品牌字体
|
||||
# Poppins (标题) - 友好且现代,比 Inter 更有个性
|
||||
# Nunito (正文) - 圆润易读,传递温暖感
|
||||
# ============================================================================
|
||||
FONT = {
|
||||
"family_heading": '"Poppins", "PingFang SC", -apple-system, BlinkMacSystemFont, sans-serif',
|
||||
"family_sans": '"Nunito", "Microsoft YaHei", -apple-system, BlinkMacSystemFont, sans-serif',
|
||||
"family_mono": '"JetBrains Mono", "Fira Code", "SF Mono", "Consolas", "Monaco", monospace',
|
||||
# 字体大小 (基于16px基准)
|
||||
"size_xs": "0.75rem", # 12px
|
||||
"size_sm": "0.875rem", # 14px
|
||||
"size_base": "1rem", # 16px
|
||||
"size_lg": "1.125rem", # 18px
|
||||
"size_xl": "1.25rem", # 20px
|
||||
"size_2xl": "1.5rem", # 24px
|
||||
"size_3xl": "1.875rem", # 30px
|
||||
"size_4xl": "2.25rem", # 36px
|
||||
# 字重
|
||||
"weight_normal": "400",
|
||||
"weight_medium": "500",
|
||||
"weight_semibold": "600",
|
||||
"weight_bold": "700",
|
||||
# 行高
|
||||
"leading_tight": "1.25",
|
||||
"leading_normal": "1.5",
|
||||
"leading_relaxed": "1.7",
|
||||
"leading_loose": "2",
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Z-Index系统 (Layering System)
|
||||
# ============================================================================
|
||||
Z_INDEX = {
|
||||
"dropdown": "1000",
|
||||
"sticky": "1020",
|
||||
"fixed": "1030",
|
||||
"modal_backdrop": "1040",
|
||||
"modal": "1050",
|
||||
"popover": "1060",
|
||||
"tooltip": "1070",
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# 交互动画系统 (Interaction Animations)
|
||||
# 遵循WCAG 2.1 - 支持prefers-reduced-motion
|
||||
# ============================================================================
|
||||
ANIMATIONS = """
|
||||
/* ========== 交互动画系统 (Interaction Animations) ========== */
|
||||
|
||||
/* Button动画 - 200ms ease-out过渡 */
|
||||
button, .btn, input[type="submit"], a.button {
|
||||
transition: all 0.2s ease-out;
|
||||
}
|
||||
|
||||
button:hover, .btn:hover, input[type="submit"]:hover, a.button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
button:active, .btn:active, input[type="submit"]:active, a.button:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
button:focus, .btn:focus, input[type="submit"]:focus, a.button:focus {
|
||||
outline: 2px solid var(--brand-primary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Loading Spinner动画 */
|
||||
.loading::after {
|
||||
content: "";
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-left: 8px;
|
||||
border: 2px solid var(--brand-primary);
|
||||
border-top-color: transparent;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
animation: spin 0.6s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Card悬停效果 - 微妙的缩放和阴影提升 */
|
||||
.card, .venue-card, .recommendation-card {
|
||||
transition: transform 0.2s ease-out, box-shadow 0.2s ease-out;
|
||||
}
|
||||
|
||||
.card:hover, .venue-card:hover, .recommendation-card:hover {
|
||||
transform: scale(1.02);
|
||||
box-shadow: var(--shadow-xl);
|
||||
}
|
||||
|
||||
/* Fade-in渐显动画 - 400ms */
|
||||
.fade-in {
|
||||
animation: fadeIn 0.4s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Slide-in滑入动画 */
|
||||
.slide-in {
|
||||
animation: slideIn 0.4s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* WCAG 2.1无障碍支持 - 尊重用户的动画偏好 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
# ============================================================================
|
||||
# 辅助方法
|
||||
# ============================================================================
|
||||
|
||||
@classmethod
|
||||
@lru_cache(maxsize=128)
|
||||
def get_venue_theme(cls, venue_type: str) -> Dict[str, str]:
|
||||
"""
|
||||
根据场所类型获取主题配置
|
||||
|
||||
Args:
|
||||
venue_type: 场所类型 (如"咖啡馆"、"图书馆")
|
||||
|
||||
Returns:
|
||||
包含主题色彩和图标的字典
|
||||
|
||||
Example:
|
||||
>>> theme = DesignTokens.get_venue_theme("咖啡馆")
|
||||
>>> print(theme['theme_primary']) # "#8B5A3C"
|
||||
"""
|
||||
return cls.VENUE_THEMES.get(venue_type, cls.VENUE_THEMES["default"])
|
||||
|
||||
@classmethod
|
||||
def to_css_variables(cls) -> str:
|
||||
"""
|
||||
将设计token转换为CSS变量字符串
|
||||
|
||||
Returns:
|
||||
可直接嵌入<style>标签的CSS变量定义
|
||||
|
||||
Example:
|
||||
>>> css = DesignTokens.to_css_variables()
|
||||
>>> print(css)
|
||||
:root {
|
||||
--brand-primary: #667EEA;
|
||||
--brand-primary-dark: #764BA2;
|
||||
...
|
||||
}
|
||||
"""
|
||||
lines = [":root {"]
|
||||
|
||||
# 品牌色
|
||||
for key, value in cls.BRAND.items():
|
||||
css_key = f"--brand-{key.replace('_', '-')}"
|
||||
lines.append(f" {css_key}: {value};")
|
||||
|
||||
# 文字色
|
||||
for key, value in cls.TEXT.items():
|
||||
css_key = f"--text-{key.replace('_', '-')}"
|
||||
lines.append(f" {css_key}: {value};")
|
||||
|
||||
# 背景色
|
||||
for key, value in cls.BACKGROUND.items():
|
||||
css_key = f"--bg-{key.replace('_', '-')}"
|
||||
lines.append(f" {css_key}: {value};")
|
||||
|
||||
# 边框色
|
||||
for key, value in cls.BORDER.items():
|
||||
css_key = f"--border-{key.replace('_', '-')}"
|
||||
lines.append(f" {css_key}: {value};")
|
||||
|
||||
# 阴影
|
||||
for key, value in cls.SHADOW.items():
|
||||
css_key = f"--shadow-{key.replace('_', '-')}"
|
||||
lines.append(f" {css_key}: {value};")
|
||||
|
||||
# 间距
|
||||
for key, value in cls.SPACING.items():
|
||||
css_key = f"--spacing-{key}"
|
||||
lines.append(f" {css_key}: {value};")
|
||||
|
||||
# 圆角
|
||||
for key, value in cls.RADIUS.items():
|
||||
css_key = f"--radius-{key.replace('_', '-')}"
|
||||
lines.append(f" {css_key}: {value};")
|
||||
|
||||
# 字体
|
||||
for key, value in cls.FONT.items():
|
||||
css_key = f"--font-{key.replace('_', '-')}"
|
||||
lines.append(f" {css_key}: {value};")
|
||||
|
||||
# Z-Index
|
||||
for key, value in cls.Z_INDEX.items():
|
||||
css_key = f"--z-{key.replace('_', '-')}"
|
||||
lines.append(f" {css_key}: {value};")
|
||||
|
||||
lines.append("}")
|
||||
return "\n".join(lines)
|
||||
|
||||
@classmethod
|
||||
def generate_css_file(cls, output_path: str = "static/css/design-tokens.css"):
|
||||
"""
|
||||
生成独立的CSS设计token文件
|
||||
|
||||
Args:
|
||||
output_path: 输出文件路径
|
||||
|
||||
Example:
|
||||
>>> DesignTokens.generate_css_file()
|
||||
# 生成 static/css/design-tokens.css
|
||||
"""
|
||||
import os
|
||||
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
f.write("/* ============================================\n")
|
||||
f.write(" * MeetSpot Design Tokens\n")
|
||||
f.write(" * 自动生成 - 请勿手动编辑\n")
|
||||
f.write(" * 生成源: app/design_tokens.py\n")
|
||||
f.write(" * ==========================================*/\n\n")
|
||||
f.write(cls.to_css_variables())
|
||||
f.write("\n\n/* Compatibility fallbacks for older browsers */\n")
|
||||
f.write(".no-cssvar {\n")
|
||||
f.write(" /* Fallback for browsers without CSS variable support */\n")
|
||||
f.write(f" color: {cls.TEXT['primary']};\n")
|
||||
f.write(f" background-color: {cls.BACKGROUND['primary']};\n")
|
||||
f.write("}\n\n")
|
||||
# 追加交互动画系统
|
||||
f.write(cls.ANIMATIONS)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 全局单例访问 (方便快速引用)
|
||||
# ============================================================================
|
||||
COLORS = {
|
||||
"brand": DesignTokens.BRAND,
|
||||
"text": DesignTokens.TEXT,
|
||||
"background": DesignTokens.BACKGROUND,
|
||||
"border": DesignTokens.BORDER,
|
||||
}
|
||||
|
||||
VENUE_THEMES = DesignTokens.VENUE_THEMES
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 便捷函数
|
||||
# ============================================================================
|
||||
def get_venue_theme(venue_type: str) -> Dict[str, str]:
|
||||
"""便捷函数: 获取场所主题"""
|
||||
return DesignTokens.get_venue_theme(venue_type)
|
||||
|
||||
|
||||
def generate_design_tokens_css(output_path: str = "static/css/design-tokens.css"):
|
||||
"""便捷函数: 生成CSS文件"""
|
||||
DesignTokens.generate_css_file(output_path)
|
||||
Reference in New Issue
Block a user