# 08 · Flutter 主题迁移与登录组件实现报告 > 作者:Frontend Developer > 日期:2026-09-03 > 依据:ADR-005(patbond-doc/docs/architecture/decisions.md)、04-ui-login-design-spec.md ## 1. 完成内容 1. `lib/core/theme/app_theme.dart` 重写为语义 token 组织(`AppColors` + 新增 `AppRadius`),落地珊瑚橙正典色板;补齐 `filledButtonTheme`(高 52、圆角 16、`primaryStrong` 填充、显式 disabled 色)、`errorBorder` / `focusedErrorBorder` / `errorStyle`、`colorScheme.error` 覆盖。主题仍集中在单一文件。 2. 五个 Tab 页面及公共组件逐处替换旧靛蓝/冷灰硬编码色值为语义 token,仅换色、未动布局。 3. 新建 5 个可复用组件于 `lib/core/widgets/`:`BrandMark`、`AppTextField`、`PrimaryButton`(含 isLoading)、`InlineErrorBanner`、`AuthScaffold`。本次未建登录页面(等后端契约冻结)。 4. 为 `PrimaryButton`、`AppTextField` 各写 3 个 widget 测试。 ## 2. Token 映射表(旧 → 新) ### 2.1 AppColors 字段值变化(字段名不变,引用处无需改) | Token | 旧值(脚手架靛蓝) | 新值(珊瑚橙正典) | | --- | --- | --- | | `primary` | `#4F46E5` | `#FF6F4C`(仅装饰/图标,不承载实心填充上的文字) | | `canvas` | `#F8FAFC` | `#FFF7ED` 奶油底 | | `ink` | `#0F172A` | `#3E2A1F` | | `muted` | `#64748B` | `#9C8977` | | `border` | `#E2E8F0` | `#F0DCC8` | | `success` | `#10B981` | `#7FA88A` sage | | `warning` | `#F59E0B` | `#F59E0B`(不变) | ### 2.2 新增 token | Token | 值 | 用途 | | --- | --- | --- | | `primaryStrong` | `#D6431A` | 实心按钮填充、可点击文字链接(白字 ≈4.5:1,WCAG AA) | | `primaryDark` | `#7A2E12` | 浅色底上的强调文字 | | `accent` / `accentDark` | `#FFB648` / `#7A4B0A` | 渐变终点、徽章 / amber 底文字 | | `surface` | `#FFFFFF` | 卡片、输入框背景(原为字面量 `Colors.white`) | | `surfaceTint` | `#FFE8D6` peach | 图标底、占位块、选中指示 | | `successInk` / `successSurface` | `#3F5744` / `#E8F0E8` | 成功提示的文字 / 底色 | | `error` | `#D0342C` | 错误文字、错误描边(白底 ≈5.4:1,AA) | | `brandGradient` | 135° `#FF6F4C → #FFB648` | 品牌渐变(装饰,不承载正文文字) | | `AppRadius.sm/md/lg/xl/pill` | 12 / 16 / 18 / 24 / 999 | 圆角刻度(§1.3) | ### 2.3 页面内硬编码旧色值 → token | 旧硬编码 | 新写法 | 位置 | | --- | --- | --- | | `#F1F5F9`(图片占位底) | `AppColors.surfaceTint` | common.dart ×2 | | `#EEF2FF`(indigo-50 图标底) | `AppColors.surfaceTint` | home ×2、profile | | `#E0E7FF`(导航指示/渐变上副文字) | `AppColors.surfaceTint` / `Colors.white` | 主题、home 促销卡 | | `[#4F46E5→#8B5CF6]`、`[primary→#7C3AED]` 渐变 | `AppColors.brandGradient` | home 故事环、促销卡 | | `#ECFDF5→#FFF7ED` 问候卡渐变 | `surfaceTint → canvas` | home | | `0x224F46E5` 装饰爪印 | `primary.withAlpha(34)` | home | | `#475569`(slate 次级文字/阴天色) | `AppColors.primaryDark` / `AppColors.ink` | home | | `#64748B`、`#F59E0B`(多云/晴天) | `AppColors.muted` / `AppColors.warning` | home 天气色 | | `0xCC0F172A` 图片压暗渐变 | `ink.withAlpha(204)` | create | | `#E2E8F0`(scrim 上副文字/进度轨道) | `Colors.white70` / `AppColors.border` | create、pets | | `#ECFDF5` / `#A7F3D0` / `#047857` 健康提醒卡 | `successSurface` / `success.withAlpha(140)` / `successInk` | pets | | `#A5B4FC` / `#334155` / `#94A3B8`(深色卡上) | `AppColors.accent` / `Colors.white24` / `Colors.white70` | profile | | 促销卡反白按钮前景 `primary` | `primaryStrong`(AA) | home | | 导航选中 label `primary` | `primaryStrong`(11px 文字保 AA) | 主题 | 保留未动:天气雨/雪的功能性蓝色(`#0284C7`、`#0891B2`,非旧品牌色)、点赞红、卡片主题 `#F1F5F9` 描边改为 `AppColors.border`。 ## 3. 改动文件清单 修改(7): - `patbond-flutter/lib/core/theme/app_theme.dart`(重写) - `patbond-flutter/lib/widgets/common.dart` - `patbond-flutter/lib/features/home/home_page.dart` - `patbond-flutter/lib/features/create/create_page.dart` - `patbond-flutter/lib/features/pets/pets_page.dart` - `patbond-flutter/lib/features/profile/profile_page.dart` - (services / post_detail / main_shell 仅引用 token 字段,值随主题自动切换,无需改动) 新增(7): - `patbond-flutter/lib/core/widgets/brand_mark.dart` - `patbond-flutter/lib/core/widgets/app_text_field.dart` - `patbond-flutter/lib/core/widgets/primary_button.dart` - `patbond-flutter/lib/core/widgets/inline_error_banner.dart` - `patbond-flutter/lib/core/widgets/auth_scaffold.dart` - `patbond-flutter/test/core/widgets/primary_button_test.dart` - `patbond-flutter/test/core/widgets/app_text_field_test.dart` ## 4. 验收命令输出摘要 ```text $ dart format --output=none --set-exit-if-changed lib test Formatted 22 files (0 changed) in 0.15 seconds. # exit 0 $ flutter analyze Analyzing patbond-flutter... No issues found! (ran in 0.7s) # 无 error、无 warning $ flutter test 00:01 +7: All tests passed! # 含原有导航冒烟测试,未改断言 ``` ## 5. 实现说明与遗留问题 1. **filledButtonTheme 的 minimumSize 用了 `Size(64, 52)` 而非规范建议的 `Size.fromHeight(52)`**:后者会给所有 FilledButton 无限最小宽度,令 Row 内既有按钮(首页促销卡「去使用」、服务页「立即预约」、对话框「确认恢复」)布局异常。改为仅约束高度 52;登录页的全宽由 `PrimaryButton` 自身 `width: double.infinity` 保证。副作用:既有小按钮从默认 40 高变为 52 高,视觉更厚重但协调,未破坏布局。 2. **PrimaryButton 的 loading 态**通过 `disabledBackgroundColor: primaryStrong` 保持珊瑚填充(规范 §4.3/§4.4:loading 不是置灰禁用态),已有测试锁定该行为。 3. **`ColorScheme.fromSeed` 仍以 `#FF6F4C` 为种子**,Material 组件(Chip、SegmentedButton、tonal 按钮等)使用生成的暖色调色板,与正典色板协调但非逐一指定;`error` 已显式覆盖为 `#D0342C`。若后续设计对某组件色不满意,在主题内补对应 componentTheme 即可。 4. **天气语义色**:雨 `#0284C7`、雪 `#0891B2` 为功能色保留硬编码;阴天映射为 `ink`、多云映射为 `muted`,如需更细的天气色阶可后续补 token。 5. **品牌字体(Baloo 2 / 圆润中文标题体)未引入**,规范允许后置;`BrandMark` 字标暂用系统字体 32/w700。 6. **成功色 sage `#7FA88A` 作 11px TagPill 文字对比度偏弱**(约 2.4:1,沿用既有 TagPill 模式);正文类成功文字请用 `successInk`。此为设计规范自带取舍,未在本次擅改。 7. Splash / 登录 / 注册页、认证状态机、flutter_secure_storage 依赖均未实现,按计划等后端契约冻结后进行。 --- **Frontend Developer** · 2026-09-03