feat: 进行功能分区方便后期维护

- 添加导航条目和分类数据模型
- 实现导航卡片网格展示
- 支持点击卡片打开链接
- 添加主题样式(渐变色、卡片阴影)
- 区分系统默认和自定义条目
- 添加分类统计信息"
This commit is contained in:
2026-09-13 22:49:02 +08:00
parent 4075519075
commit e2cc3ac30f
40 changed files with 3379 additions and 515 deletions
+76
View File
@@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
/// 全局配色。
///
/// 取值逐条对应 `index.html` 里 `:root` 的 CSS 变量,
/// 组件只引用这里的语义名,不再出现十六进制字面量。
/// 将来调色只需要改这个文件。
abstract final class AppColors {
// ---- 基础色 ----
/// 页面底色(`--bg-color`)。
static const Color background = Color(0xFFF7F9FB);
/// 卡片 / 侧栏底色(`--card-bg`)。
static const Color surface = Color(0xFFFFFFFF);
/// 主色(`--primary-color`):logo、导航选中态、图标文字、焦点边框。
static const Color primary = Color(0xFF3B82F6);
/// 主要文字(`--text-main`):标题与卡片标题。
static const Color textPrimary = Color(0xFF1E293B);
/// 次要文字(`--text-sub`):描述、未选中的导航项。
static const Color textSecondary = Color(0xFF64748B);
/// 描边(`--border-color`):侧栏右边界、卡片边框、搜索框边框。
static const Color border = Color(0xFFE2E8F0);
/// 导航项悬停 / 选中底色(`.nav-item:hover`)。
static const Color primaryFaint = Color(0xFFEFF6FF);
/// 导航项悬停底色(`.nav-link:hover`,比选中态更浅)。
static const Color navItemHover = Color(0xFFF8FAFC);
/// 提交按钮悬停底色(`.submit-btn:hover`),与卡片图标底色同值。
static const Color navItemHoverDeep = Color(0xFFF1F5F9);
/// 折叠箭头颜色(`.arrow-icon`)。
static const Color navArrow = Color(0xFF94A3B8);
/// 卡片图标底色(`.card-icon`)。
static const Color iconSurface = Color(0xFFF1F5F9);
/// 搜索框聚焦时的外发光(`0 0 0 3px rgba(59,130,246,.15)`)。
static const Color focusRing = Color(0x263B82F6);
// ---- 阴影 ----
/// 卡片默认阴影(`--shadow`)。
static const List<BoxShadow> cardShadow = <BoxShadow>[
BoxShadow(
color: Color(0x0D000000),
blurRadius: 6,
offset: Offset(0, 4),
),
BoxShadow(
color: Color(0x08000000),
blurRadius: 4,
offset: Offset(0, 2),
),
];
/// 卡片悬停阴影(`--shadow-hover`)。
static const List<BoxShadow> cardShadowHover = <BoxShadow>[
BoxShadow(
color: Color(0x14000000),
blurRadius: 15,
offset: Offset(0, 10),
),
BoxShadow(
color: Color(0x0A000000),
blurRadius: 6,
offset: Offset(0, 4),
),
];
}
@@ -0,0 +1,105 @@
import 'package:flutter/material.dart';
import 'app_colors.dart';
/// 语义化文字样式。
///
/// 字号与字重对应 `index.html` 的 CSS;字体族不显式指定,
/// 沿用 HTML 里的系统字体栈(`-apple-system, Segoe UI, ...`)。
/// 颜色留空的样式由调用方按状态着色(例如导航项的选中 / 未选中)。
abstract final class AppTextStyles {
/// 侧栏 logo 文字(`.logo`,18px / 700)。
static const TextStyle logo = TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: AppColors.primary,
);
/// 侧栏折叠后的 logo 图标(`.logo-icon`,20px / 800)。
static const TextStyle logoIcon = TextStyle(
fontSize: 20,
fontWeight: FontWeight.w800,
color: AppColors.primary,
);
/// 侧栏一级导航项(`.nav-link`,14px / 500)。
static const TextStyle navItem = TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
);
/// 侧栏二级导航项(`.sub-menu .nav-link`,13px / 500)。
static const TextStyle subNavItem = TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
);
/// 折叠箭头(`.arrow-icon`,14px)。
static const TextStyle navArrow = TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: AppColors.navArrow,
fontFamily: 'monospace',
height: 1,
);
/// 提交按钮文字(`.submit-btn`,14px / 500)。
static const TextStyle submitButton = TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
);
/// 分组标题(`.section-title`,16px / 600)。
static const TextStyle sectionTitle = TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
);
/// 搜索框正文(`.search-box`,15px)。
static const TextStyle searchInput = TextStyle(
fontSize: 15,
color: AppColors.textPrimary,
);
/// 搜索框占位文字。
static const TextStyle searchHint = TextStyle(
fontSize: 15,
color: AppColors.textSecondary,
);
/// 卡片标题(`.card-title`,14px / 600)。
static const TextStyle cardTitle = TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
);
/// 卡片描述(`.card-desc`,12px)。
static const TextStyle cardDesc = TextStyle(
fontSize: 12,
color: AppColors.textSecondary,
);
/// 卡片图标里的首字母(`.card-icon`)。
///
/// 原型没有给 .card-icon 设 font-size,它继承 body 的浏览器默认字号 16px。
static const TextStyle cardIcon = TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.primary,
);
/// 空状态主文案。
static const TextStyle emptyTitle = TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: AppColors.textPrimary,
);
/// 空状态补充说明。
static const TextStyle emptyMessage = TextStyle(
fontSize: 13,
color: AppColors.textSecondary,
);
}
+31
View File
@@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'app_colors.dart';
/// 全局主题。
abstract final class AppTheme {
/// 浅色主题(当前唯一主题,取值来自 index.html 的设计变量)。
static ThemeData get light {
return ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: AppColors.primary,
primary: AppColors.primary,
surface: AppColors.surface,
),
scaffoldBackgroundColor: AppColors.background,
dividerColor: AppColors.border,
iconTheme: const IconThemeData(color: AppColors.textSecondary, size: 20),
// 搜索框自己绘制边框与聚焦光晕,这里只负责光标颜色。
textSelectionTheme: const TextSelectionThemeData(
cursorColor: AppColors.primary,
selectionColor: AppColors.focusRing,
selectionHandleColor: AppColors.primary,
),
snackBarTheme: const SnackBarThemeData(
behavior: SnackBarBehavior.floating,
backgroundColor: AppColors.textPrimary,
contentTextStyle: TextStyle(color: AppColors.surface, fontSize: 14),
),
);
}
}