first commit

This commit is contained in:
ytc1012
2026-02-04 16:11:55 +08:00
commit 0f3ee050dc
165 changed files with 25795 additions and 0 deletions

View File

@@ -0,0 +1,216 @@
"""
WCAG 2.1色彩对比度验证工具
验证所有设计token中的颜色组合是否符合WCAG标准:
- AA级 (正文): 对比度 ≥ 4.5:1
- AA级 (大文字): 对比度 ≥ 3.0:1
- AAA级 (正文): 对比度 ≥ 7.0:1
使用方法:
python tools/validate_colors.py
或集成到CI/CD:
pytest tests/test_accessibility.py::test_color_contrast
"""
import math
import sys
from pathlib import Path
from typing import Dict, Tuple
# 添加项目根目录到Python路径
sys.path.insert(0, str(Path(__file__).parent.parent))
from app.design_tokens import DesignTokens
def hex_to_rgb(hex_color: str) -> Tuple[int, int, int]:
"""将十六进制颜色转换为RGB"""
hex_color = hex_color.lstrip("#")
return tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4))
def relative_luminance(rgb: Tuple[int, int, int]) -> float:
"""
计算相对亮度 (WCAG公式)
https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
"""
r, g, b = [x / 255.0 for x in rgb]
def adjust(channel):
if channel <= 0.03928:
return channel / 12.92
return math.pow((channel + 0.055) / 1.055, 2.4)
r, g, b = adjust(r), adjust(g), adjust(b)
return 0.2126 * r + 0.7152 * g + 0.0722 * b
def contrast_ratio(color1: str, color2: str) -> float:
"""
计算两个颜色之间的对比度
https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
Returns:
对比度比值 (1:1 到 21:1)
"""
lum1 = relative_luminance(hex_to_rgb(color1))
lum2 = relative_luminance(hex_to_rgb(color2))
lighter = max(lum1, lum2)
darker = min(lum1, lum2)
return (lighter + 0.05) / (darker + 0.05)
def check_wcag_compliance(
foreground: str, background: str, level: str = "AA", text_size: str = "normal"
) -> Dict[str, any]:
"""
检查颜色组合是否符合WCAG标准
Args:
foreground: 前景色 (文字)
background: 背景色
level: WCAG级别 ("AA""AAA")
text_size: 文字大小 ("normal""large")
Returns:
{
"ratio": 对比度,
"passes": 是否通过,
"level": 合规级别,
"recommendation": 建议
}
"""
ratio = contrast_ratio(foreground, background)
# WCAG标准阈值
thresholds = {
"AA": {"normal": 4.5, "large": 3.0},
"AAA": {"normal": 7.0, "large": 4.5},
}
required_ratio = thresholds[level][text_size]
passes = ratio >= required_ratio
result = {
"ratio": round(ratio, 2),
"passes": passes,
"level": level,
"required": required_ratio,
"foreground": foreground,
"background": background,
}
# 生成建议
if not passes:
if ratio < required_ratio * 0.8:
result["recommendation"] = "对比度严重不足,需要更换颜色"
else:
result["recommendation"] = "对比度略低,建议微调颜色深浅"
else:
if ratio >= thresholds["AAA"][text_size]:
result["recommendation"] = "优秀符合WCAG AAA级标准"
else:
result["recommendation"] = "符合WCAG AA级标准"
return result
def validate_design_tokens():
"""验证所有设计token的色彩对比度"""
results = []
print("=" * 80)
print("MeetSpot Design Tokens - WCAG 2.1色彩对比度验证报告")
print("=" * 80)
print()
# 1. 验证品牌色在白色背景上
print("📊 品牌色 vs 白色背景")
print("-" * 80)
white_bg = DesignTokens.BACKGROUND["primary"]
for color_name, color_value in DesignTokens.BRAND.items():
if color_name == "gradient":
continue # 跳过渐变
result = check_wcag_compliance(color_value, white_bg, "AA", "normal")
results.append(result)
status = "✅ PASS" if result["passes"] else "❌ FAIL"
print(
f"{status} | {color_name:20s} | {color_value:10s} | {result['ratio']:5.2f}:1 | {result['recommendation']}"
)
print()
# 2. 验证文字色在白色背景上
print("📊 文字色 vs 白色背景")
print("-" * 80)
for color_name, color_value in DesignTokens.TEXT.items():
if color_name == "inverse":
continue # 跳过反转色
result = check_wcag_compliance(color_value, white_bg, "AA", "normal")
results.append(result)
status = "✅ PASS" if result["passes"] else "❌ FAIL"
print(
f"{status} | {color_name:20s} | {color_value:10s} | {result['ratio']:5.2f}:1 | {result['recommendation']}"
)
print()
# 3. 验证场所主题色
print("📊 场所主题色验证 (主色 vs 白色背景)")
print("-" * 80)
for venue_name, theme in DesignTokens.VENUE_THEMES.items():
if venue_name == "default":
continue
# 主色 vs 白色背景 (用于大文字/按钮)
result = check_wcag_compliance(
theme["theme_primary"], white_bg, "AA", "large" # 大文字标准 (3.0:1)
)
results.append(result)
status = "✅ PASS" if result["passes"] else "❌ FAIL"
print(
f"{status} | {venue_name:12s} | {theme['theme_primary']:10s} | {result['ratio']:5.2f}:1 | {result['recommendation']}"
)
# 深色 vs 浅色背景 (用于卡片内文字)
result_card = check_wcag_compliance(
theme["theme_dark"], theme["theme_light"], "AA", "normal"
)
results.append(result_card)
status_card = "✅ PASS" if result_card["passes"] else "❌ FAIL"
print(
f" └─ {status_card} | 卡片文字 | {theme['theme_dark']:10s} on {theme['theme_light']:10s} | {result_card['ratio']:5.2f}:1"
)
print()
print("=" * 80)
# 统计结果
total = len(results)
passed = sum(1 for r in results if r["passes"])
failed = total - passed
print(f"验证总数: {total}")
print(f"✅ 通过: {passed} ({passed/total*100:.1f}%)")
print(f"❌ 失败: {failed} ({failed/total*100:.1f}%)")
print("=" * 80)
# 返回是否全部通过
return failed == 0
if __name__ == "__main__":
all_passed = validate_design_tokens()
sys.exit(0 if all_passed else 1)