- 新增菜品对比功能,支持1v1左右和1vN上下布局切换 - 新增食物相生相克查询工具,包含API服务和详情页面 - 优化平台工具类,移除冗余的鸿蒙系统检测逻辑 - 更新版本号至1.5.1,修改更新日志和版本说明 - 修复多个页面列表分隔符构建器参数警告 - 新增雷达图组件,用于展示多维度对比数据 - 新增可编辑数值组件,支持双击编辑和范围验证 - 优化导出按钮,增加对比功能入口 - 新增搜索引擎枚举工具类 - 更新应用路由配置,添加对比和相生相克相关页面
767 lines
32 KiB
Markdown
767 lines
32 KiB
Markdown
# Changelog
|
||
|
||
All notable changes to this project will be documented in this file.
|
||
|
||
|
||
## [0.99.85] - 2026-05-01
|
||
|
||
### 🐛 修复: ComparisonController注册方式统一 + 硬编码颜色替换
|
||
|
||
#### 问题1: 注册方式不一致
|
||
`ComparisonController` 使用 `Get.put(permanent: true)` 注册,而其他功能控制器均使用 `Get.lazyPut(fenix: true)`。
|
||
|
||
#### 问题2: 硬编码颜色值
|
||
`slotColors` 使用硬编码颜色值(`Color(0xFF007AFF)` 等),未使用 DesignTokens 令牌。
|
||
|
||
#### 修复
|
||
|
||
| 文件 | 变更 |
|
||
|------|------|
|
||
| `app_binding.dart` | `Get.put(ComparisonController(), permanent: true)` → `Get.lazyPut(() => ComparisonController(), fenix: true)` |
|
||
| `comparison_controller.dart` | `slotColors` 硬编码颜色替换为 `DesignTokens.primary/orange/green/purple` |
|
||
|
||
#### 颜色映射
|
||
|
||
| 槽位 | 修复前 | 修复后 |
|
||
|------|--------|--------|
|
||
| 0 | `Color(0xFF007AFF)` | `DesignTokens.primary` |
|
||
| 1 | `Color(0xFFFF9500)` | `DesignTokens.orange` |
|
||
| 2 | `Color(0xFF34C759)` | `DesignTokens.green` |
|
||
| 3 | `Color(0xFFAF52DE)` | `DesignTokens.purple` |
|
||
|
||
> 注:槽位3颜色从 iOS 系统紫(0xFFAF52DE)调整为 DesignTokens.purple(0xFF9C27B0),与设计系统统一
|
||
|
||
## [0.99.84] - 2026-05-01
|
||
|
||
### 🐛 修复: 对比控制器超时保护补全
|
||
|
||
#### 问题
|
||
`ComparisonController` 中 `addDish`、`loadSlotDetail`、`loadIngredientSlotDetail` 三个方法调用 API 时未设置超时,可能导致长时间阻塞(与已修复的 `addIngredient` 超时问题一致)。
|
||
|
||
#### 修复内容
|
||
|
||
| 方法 | 修复 |
|
||
|------|------|
|
||
| `addDish` | `_repo.fetchDetail` 增加 5 秒超时,超时后使用源数据 |
|
||
| `loadSlotDetail` | `_repo.fetchDetail` 增加 5 秒超时,超时后跳过加载 |
|
||
| `loadIngredientSlotDetail` | `_repo.fetchIngredientDetail` 增加 5 秒超时,超时后跳过加载 |
|
||
|
||
#### 修改文件
|
||
|
||
| 文件 | 变更 |
|
||
|------|------|
|
||
| `comparison_controller.dart` | 三处 API 调用添加 `.timeout(Duration(seconds: 5))` 及 `TimeoutException` 处理 |
|
||
|
||
## [0.99.83] - 2026-05-01
|
||
|
||
### ✨ 新功能: 相生相克Sheet二次搜索+无结果说明
|
||
|
||
#### 交互流程
|
||
|
||
```
|
||
点击「🔗 相生相克」
|
||
→ 搜索当前食材名
|
||
→ 有结果:显示列表
|
||
→ 无结果:显示「📋 查看相关推荐」按钮
|
||
→ 点击后截断关键词重搜
|
||
→ 有结果:显示列表
|
||
→ 无结果:显示「暂无相生相克记录」+ 说明卡片
|
||
```
|
||
|
||
#### 无结果说明卡片内容
|
||
|
||
| emoji | 说明 |
|
||
|-------|------|
|
||
| 💡 | 并非所有食材都有相生相克的传统记载。部分食材因食用历史、搭配习惯等因素,尚未形成明确的相生相克说法 |
|
||
| 🔬 | 相生相克多源于中医食疗理论,现代营养学对此尚无统一定论。食材搭配宜根据个人体质和健康状况灵活调整 |
|
||
| 🥗 | 日常饮食建议遵循「多样均衡、适量搭配」的原则,不必过度拘泥于相生相克的说法 |
|
||
|
||
#### 修改文件
|
||
|
||
| 文件 | 变更 |
|
||
|------|------|
|
||
| `ingredient_detail_page.dart` | 新增 `_hasSearchedRelated` 状态、`_buildNoCompatSection()`、`_noCompatTip()` |
|
||
|
||
## [0.99.82] - 2026-05-01
|
||
|
||
### 🎨 UI调整: 食材详情页按钮布局优化
|
||
|
||
#### 变更前
|
||
|
||
```
|
||
┌─────────────────────┐
|
||
│ 🔗 相生相克 │ ← 全宽渐变按钮(单独一行)
|
||
├─────────────────────┤
|
||
│ [返回首页] [🥬食材对比] │ ← 左右对齐
|
||
└─────────────────────┘
|
||
```
|
||
|
||
#### 变更后
|
||
|
||
```
|
||
┌───────────┬──────────┐
|
||
│ 🔗相生相克 │ 🥬食材对比 │ ← 左右等宽对齐,去掉返回首页
|
||
└───────────┴──────────┘
|
||
```
|
||
|
||
#### 修改内容
|
||
|
||
- 删除「返回首页」按钮
|
||
- 「🔗 相生相克」与「🥬 食材对比」改为 Row 左右等宽并排
|
||
|
||
## [0.99.81] - 2026-05-01
|
||
|
||
### ✨ 新功能: 相生相克详情页 AppBar 新增免责声明入口
|
||
|
||
#### 变更内容
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 位置 | `CupertinoNavigationBar` 右侧 trailing |
|
||
| 图标 | `CupertinoIcons.info_circle` (ℹ️) |
|
||
| 交互 | 点击弹出 `CupertinoActionSheet` 免责声明 |
|
||
|
||
#### 免责声明内容(5 条)
|
||
|
||
| emoji | 标题 | 内容摘要 |
|
||
|-------|------|----------|
|
||
| 🌐 | 数据来源 | 部分数据来源于网络公开资料(中医药典籍、营养学文献、健康网站等) |
|
||
| 📚 | 民间整理 | 部分来自民间饮食经验收集整理,未经系统科学验证 |
|
||
| ⚗️ | 科学依据 | 相生相克可能缺乏现代医学/营养学充分科学依据,仅供参考,不构成医疗建议 |
|
||
| 🛡️ | 自行辨别 | 结合自身健康状况理性判断,特殊体质/过敏史/疾病请咨询专业医师或营养师 |
|
||
| 📝 | 版权声明 | 仅作信息聚合展示,不对准确性、完整性做保证,如有侵权请联系删除 |
|
||
|
||
#### 修改文件
|
||
|
||
| 文件 | 变更 |
|
||
|------|------|
|
||
| `food_compat_detail_page.dart` | 新增 `_showDisclaimer()` + `_disclaimerItem()` + AppBar trailing |
|
||
|
||
## [0.99.80] - 2026-05-01
|
||
|
||
### 🎨 UI调整: 删除搜索框,底部按钮上移至详情头部下方
|
||
|
||
#### 变更前
|
||
|
||
| 位置 | 组件 |
|
||
|------|------|
|
||
| 顶部(导航栏下) | 🔍 搜索食材输入框 |
|
||
| 底部(列表末尾) | 🔗相生相克 + 返回首页 + 🥬食材对比 |
|
||
|
||
#### 变更后
|
||
|
||
| 位置 | 组件 |
|
||
|------|------|
|
||
| 顶部(导航栏下) | DetailHeaderCard(食材名+分类+热量) |
|
||
| 头部卡片下方 | 🔗相生相克(渐变按钮)+ 返回首页 + 🥬食材对比 |
|
||
|
||
#### 修改内容
|
||
|
||
- 删除 `_buildSearchBar()` 方法及其在 `build()` 中的调用
|
||
- 将 `_buildCompatButton` + `Row(返回首页, 食材对比)` 从 ListView 末尾移至 `DetailHeaderCard` 之后
|
||
- 简化布局:`SafeArea → _buildContent`(去掉中间 Column 层)
|
||
|
||
## [0.99.79] - 2026-05-01
|
||
|
||
### ✨ 新功能: 食材详情页新增「相生相克」按钮 + Sheet弹窗
|
||
|
||
#### 变更前
|
||
|
||
| 项目 | 状态 |
|
||
|------|------|
|
||
| 搜索框 | 调用API实时搜索,双区展示(本地食材+相生相克) |
|
||
| 交互方式 | 输入框内搜索 |
|
||
|
||
#### 变更后
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 搜索框 | 恢复为本地食材过滤 |
|
||
| 新增按钮 | 🔗 相生相克(橙红渐变按钮,位于详情页底部) |
|
||
| 交互方式 | 点击按钮 → 弹出 Sheet → 自动搜索当前食材名称 |
|
||
| Sheet内容 | 标题栏(食材名+关闭按钮)+ 结果列表 |
|
||
| 结果卡片 | 相生绿色边框🤝 / 相克红色边框⚠️ / 描述摘要 |
|
||
| 无结果处理 | 显示"未找到记录"+「查看相关推荐」按钮(截断关键词重搜) |
|
||
| 错误处理 | 网络失败显示⚠️+「重试」按钮 |
|
||
| 点击跳转 | 点击结果 → 关闭Sheet → `toolsFoodCompatDetail` 详情页 |
|
||
|
||
#### 修改文件
|
||
|
||
| 文件 | 变更内容 |
|
||
|------|----------|
|
||
| `ingredient_detail_page.dart` | 删除`_buildCompatibilityCard`/双区展示;新增`_buildCompatButton`+`_showCompatibilitySheet`+`_CompatSheetContent`(StatefulWidget) |
|
||
|
||
## [0.99.78] - 2026-05-01
|
||
|
||
### ✨ 新功能: 食材详情页搜索框改为相生相克搜索
|
||
|
||
#### 变更前
|
||
|
||
| 项目 | 状态 |
|
||
|------|------|
|
||
| 搜索框 | 空壳UI,只过滤本地食材列表 |
|
||
| placeholder | "搜索食材..." |
|
||
|
||
#### 变更后
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 搜索框 | 调用 `FoodCompatibilityService.search()` API 实时搜索 |
|
||
| placeholder | "搜索相生相克..." |
|
||
| 防抖 | 400ms 防抖,避免频繁请求 |
|
||
| 双区展示 | 上方"📦 本地食材" + 下方"🔗 相生相克" |
|
||
| 结果卡片 | 相生绿色边框🤝 / 相克红色边框⚠️ / 浏览量 / 描述摘要 |
|
||
| 点击跳转 | 点击结果 → `toolsFoodCompatDetail` 详情页 |
|
||
| 加载状态 | 搜索中显示 CupertinoActivityIndicator |
|
||
|
||
#### 修改文件
|
||
|
||
| 文件 | 变更内容 |
|
||
|------|----------|
|
||
| `ingredient_detail_controller.dart` | 新增 `compatibilityResults`/`isSearchingCompatibility`/`searchCompatibility()` |
|
||
| `ingredient_detail_page.dart` | 搜索框改为API搜索+双区展示+`_buildCompatibilityCard`+点击跳转详情 |
|
||
|
||
## [0.99.77] - 2026-05-01
|
||
|
||
### 🐛 修复: 条形图左侧坐标值不切实际
|
||
|
||
#### 问题
|
||
|
||
| 问题 | 原因 |
|
||
|------|------|
|
||
| 只显示一个值 | leftTitles 把 Y 轴坐标(0-100)误当组索引查 label |
|
||
| 数值不切实际 | 每组独立归一化,不同组柱高不可横向比较 |
|
||
|
||
#### 修复方案
|
||
|
||
| 变更项 | 说明 |
|
||
|--------|------|
|
||
| 全局最大值归一化 | `_globalMax` 遍历所有食材×所有指标取全局最大值,所有组共享同一比例尺 |
|
||
| Y轴刻度 5档 | `interval: 25`,显示 100→75→50→25→0 对应的实际数值 |
|
||
| 从上到下递减 | fl_chart Y 轴原生从大到小排列,无需额外处理 |
|
||
| 数值格式化 | ≥1000 显示为 `1.2k`,整数去掉小数点 |
|
||
|
||
#### 效果示例(百合100 vs 花椒50)
|
||
|
||
```
|
||
100 ← 全局最大值
|
||
75
|
||
50
|
||
25
|
||
0
|
||
┃███████┃█████┃ 热量
|
||
┃█████┃██┃ 蛋白质
|
||
...
|
||
```
|
||
|
||
#### 修改文件
|
||
|
||
| 文件 | 变更内容 |
|
||
|------|----------|
|
||
| `comparison_bar_chart.dart` | 新增 `_globalMax`/`_formatAxisValue`,leftTitles 改为 Y 轴刻度模式 |
|
||
|
||
## [0.99.76] - 2026-05-01
|
||
|
||
### 🐛 修复: 点击添加食材等待30秒 + 条形图左侧无坐标值
|
||
|
||
#### 问题1:30秒延迟
|
||
|
||
| 问题 | 原因 |
|
||
|------|------|
|
||
| 添加食材卡30秒 | `fetchIngredientDetail` 无超时限制,API慢/不可达时一直await |
|
||
|
||
**修复**:所有 `fetchIngredientDetail` 调用增加 `.timeout(Duration(seconds: 5))`,超时后使用源数据继续
|
||
|
||
#### 问题2:条形图左侧无坐标值
|
||
|
||
| 问题 | 原因 |
|
||
|------|------|
|
||
| 左侧空白 | `leftTitles: SideTitles(showTitles: false)` 被关闭 |
|
||
|
||
**修复**:启用 leftTitles,显示每组最大值+单位(如 100kcal、5g)
|
||
|
||
#### 修改文件
|
||
|
||
| 文件 | 变更内容 |
|
||
|------|----------|
|
||
| `comparison_controller.dart` | fetchIngredientDetail 增加 5 秒超时 + TimeoutException 处理 |
|
||
| `comparison_bar_chart.dart` | leftTitles 启用,显示每组最大值+单位 |
|
||
|
||
## [0.99.75] - 2026-05-01
|
||
|
||
### 🐛 修复: 食材对比页面营养数据全为0
|
||
|
||
#### 根因
|
||
|
||
| 问题 | 原因 |
|
||
|------|------|
|
||
| 详情页有数据,对比页为0 | nutrition 数据经多层传递(详情→addIngredient→slots→_parseNutritionValue),任一环节丢失即解析为0 |
|
||
| 本地DB未兜底 | `_parseNutritionValue` 解析失败直接返回0,没有从 IngredientNutritionDb 补充 |
|
||
|
||
#### 修复方案
|
||
|
||
| 变更项 | 说明 |
|
||
|--------|------|
|
||
| 新增 _resolveNutrition | 优先从 nutrition 字符串解析 → 失败则从 IngredientNutritionDb 直接查询数值 |
|
||
| 移除 hasNutrition 守卫 | 不再因任一食材无 nutrition 就隐藏整个图表 |
|
||
| 三层保障 | ①源数据字符串 ②本地DB精确匹配 ③分类fallback默认值 |
|
||
|
||
#### 修改文件
|
||
|
||
| 文件 | 变更内容 |
|
||
|------|----------|
|
||
| `ingredient_comparison_page.dart` | 新增 `_resolveNutrition` 方法:解析失败→IngredientNutritionDb兜底 |
|
||
|
||
## [0.99.74] - 2026-05-01
|
||
|
||
### 🐛 修复: 食材对比条形图只显示一个食材
|
||
|
||
#### 根因
|
||
|
||
| 问题 | 原因 |
|
||
|------|------|
|
||
| 只显示一个条形 | 第二个食材营养值全为0,`toY=(0/max)*100=0`,柱子高度为0不可见 |
|
||
|
||
#### 修复方案
|
||
|
||
| 变更项 | 说明 |
|
||
|--------|------|
|
||
| 零值最小高度 | `toY` 从 0 改为 3(最小可见高度) |
|
||
| 零值半透明 | 无数据时颜色 `alpha: 0.3`,区分有/无数据 |
|
||
| tooltip优化 | 零值显示"暂无数据"而非"0" |
|
||
|
||
#### 修改文件
|
||
|
||
| 文件 | 变更内容 |
|
||
|------|----------|
|
||
| `comparison_bar_chart.dart` | `_buildBarGroups()`零值给最小高度3+半透明色,tooltip显示暂无数据 |
|
||
|
||
## [0.99.73] - 2026-05-01
|
||
|
||
### 🐛 修复: 食材对比营养数据空白
|
||
|
||
#### 根因
|
||
|
||
| 问题 | 原因 |
|
||
|------|------|
|
||
| 营养对比全空白 | `addIngredient`始终调用API,API不返回nutrition字段,覆盖了详情页传入的完整数据 |
|
||
| 选择面板食材无营养 | 搜索/浏览记录/快速选择的食材无nutrition,API也补不上 |
|
||
|
||
#### 修复方案
|
||
|
||
| 变更项 | 说明 |
|
||
|--------|------|
|
||
| 源数据优先策略 | `addIngredient`检测传入食材是否已有nutrition,有则锁定不被API覆盖 |
|
||
| API仅补充缺失字段 | 有营养数据时,API只补充image/categoryName等,nutrition保持源数据 |
|
||
| 无营养时本地补充 | 从选择面板添加的食材(无nutrition),merge后从`IngredientNutritionDb`补充 |
|
||
|
||
#### 修改文件
|
||
|
||
| 文件 | 变更内容 |
|
||
|------|----------|
|
||
| `comparison_controller.dart` | `addIngredient`重构双路径:有nutrition→直接用+API补充;无nutrition→API+本地DB兜底 |
|
||
| `comparison_pick_sheet.dart` | `Get.back()`→`Navigator.pop()`+isCurrent检查,修复选择后返回上一页 |
|
||
|
||
## [0.99.72] - 2026-05-01
|
||
|
||
### 🐛 修复: 食材对比选择后自动返回上一页
|
||
|
||
#### 核心变更
|
||
|
||
| 变更项 | 说明 |
|
||
|--------|------|
|
||
| 选择面板关闭逻辑 | `Get.back()` 替换为 `Navigator.of(context).pop()` + `ModalRoute.isCurrent` 检查 |
|
||
| 防止误关对比页 | 异步addIngredient/addDish完成后,先确认底部弹窗仍在栈顶再关闭,避免关闭对比页面 |
|
||
| 食材详情→对比数据 | 优先从本地缓存读取食材数据,营养数据从IngredientNutritionDb补充 |
|
||
|
||
#### 修改文件
|
||
|
||
| 文件 | 变更内容 |
|
||
|------|----------|
|
||
| `comparison_pick_sheet.dart` | 新增`_closeSheet()`方法,7处`Get.back()`全部替换,增加`isCurrent`安全检查 |
|
||
| `ingredient_detail_page.dart` | `_navigateToComparison`优先本地缓存,`_enrichWithLocalNutrition`补充营养数据 |
|
||
|
||
## [0.99.71] - 2026-05-01
|
||
|
||
### 🆕 新增: 食材详情页增加食材对比入口
|
||
|
||
#### 核心变更
|
||
|
||
| 变更项 | 说明 |
|
||
|--------|------|
|
||
| 食材详情页对比按钮 | 新增「🥬 食材对比」按钮,与「返回首页」左右对齐 |
|
||
| 对比跳转+预填 | 点击后跳转食材对比页面,当前食材自动填入第一个槽位 |
|
||
| 食材选择面板重构 | ComparisonPickSheet浏览记录Tab区分菜品/食材,食材模式使用IngredientCacheService |
|
||
| 个人中心入口 | profile_home 中「点餐助手」改为「菜品对比」,跳转对比页面 |
|
||
| 工具面板收起 | tools_panel_widget 底部「首页」按钮改为「收起」,点击关闭面板 |
|
||
| 食物相克修复 | 列表被AppBar遮住(SafeArea)、相关推荐不再堆叠页面 |
|
||
|
||
#### 修改文件
|
||
|
||
| 文件 | 变更内容 |
|
||
|------|----------|
|
||
| `ingredient_detail_page.dart` | 新增食材对比按钮+预填逻辑,优先使用API详情数据 |
|
||
| `comparison_pick_sheet.dart` | 浏览记录Tab区分菜品/食材,食材模式使用IngredientCacheService |
|
||
| `comparison_controller.dart` | 新增browseHistoryIngredients属性,从IngredientCacheService获取食材浏览记录 |
|
||
| `profile_home.dart` | 点餐助手→菜品对比,图标和路由更新 |
|
||
| `tools_panel_widget.dart` | 首页按钮→收起按钮,仅关闭面板 |
|
||
| `food_compat_page.dart` | SafeArea(top:false)→SafeArea(),修复列表被遮住 |
|
||
| `food_compat_detail_page.dart` | 相关推荐改为当前页面重新加载,移除Get依赖 |
|
||
|
||
## [0.99.70] - 2026-05-01
|
||
|
||
### 🆕 新增: 食物相生相克工具(Flutter页面+路由+工具注册)
|
||
|
||
#### 核心变更
|
||
|
||
| 变更项 | 说明 |
|
||
|--------|------|
|
||
| 食物相生相克主页面 | 新增 FoodCompatPage,支持搜索、分类筛选、热门标签、分页加载 |
|
||
| 食物相生相克详情页面 | 新增 FoodCompatDetailPage,展示详情、复制+搜索、上下条导航、相关推荐 |
|
||
| 工具注册 | 在 ToolRegistry.defaultTools 中注册食物相生相克工具项 |
|
||
| 路由注册 | 新增 /tools/food-compatibility 和 /tools/food-compatibility/detail 路由 |
|
||
| API服务 | FoodCompatibilityService 封装列表/详情/分类/搜索/热门/随机接口 |
|
||
|
||
#### 新增文件
|
||
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `lib/src/pages/tools/health/food_compat/food_compat_page.dart` | 食物相生相克主页面(列表+搜索+分类+热门) |
|
||
| `lib/src/pages/tools/health/food_compat/food_compat_detail_page.dart` | 食物相生相克详情页面(详情+复制搜索+导航+相关推荐) |
|
||
| `lib/src/services/data/business/food_compatibility_service.dart` | API服务 + 数据模型(FoodItem/FoodDetail/FoodCategory/FoodListResult) |
|
||
|
||
#### 修改文件
|
||
|
||
| 文件 | 变更内容 |
|
||
|------|----------|
|
||
| `tool_item_model.dart` | 新增 food_compatibility 工具项(health分类,瀑布流展示) |
|
||
| `app_routes.dart` | 新增 toolsFoodCompat 和 toolsFoodCompatDetail 路由及PageInfo |
|
||
|
||
#### 页面功能
|
||
|
||
- 🔍 搜索:输入关键词搜索食物相生相克关系
|
||
- 📂 分类:按相生/相克/蔬菜/水果等分类筛选
|
||
- 🔥 热门标签:一键查看热门食物,支持换一批
|
||
- 📄 分页:自动加载更多,滚动到底部触发
|
||
- 📋 复制+搜索:详情页支持复制信息和跳转搜索引擎
|
||
- ⬅️➡️ 导航:上一条/下一条快速切换
|
||
- 🔗 相关推荐:查看相关食物搭配
|
||
|
||
---
|
||
|
||
## [0.99.69] - 2026-05-01
|
||
|
||
### 🔧 重构: SearchEngine枚举和搜索布局提取为公开组件
|
||
|
||
#### 核心变更
|
||
|
||
| 变更项 | 说明 |
|
||
|--------|------|
|
||
| SearchEngine枚举提取 | 从 recipe_email_button.dart 提取到独立公开文件 search_engine.dart |
|
||
| 搜索引擎面板提取 | 新增 SearchEngineSheet 公开组件,支持任意页面调用搜索引擎选择 |
|
||
| 复制+搜索操作栏 | 新增 CopyAndSearchBar 公开组件,一键复制+跳转搜索 |
|
||
| HTML预览增强 | 食物相生相克详情sheet底部增加「📋 复制信息」和「🔍 去搜索」按钮 |
|
||
| 搜索引擎菜单 | HTML预览新增搜索引擎选择面板(百度/Bing/谷歌/搜狗) |
|
||
|
||
#### 新增文件
|
||
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `lib/src/utils/search_engine.dart` | SearchEngine枚举(百度/Bing/谷歌/自定义),公开可用 |
|
||
| `lib/src/widgets/common/search_engine_sheet.dart` | 搜索引擎选择面板 + SearchEngineCard + CopyAndSearchBar |
|
||
|
||
#### 修改文件
|
||
|
||
| 文件 | 变更内容 |
|
||
|------|----------|
|
||
| `recipe_email_button.dart` | 移除内联SearchEngine枚举和_SearchEngineDialog,改用公开组件 |
|
||
| `food_compatibility.html` | 详情sheet底部增加复制+去搜索按钮,新增搜索引擎选择面板 |
|
||
| `food_compat_query.html` | 详情页底部增加复制+去搜索按钮,新增搜索引擎选择面板 |
|
||
|
||
#### 公开组件使用方式
|
||
|
||
```dart
|
||
// 弹出搜索引擎选择Sheet
|
||
SearchEngineSheet.show(context, '搜索关键词');
|
||
|
||
// 复制+搜索操作栏
|
||
CopyAndSearchBar(copyText: '复制内容', searchKeyword: '搜索词');
|
||
|
||
// 单个搜索引擎卡片
|
||
SearchEngineCard(engine: SearchEngine.baidu, keyword: '搜索词');
|
||
```
|
||
|
||
---
|
||
|
||
## [0.99.68] - 2026-05-01
|
||
|
||
### 🆕 对比功能升级:2~4 槽位 + fl_chart 可视化 + 双击编辑
|
||
|
||
#### 核心变更
|
||
|
||
| 变更项 | 说明 |
|
||
|--------|------|
|
||
| 槽位扩展 | 对比从固定 2 槽位升级为 2~4 动态槽位 |
|
||
| 动态布局 | 1v1 左右对齐 / 1vN 上下排列自动切换 |
|
||
| fl_chart 可视化 | 新增 ComparisonBarChart(分组柱状图)+ ComparisonRadarChart(雷达图) |
|
||
| 双击编辑 | 核心营养数值支持双击编辑,带范围验证和编辑标识 |
|
||
| 颜色编码 | 4 个槽位分别用蓝/橙/绿/紫颜色标识 |
|
||
|
||
#### 新增文件
|
||
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `lib/src/widgets/comparison/comparison_bar_chart.dart` | 营养对比分组柱状图 (fl_chart BarChart) |
|
||
| `lib/src/widgets/comparison/comparison_radar_chart.dart` | 综合雷达图 (fl_chart RadarChart) |
|
||
| `lib/src/widgets/comparison/comparison_layout.dart` | 通用对比布局 (1v1左右 / 1vN上下) |
|
||
| `lib/src/widgets/comparison/comparison_editable_value.dart` | 可编辑数值组件 (双击+验证+标识) |
|
||
|
||
#### 修改文件
|
||
|
||
| 文件 | 变更内容 |
|
||
|------|----------|
|
||
| `comparison_controller.dart` | maxSlots 2→4,新增编辑状态管理 (editedValues/editedKeys),颜色编码 |
|
||
| `dish_comparison_page.dart` | 重构为动态多槽位,集成图表组件和可编辑数值 |
|
||
| `ingredient_comparison_page.dart` | 重构为动态多槽位,集成图表组件 |
|
||
| `comparison_pick_sheet.dart` | 适配多槽位选择,显示槽位颜色标识 |
|
||
|
||
#### 交互特性
|
||
- 🔀 1v1 模式:左右对比 + 交换按钮 + VS 徽章
|
||
- 📊 1vN 模式:上下对比 + 颜色进度条 + 添加按钮
|
||
- ✏️ 双击营养数值进入编辑,Enter/✅ 确认,❌ 取消
|
||
- 🏷️ 编辑后显示圆点标识 + 铅笔图标
|
||
|
||
---
|
||
|
||
## [0.99.67] - 2026-04-25
|
||
|
||
### 🎨 新增: 鸿蒙(HarmonyOS)分层图标
|
||
|
||
#### 问题
|
||
- 华为应用审核警告:`应用未配置图标的前景图和后景图,标准要求尺寸1024px*1024px`
|
||
- 原因:使用单层 `app_icon.png`,未采用鸿蒙分层图标规范
|
||
|
||
#### 解决方案
|
||
按照华为UX设计规范,生成 **foreground + background** 双层图标:
|
||
|
||
| 文件 | 尺寸 | 说明 |
|
||
|------|------|------|
|
||
| `background_icon.png` | 1024×1024 | 纯色不透明背景 RGB(237,189,109) 金黄色 |
|
||
| `foreground_icon.png` | 1024×1024 | 透明PNG,图标主体内容(含圆角遮罩) |
|
||
| `layered_image.json` | - | 分层图标配置文件 |
|
||
|
||
#### 配置变更
|
||
- `AppScope/app.json5`: `"icon"` → `$media:layered_image`
|
||
- `entry/src/main/module.json5`: `"icon"` + `"startWindowIcon"` → `$media:layered_image`
|
||
|
||
#### 生成位置
|
||
```
|
||
ohos/AppScope/resources/base/media/
|
||
├── background_icon.png # 背景层
|
||
├── foreground_icon.png # 前景层
|
||
├── layered_image.json # 分层配置
|
||
└── app_icon.png # (保留)
|
||
|
||
ohos/entry/src/main/resources/base/media/
|
||
├── background_icon.png
|
||
├── foreground_icon.png
|
||
├── layered_image.json
|
||
└── icon.png # (保留)
|
||
```
|
||
|
||
#### 工具脚本
|
||
- [gen_hmos_icons.py](scripts/gen_hmos_icons.py): 从源图标自动提取前景/背景层
|
||
|
||
#### 🔧 修复: layered_image.json 格式
|
||
- **问题**: 初始生成的 JSON 格式不符合华为规范,警告仍存在
|
||
- **原因**: 缺少外层 `"layered-image"` 包装键
|
||
- **修复前** (❌):
|
||
```json
|
||
{ "foreground": "...", "background": "...", "size": {...} }
|
||
```
|
||
- **修复后** (✅):
|
||
```json
|
||
{ "layered-image": { "foreground": "$media:foreground_icon", "background": "$media:background_icon" } }
|
||
```
|
||
|
||
---
|
||
|
||
|
||
## [0.99.47] - 2026-04-25
|
||
|
||
### 🐛 修复: PDF导出中文乱码 + 封面图片缺失
|
||
|
||
#### 问题1: 中文显示为空白/乱码
|
||
- **症状**: PDF导出后中文全部不显示,只有线条和序号,文件大小仅 6KB
|
||
- **根因链**:
|
||
1. 原字体为 **OpenType/CFF (.otf)** 格式 → pdf 包不识别为 Unicode 字体
|
||
2. 使用 fontTools 转换 .otf → .ttf 后 **缺少 `loca` (Location) 表**
|
||
3. `loca` 表存储字形偏移索引 → 缺失导致 PDF 无法定位中文字形 → 空白
|
||
- **诊断过程**:
|
||
- 创建 `scripts/test_pdf_cjk_debug.dart` 检测字体格式
|
||
- 创建 `scripts/check_ttf_integrity.py` 验证字体表完整性
|
||
- 发现转换后的 TTF 有 glyf 但**无 loca 表**
|
||
- **最终方案**: 从 GitHub [cjk-fonts-ttf](https://github.com/life888888/cjk-fonts-ttf) 下载**官方 TrueType 版本**
|
||
- NotoSansCJKsc-Regular.ttf (16.9MB, 65533 字形, glyf+loca 完整)
|
||
- NotoSansCJKsc-Bold.ttf (16.8MB, 同上)
|
||
- **验证结果**: PDF 从 7KB → **22.5KB** (中文字形成功嵌入),中文正常显示
|
||
|
||
#### 问题2: 封面图片不显示 (缓存有图但PDF无图)
|
||
- **症状**: 菜品详情页已显示封面图(缓存命中),但 PDF 导出无图片
|
||
- **根因**: `_getCachedCoverImage()` 仅用 `recipe.cover.toString()` 查缓存,与 **RecipeImage 实际使用的 URL 不匹配**
|
||
- RecipeImage 使用 URL 链: `_ensureHttps(cover)` → `pic/{picId}a.jpg` → `pic/{picId}b.jpg` → fallback
|
||
- 若 cover 为相对路径 `/pic/123a.jpg` 或 HTTP URL,缓存 key 不同 → **查不到**
|
||
- **修复**: 重写为**多候选 URL 链 + 缓存/下载双通道**
|
||
- 构建 3~5 个候选 URL(与 RecipeImage._buildUrlChain 一致)
|
||
- 每个 URL 先查缓存,未命中则网络下载
|
||
- 任一成功即返回,全部失败才放弃
|
||
|
||
#### 文件变更
|
||
| 文件 | 操作 | 说明 |
|
||
|------|------|------|
|
||
| `assets/fonts/NotoSansSC-Regular.ttf` | 替换 | 官方TTF版本(16.9MB),替换损坏的fontTools转换版(3.6MB) |
|
||
| `assets/fonts/NotoSansSC-Bold.ttf` | 替换 | 同上 |
|
||
| `recipe_export_button.dart` | 修改 | 图片加载重写: URL链(与RecipeImage一致) + 缓存/下载双通道 + 详细日志 |
|
||
| `scripts/test_pdf_app_flow.dart` | 新增 | App端完整PDF流程模拟脚本 |
|
||
| `scripts/check_ttf_integrity.py` | 新增 | 字体完整性验证脚本(glyf/loca/CJK映射) |
|
||
|
||
#### 保留的诊断工具
|
||
- `scripts/test_pdf_cjk_debug.dart` — 字体格式检测 + PDF生成测试
|
||
- `scripts/otf_to_ttf.py`, `otf_to_ttf_v2.py` — OTF→TTF转换脚本(备用)
|
||
|
||
|
||
## [0.99.46] - 2026-04-25
|
||
|
||
### 🐛 修复: PDF导出中文崩溃 "Invalid argument (string): Contains invalid characters"
|
||
|
||
- **错误**: 导出含中文菜谱PDF时崩溃,`Invalid argument (string): Contains invalid characters`
|
||
- **根因链**:
|
||
1. pdf 包的 `TtfParser.unicode` 仅当字体文件头为 `0x00010000` (TrueType) 时返回 `true`
|
||
2. 当前使用的 NotoSansSC 字体是 **OpenType/CFF (.otf)** 格式,文件头为 `0x4F54544F` ('OTTO')
|
||
3. `unicode=false` → pdf 包使用 **latin1 编码** → 中文字符超出范围 → 崩溃
|
||
- **诊断工具**: 新增 `scripts/test_pdf_cjk_debug.dart` 脚本,检测字体格式和PDF生成能力
|
||
- **解决方案**: 使用 Python fontTools 将 .otf (CFF轮廓) 转换为 .ttf (TrueType轮廓)
|
||
- 转换脚本: `scripts/otf_to_ttf.py` (CFF→glyf字形转换 + 文件头修补)
|
||
- 13086个CJK字形全部成功转换
|
||
- **文件变更**:
|
||
- `assets/fonts/NotoSansSC-Regular.ttf` — 新增(从.otf转换)
|
||
- `assets/fonts/NotoSansSC-Bold.ttf` — 新增(从.otf转换)
|
||
- `recipe_export_button.dart` — 字体路径 .otf → .ttf
|
||
- `scripts/otf_to_ttf.py` — 新增(OTF→TTF转换脚本)
|
||
- `scripts/test_pdf_cjk_debug.dart` — 新增(PDF CJK诊断脚本)
|
||
|
||
|
||
## [0.99.45] - 2026-04-25
|
||
|
||
### 📄 PDF导出重构: 替换 docs_gee.PdfGenerator → pdf ^3.12.0 (纯Dart包)
|
||
|
||
- **问题**: docs_gee 自带的 PdfGenerator 导出PDF内容空白,中文字体渲染异常
|
||
- **方案**: 引入成熟的 `pdf` 包(Dart生态最流行的PDF生成库,GitHub 2k+ stars)
|
||
- **适配**: 纯Dart包,仅改版本号 `3.12.0` → `3.12.0-ohos.1`,无需ohos目录
|
||
- **依赖冲突解决**: pdf→barcode→qr ^3.0.0 与本地qr包冲突,通过 dependency_overrides 解决
|
||
- **代码变更**:
|
||
- 新增 import: `package:pdf/pdf.dart`, `package:pdf/widgets.dart as pw`
|
||
- 重写 `_exportAsPdf()`: 使用 pw.Document + pw.MultiPage API
|
||
- 新增辅助方法: `_buildPdfTitle()`, `_pdfText()`, `_buildPdfIngredients()`, `_buildPdfSteps()`, `_buildPdfTags()`
|
||
- 支持封面图片、中文TTF字体、自动分页、页脚、食材表格、步骤编号圆圈、标签胶囊样式
|
||
- **文件变更**:
|
||
- `packages/pdf/` — 新增(从 GitHub 克隆 dart_pdf)
|
||
- `lib/src/widgets/recipe_detail/interaction/recipe_export_button.dart` — 重写PDF导出逻辑
|
||
- `pubspec.yaml` — 添加 pdf 本地依赖 + qr dependency_overrides
|
||
|
||
|
||
## [0.99.44] - 2026-04-25
|
||
|
||
### 🔴 紧急修复: 鸿蒙端启动闪退 (Invalid relative path 9001005) — 全面排查
|
||
|
||
- **错误**: `Error message: Invalid relative path, Error code: 9001005`
|
||
- **堆栈**: `copyResource → startInitialization → checkLoader → setupFlutterEngine → onCreate`
|
||
- **根因**: 9个纯Dart包被错误添加了ohos目录,引用不存在的`libs/flutter.har`,Flutter引擎启动时逐一扫描全部模块资源
|
||
- **排查过程**:
|
||
1. 删除 docs_gee/ohos(第一轮)→ 仍闪退
|
||
2. 全量扫描所有 packages 的 ohos-package.json5 → 发现共12个包有ohos目录
|
||
3. 逐个检查 Plugin 代码 → 区分空壳 vs 真插件
|
||
|
||
#### 删除的纯Dart空壳包 (9个)
|
||
| 包名 | Plugin代码 | 原因 |
|
||
|------|-----------|------|
|
||
| docs_gee | `DocsGeePlugin {}` | 纯Dart文档生成库 |
|
||
| mailer | `MailerPlugin {}` | 纯Dart邮件库 |
|
||
| qr | `QrPlugin {}` | 纯Dart二维码生成 |
|
||
| flutter_card_swiper | `FlutterCardSwiperPlugin {}` | 纯Dart卡片滑动组件 |
|
||
| flutter_markdown_plus | `FlutterMarkdownPlusPlugin {}` | 纯Dart Markdown渲染 |
|
||
| cached_network_image | `CachedNetworkImagePlugin {}` | 纯Dart网络图片缓存 |
|
||
| flutter_staggered_grid_view | `FlutterStaggeredGridViewPlugin {}` | 纯Dart瀑布流布局 |
|
||
| badges | `BadgesPlugin {}` | 纯Dart徽章组件 |
|
||
| fl_chart | `FlChartPlugin {}` | 纯Dart图表库 |
|
||
|
||
#### 修复的真插件 (3个)
|
||
| 包名 | 问题 | 修复 |
|
||
|------|------|------|
|
||
| mobile_scanner | 缺 libs/flutter.har (Camera/Barcode真实原生代码) | 从 fluttertoast_ohos 复制 har 文件到 libs/ |
|
||
| file_picker | 缺 har/flutter.har (文件选择器原生代码) | 创建 har/ 目录并复制 flutter.har |
|
||
| fluttertoast_ohos | 原始正确,无需修改 | ✅ 已有 libs/flutter.har |
|
||
|
||
#### 清理的example目录 (3个)
|
||
- get/example_nav2/ohos, get/example/ohos, file_picker/example/ohos(不参与主项目编译)
|
||
|
||
#### 最终保留的ohos目录 (仅3个真插件)
|
||
```
|
||
packages/
|
||
├── mobile_scanner/ohos/ ← libs/flutter.har ✅
|
||
├── file_picker/ohos/ ← har/flutter.har ✅
|
||
└── fluttertoast_ohos/ohos/ ← libs/flutter.har ✅
|
||
```
|
||
|
||
### 📝 文档更新: 修正纯Dart包适配指导
|
||
|
||
- **文件**: `packages/本地已适配鸿蒙的库.md`
|
||
- **修正内容**:
|
||
1. 删除「创建空壳 ohos 目录」的完整模板(Index.ets / Plugin.ets / module.json5 等)
|
||
2. 纯Dart包适配步骤简化为:拉取源码 → 改版本号 → 引用项目 → 完成
|
||
3. 新增「⛔ 血泪教训」章节,记录 9001005 闪退根因和排查过程
|
||
4. 总览表新增「类型」「ohos目录」列,明确区分 🟢纯Dart vs 🔴原生插件
|
||
5. 兼容性总览表新增类型列,补充 file_picker / fluttertoast_ohos 条目
|
||
- **核心原则**: 纯Dart包 = 不需要任何ohos文件!Flutter AOT编译直接运行
|
||
|
||
---
|
||
|
||
## [0.99.43] - 2026-04-25
|
||
|
||
### 🐛 修复 — 3项关键Bug(鸿蒙端)
|
||
|
||
#### 🔴 修复: 鸿蒙端启动闪退 (Invalid relative path 9001005)
|
||
- **错误**: `Error message: Invalid relative path, Error code: 9001005`
|
||
- **堆栈**: `copyResource → startInitialization → checkLoader → setupFlutterEngine → onCreate`
|
||
- **根因**: `docs_gee` 是纯Dart库,但被错误配置为鸿蒙原生har模块
|
||
- `ohos/oh-package.json5` 引用不存在的路径 `"@ohos/flutter_ohos": "file:libs/flutter.har"`
|
||
- Flutter引擎启动时尝试复制该模块资源 → 路径不存在 → 闪退
|
||
- **修复**: 删除 `packages/docs_gee/docx_generator/ohos/` 整个目录
|
||
- 纯Dart库不需要原生适配,删除后不再触发资源复制
|
||
- **教训**: 纯Dart包**禁止**添加 ohos 目录,会导致启动阶段崩溃
|
||
- **文件**: `packages/docs_gee/docx_generator/ohos/` (已删除)
|
||
|
||
#### 🟠 修复: 鸿蒙端文件选择器不弹出
|
||
- **问题**: 导出PDF/Word时点击保存 → 不弹选择器 → 直接报"已取消保存"
|
||
- **根因**: `FilePickerPlugin.ets` 第101行读取了 bytes 参数但未传给 delegate
|
||
- `delegate.saveFile()` 检查 `this.bytesList` 为空 → 报错返回 null
|
||
- **修复**:
|
||
- `FilePickerPlugin.ets`: 调用 saveFile 前调用 `delegate?.setSaveBytes(fileName, [bytes])`
|
||
- `FilePlckerDelegate.ets`: 新增 `setSaveBytes()` 方法设置 savaFilePath 和 bytesList
|
||
- **文件**: `packages/file_picker/ohos/src/main/ets/components/plugin/FilePickerPlugin.ets`
|
||
- **文件**: `packages/file_picker/ohos/src/main/ets/components/plugin/FilePlckerDelegate.ets`
|
||
|
||
#### 🔴 修复: PDF全空白(10MB)
|
||
- **问题**: 导出PDF文件大小10MB但内容完全空白
|
||
- **根因**: CIDFont + Identity-H 编码的字体不能用 `(text)` 格式输出 Tj 操作符
|
||
- WinAnsiEncoding 的括号字符串语法与 Identity-H 十六进制编码冲突
|
||
- PDF阅读器无法解析 → 显示空白页
|
||
- **修复**:
|
||
- 新增 `_toHexEncodedText(text)`: 将字符串转为 UTF-16BE 十六进制
|
||
- 新增 `_formatTextForTj(text)`: 有嵌入字体用 `<hex>`,无嵌入用 `(text)`
|
||
- 修改 `_renderParagraph()` 和表格渲染中的 Tj 输出
|
||
- **验证**: `scripts/test_pdf_export_full.dart` 全流程验证脚本(7项测试)
|
||
- **文件**: `packages/docs_gee/docx_generator/lib/src/pdf_generator.dart`
|
||
|
||
---
|