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)
修改文件
| 文件 |
变更内容 |
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 |
详情页底部增加复制+去搜索按钮,新增搜索引擎选择面板 |
公开组件使用方式
[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
生成位置
工具脚本
🔧 修复: layered_image.json 格式
- 问题: 初始生成的 JSON 格式不符合华为规范,警告仍存在
- 原因: 缺少外层
"layered-image" 包装键
- 修复前 (❌):
- 修复后 (✅):
[0.99.47] - 2026-04-25
🐛 修复: PDF导出中文乱码 + 封面图片缺失
问题1: 中文显示为空白/乱码
- 症状: PDF导出后中文全部不显示,只有线条和序号,文件大小仅 6KB
- 根因链:
- 原字体为 OpenType/CFF (.otf) 格式 → pdf 包不识别为 Unicode 字体
- 使用 fontTools 转换 .otf → .ttf 后 缺少
loca (Location) 表
loca 表存储字形偏移索引 → 缺失导致 PDF 无法定位中文字形 → 空白
- 诊断过程:
- 创建
scripts/test_pdf_cjk_debug.dart 检测字体格式
- 创建
scripts/check_ttf_integrity.py 验证字体表完整性
- 发现转换后的 TTF 有 glyf 但无 loca 表
- 最终方案: 从 GitHub 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
- 根因链:
- pdf 包的
TtfParser.unicode 仅当字体文件头为 0x00010000 (TrueType) 时返回 true
- 当前使用的 NotoSansSC 字体是 OpenType/CFF (.otf) 格式,文件头为
0x4F54544F ('OTTO')
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引擎启动时逐一扫描全部模块资源
- 排查过程:
- 删除 docs_gee/ohos(第一轮)→ 仍闪退
- 全量扫描所有 packages 的 ohos-package.json5 → 发现共12个包有ohos目录
- 逐个检查 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个真插件)
📝 文档更新: 修正纯Dart包适配指导
- 文件:
packages/本地已适配鸿蒙的库.md
- 修正内容:
- 删除「创建空壳 ohos 目录」的完整模板(Index.ets / Plugin.ets / module.json5 等)
- 纯Dart包适配步骤简化为:拉取源码 → 改版本号 → 引用项目 → 完成
- 新增「⛔ 血泪教训」章节,记录 9001005 闪退根因和排查过程
- 总览表新增「类型」「ohos目录」列,明确区分 🟢纯Dart vs 🔴原生插件
- 兼容性总览表新增类型列,补充 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