Files
xianyan/lib/core/registry/page_registry.dart
Developer a5b997aecb feat: 新增编辑器功能与优化
1. 添加编辑器3D场景服务接口及实现
2. 实现平台IO工具类与数据库连接
3. 新增SVG图标资源
4. 优化编辑器操作Mixin架构
5. 修复Web端兼容性问题
6. 更新依赖配置与构建脚本
7. 改进主题自适应服务
8. 添加对齐辅助线组件
9. 完善迷你文字编辑栏
10. 优化页面过渡动画
2026-04-25 09:50:30 +08:00

677 lines
18 KiB
Dart

/// ============================================================
/// 闲言APP — 页面注册表
/// 创建时间: 2026-04-20
/// 更新时间: 2026-04-20
/// 作用: 所有页面统一注册 + 合法性检测 + 主题令牌 + 描述
/// 上次更新: 初始创建 — 全页面注册+设计令牌映射+合法性校验
/// ============================================================
import '../theme/app_spacing.dart';
import '../theme/app_radius.dart';
import '../router/app_router.dart';
// ============================================================
// 页面注册条目
// ============================================================
/// 页面注册条目模型
class PageRegistryEntry {
const PageRegistryEntry({
required this.route,
required this.name,
required this.description,
required this.category,
this.isTab = false,
this.isFullscreen = false,
this.designTokens = const [],
this.status = PageStatus.active,
});
/// 路由路径
final String route;
/// 页面名称
final String name;
/// 页面描述
final String description;
/// 页面分类
final PageCategory category;
/// 是否为 Tab 页面
final bool isTab;
/// 是否全屏页面
final bool isFullscreen;
/// 使用的设计令牌列表
final List<DesignTokenRef> designTokens;
/// 页面状态
final PageStatus status;
}
/// 页面分类
enum PageCategory {
core('核心', '🏠'),
editor('编辑器', '🎨'),
discovery('发现', '🔍'),
settings('设置', '⚙️'),
membership('会员', '👑');
const PageCategory(this.label, this.emoji);
final String label;
final String emoji;
}
/// 页面状态
enum PageStatus {
active('已上线'),
wip('开发中'),
planned('规划中'),
deprecated('已废弃');
const PageStatus(this.label);
final String label;
}
/// 设计令牌引用
class DesignTokenRef {
const DesignTokenRef({
required this.name,
required this.type,
required this.value,
this.description,
});
final String name;
final DesignTokenType type;
final String value;
final String? description;
}
/// 设计令牌类型
enum DesignTokenType { color, spacing, radius, typography, shadow, glass }
// ============================================================
// 设计令牌参考表
// ============================================================
/// 全局设计令牌参考
class DesignTokenRegistry {
DesignTokenRegistry._();
// ---- 颜色令牌 ----
static const colorTokens = [
DesignTokenRef(
name: 'primary',
type: DesignTokenType.color,
value: '#6C63FF',
description: '主色-薰衣草紫',
),
DesignTokenRef(
name: 'secondary',
type: DesignTokenType.color,
value: '#FF6B6B',
description: '辅助色-珊瑚红',
),
DesignTokenRef(
name: 'accent',
type: DesignTokenType.color,
value: '#4ECDC4',
description: '强调色-薄荷绿',
),
DesignTokenRef(
name: 'success',
type: DesignTokenType.color,
value: '#10B981',
description: '成功色',
),
DesignTokenRef(
name: 'warning',
type: DesignTokenType.color,
value: '#F59E0B',
description: '警告色',
),
DesignTokenRef(
name: 'error',
type: DesignTokenType.color,
value: '#EF4444',
description: '错误色',
),
DesignTokenRef(
name: 'bgPrimary-light',
type: DesignTokenType.color,
value: '#FAFAFA',
description: '日间主背景',
),
DesignTokenRef(
name: 'bgPrimary-dark',
type: DesignTokenType.color,
value: '#1A1A2E',
description: '夜间主背景',
),
DesignTokenRef(
name: 'textPrimary-light',
type: DesignTokenType.color,
value: '#1A1A2E',
description: '日间主文字',
),
DesignTokenRef(
name: 'textPrimary-dark',
type: DesignTokenType.color,
value: '#E5E5E5',
description: '夜间主文字',
),
];
// ---- 间距令牌 ----
static const spacingTokens = [
DesignTokenRef(
name: 'xs',
type: DesignTokenType.spacing,
value: '${AppSpacing.xs}px',
description: '紧凑间距',
),
DesignTokenRef(
name: 'sm',
type: DesignTokenType.spacing,
value: '${AppSpacing.sm}px',
description: '元素内间距',
),
DesignTokenRef(
name: 'md',
type: DesignTokenType.spacing,
value: '${AppSpacing.md}px',
description: '标准间距',
),
DesignTokenRef(
name: 'lg',
type: DesignTokenType.spacing,
value: '${AppSpacing.lg}px',
description: '区块间距',
),
DesignTokenRef(
name: 'xl',
type: DesignTokenType.spacing,
value: '${AppSpacing.xl}px',
description: '大区块间距',
),
];
// ---- 圆角令牌 ----
static const radiusTokens = [
DesignTokenRef(
name: 'sm',
type: DesignTokenType.radius,
value: '${AppRadius.sm}px',
description: '小按钮/标签',
),
DesignTokenRef(
name: 'md',
type: DesignTokenType.radius,
value: '${AppRadius.md}px',
description: '卡片/弹窗',
),
DesignTokenRef(
name: 'lg',
type: DesignTokenType.radius,
value: '${AppRadius.lg}px',
description: '大卡片/面板',
),
DesignTokenRef(
name: 'xl',
type: DesignTokenType.radius,
value: '${AppRadius.xl}px',
description: '底部弹窗',
),
DesignTokenRef(
name: 'full',
type: DesignTokenType.radius,
value: '999px',
description: '胶囊/圆形',
),
];
// ---- 字体令牌 ----
static const typographyTokens = [
DesignTokenRef(
name: 'display',
type: DesignTokenType.typography,
value: '34px/Bold',
description: '超大标题',
),
DesignTokenRef(
name: 'title1',
type: DesignTokenType.typography,
value: '28px/Bold',
description: '大标题',
),
DesignTokenRef(
name: 'title2',
type: DesignTokenType.typography,
value: '22px/Bold',
description: '中标题',
),
DesignTokenRef(
name: 'title3',
type: DesignTokenType.typography,
value: '20px/SemiBold',
description: '小标题',
),
DesignTokenRef(
name: 'headline',
type: DesignTokenType.typography,
value: '17px/SemiBold',
description: '头条',
),
DesignTokenRef(
name: 'body',
type: DesignTokenType.typography,
value: '17px/Regular',
description: '正文',
),
DesignTokenRef(
name: 'callout',
type: DesignTokenType.typography,
value: '16px/Medium',
description: '标注',
),
DesignTokenRef(
name: 'subhead',
type: DesignTokenType.typography,
value: '15px/Regular',
description: '副标题',
),
DesignTokenRef(
name: 'footnote',
type: DesignTokenType.typography,
value: '13px/Regular',
description: '脚注',
),
DesignTokenRef(
name: 'caption1',
type: DesignTokenType.typography,
value: '12px/Regular',
description: '说明文字',
),
DesignTokenRef(
name: 'caption2',
type: DesignTokenType.typography,
value: '11px/Regular',
description: '小说明文字',
),
];
// ---- 毛玻璃令牌 ----
static const glassTokens = [
DesignTokenRef(
name: 'glassColorLight',
type: DesignTokenType.glass,
value: '#FFFFFF',
description: '日间毛玻璃底色',
),
DesignTokenRef(
name: 'glassColorDark',
type: DesignTokenType.glass,
value: '#2D2D44',
description: '夜间毛玻璃底色',
),
DesignTokenRef(
name: 'borderOpacity',
type: DesignTokenType.glass,
value: '0.3',
description: '毛玻璃边框透明度',
),
];
/// 获取所有令牌
static List<DesignTokenRef> get allTokens => [
...colorTokens,
...spacingTokens,
...radiusTokens,
...typographyTokens,
...glassTokens,
];
/// 按类型筛选令牌
static List<DesignTokenRef> tokensByType(DesignTokenType type) {
return allTokens.where((t) => t.type == type).toList();
}
}
// ============================================================
// 页面注册表
// ============================================================
/// 页面注册表 — 所有页面必须在此注册
class PageRegistry {
PageRegistry._();
/// 全部注册页面
static const List<PageRegistryEntry> pages = [
// ---- 核心 Tab 页面 ----
PageRegistryEntry(
route: AppRoutes.home,
name: '首页',
description: '句子阅读主页面,展示每日推荐 + 句子流 + Hitokoto API',
category: PageCategory.core,
isTab: true,
designTokens: [
DesignTokenRef(
name: 'primary',
type: DesignTokenType.color,
value: '#6C63FF',
),
DesignTokenRef(
name: 'md',
type: DesignTokenType.spacing,
value: '16px',
),
DesignTokenRef(name: 'lg', type: DesignTokenType.radius, value: '12px'),
DesignTokenRef(
name: 'title1',
type: DesignTokenType.typography,
value: '28px/Bold',
),
DesignTokenRef(
name: 'body',
type: DesignTokenType.typography,
value: '17px/Regular',
),
],
),
PageRegistryEntry(
route: AppRoutes.inspiration,
name: '灵感',
description: '灵感发现 + 分类浏览 + 热门标签 + 句子瀑布流',
category: PageCategory.discovery,
isTab: true,
designTokens: [
DesignTokenRef(
name: 'accent',
type: DesignTokenType.color,
value: '#4ECDC4',
),
DesignTokenRef(
name: 'md',
type: DesignTokenType.spacing,
value: '16px',
),
DesignTokenRef(name: 'md', type: DesignTokenType.radius, value: '8px'),
DesignTokenRef(
name: 'title3',
type: DesignTokenType.typography,
value: '20px/SemiBold',
),
],
),
PageRegistryEntry(
route: AppRoutes.editor,
name: '编辑器',
description: '卡片/壁纸编辑器,包含画布预览+工具栏+底部面板',
category: PageCategory.editor,
isTab: true,
designTokens: [
DesignTokenRef(
name: 'primary',
type: DesignTokenType.color,
value: '#6C63FF',
),
DesignTokenRef(name: 'sm', type: DesignTokenType.spacing, value: '8px'),
DesignTokenRef(name: 'md', type: DesignTokenType.radius, value: '8px'),
DesignTokenRef(
name: 'glassColorLight',
type: DesignTokenType.glass,
value: '#FFFFFF',
),
],
),
PageRegistryEntry(
route: AppRoutes.profile,
name: '个人中心',
description: '用户个人设置 + 主题切换 + 收藏/历史 + 关于',
category: PageCategory.settings,
isTab: true,
designTokens: [
DesignTokenRef(
name: 'primary',
type: DesignTokenType.color,
value: '#6C63FF',
),
DesignTokenRef(
name: 'md',
type: DesignTokenType.spacing,
value: '16px',
),
DesignTokenRef(name: 'lg', type: DesignTokenType.radius, value: '12px'),
],
),
// ---- 全屏页面 ----
PageRegistryEntry(
route: AppRoutes.search,
name: '搜索',
description: '句子搜索 + 热搜词 + 搜索历史 + 结果展示',
category: PageCategory.discovery,
isFullscreen: true,
designTokens: [
DesignTokenRef(
name: 'primary',
type: DesignTokenType.color,
value: '#6C63FF',
),
DesignTokenRef(
name: 'md',
type: DesignTokenType.spacing,
value: '16px',
),
DesignTokenRef(name: 'md', type: DesignTokenType.radius, value: '8px'),
],
),
PageRegistryEntry(
route: AppRoutes.source,
name: '句子来源',
description: '句子来源详情 — 书籍/影视/人物/动漫/歌词分类浏览',
category: PageCategory.discovery,
isFullscreen: true,
designTokens: [
DesignTokenRef(
name: 'accent',
type: DesignTokenType.color,
value: '#4ECDC4',
),
DesignTokenRef(
name: 'md',
type: DesignTokenType.spacing,
value: '16px',
),
DesignTokenRef(name: 'lg', type: DesignTokenType.radius, value: '12px'),
],
),
PageRegistryEntry(
route: AppRoutes.member,
name: '会员中心',
description: '会员权益展示 + 订阅管理 + FAQ',
category: PageCategory.membership,
isFullscreen: true,
designTokens: [
DesignTokenRef(
name: 'warning',
type: DesignTokenType.color,
value: '#F59E0B',
),
DesignTokenRef(
name: 'md',
type: DesignTokenType.spacing,
value: '16px',
),
DesignTokenRef(name: 'xl', type: DesignTokenType.radius, value: '16px'),
],
),
PageRegistryEntry(
route: '/settings/theme',
name: '主题个性化',
description: '主题模式+强调色+字体大小+毛玻璃强度管理',
category: PageCategory.settings,
isFullscreen: true,
designTokens: [
DesignTokenRef(
name: 'primary',
type: DesignTokenType.color,
value: '#6C63FF',
),
DesignTokenRef(
name: 'md',
type: DesignTokenType.spacing,
value: '16px',
),
DesignTokenRef(name: 'md', type: DesignTokenType.radius, value: '8px'),
DesignTokenRef(
name: 'glassColorLight',
type: DesignTokenType.glass,
value: '#FFFFFF',
),
],
),
PageRegistryEntry(
route: AppRoutes.editor,
name: '编辑器',
description: '从首页/其他页面进入的全屏编辑器',
category: PageCategory.editor,
isFullscreen: true,
designTokens: [
DesignTokenRef(
name: 'primary',
type: DesignTokenType.color,
value: '#6C63FF',
),
DesignTokenRef(name: 'sm', type: DesignTokenType.spacing, value: '8px'),
DesignTokenRef(name: 'md', type: DesignTokenType.radius, value: '8px'),
],
),
];
/// 按路由查找页面
static PageRegistryEntry? findByRoute(String route) {
for (final entry in pages) {
if (entry.route == route) return entry;
}
return null;
}
/// 按分类筛选
static List<PageRegistryEntry> byCategory(PageCategory category) {
return pages.where((p) => p.category == category).toList();
}
/// 获取 Tab 页面
static List<PageRegistryEntry> get tabPages =>
pages.where((p) => p.isTab).toList();
/// 获取全屏页面
static List<PageRegistryEntry> get fullscreenPages =>
pages.where((p) => p.isFullscreen).toList();
/// 检测路由是否合法
static bool isRouteRegistered(String route) {
return pages.any((p) => p.route == route);
}
/// 检测所有页面是否都使用了设计令牌
static List<String> validateDesignTokens() {
final warnings = <String>[];
for (final page in pages) {
if (page.designTokens.isEmpty) {
warnings.add('${page.name}(${page.route}) 未声明使用的设计令牌');
}
}
return warnings;
}
/// 强制验证 — 路由表与注册表一致性
///
/// 检查所有 AppRoutes 中定义的路由是否都在注册表中注册。
static List<String> validateRouteConsistency() {
final errors = <String>[];
final registeredRoutes = pages.map((p) => p.route).toSet();
final appRoutes = [
AppRoutes.home,
AppRoutes.inspiration,
AppRoutes.editor,
AppRoutes.profile,
AppRoutes.search,
AppRoutes.source,
AppRoutes.member,
AppRoutes.themeSettings,
];
for (final route in appRoutes) {
if (!registeredRoutes.contains(route)) {
errors.add('路由 $route 在 AppRoutes 中定义但未在 PageRegistry 中注册');
}
}
for (final route in registeredRoutes) {
if (!appRoutes.contains(route)) {
errors.add('路由 $route 在 PageRegistry 中注册但未在 AppRoutes 中定义');
}
}
return errors;
}
/// 强制验证 — 页面设计规范
///
/// 检查每个页面是否使用了正确的设计令牌类型。
static List<String> validateDesignCompliance() {
final errors = <String>[];
for (final page in pages) {
final hasColor = page.designTokens.any(
(t) => t.type == DesignTokenType.color,
);
final hasSpacing = page.designTokens.any(
(t) => t.type == DesignTokenType.spacing,
);
final hasRadius = page.designTokens.any(
(t) => t.type == DesignTokenType.radius,
);
if (!hasColor) {
errors.add('${page.name}(${page.route}) 缺少颜色令牌声明');
}
if (!hasSpacing) {
errors.add('${page.name}(${page.route}) 缺少间距令牌声明');
}
if (!hasRadius) {
errors.add('${page.name}(${page.route}) 缺少圆角令牌声明');
}
}
return errors;
}
/// 执行全部验证
static List<String> validateAll() {
return [
...validateDesignTokens(),
...validateRouteConsistency(),
...validateDesignCompliance(),
];
}
/// 获取注册页面总数
static int get pageCount => pages.length;
/// 获取各分类页面数量
static Map<PageCategory, int> get categoryCounts {
final counts = <PageCategory, int>{};
for (final page in pages) {
counts[page.category] = (counts[page.category] ?? 0) + 1;
}
return counts;
}
}