Files
kitchen/CHANGELOG.md
Developer 3a056ca676 feat: 新增菜品对比和食物相生相克功能
- 新增菜品对比功能,支持1v1左右和1vN上下布局切换
- 新增食物相生相克查询工具,包含API服务和详情页面
- 优化平台工具类,移除冗余的鸿蒙系统检测逻辑
- 更新版本号至1.5.1,修改更新日志和版本说明
- 修复多个页面列表分隔符构建器参数警告
- 新增雷达图组件,用于展示多维度对比数据
- 新增可编辑数值组件,支持双击编辑和范围验证
- 优化导出按钮,增加对比功能入口
- 新增搜索引擎枚举工具类
- 更新应用路由配置,添加对比和相生相克相关页面
2026-05-01 09:13:27 +08:00

32 KiB
Raw Permalink Blame History

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

🐛 修复: 对比控制器超时保护补全

问题

ComparisonControlleraddDishloadSlotDetailloadIngredientSlotDetail 三个方法调用 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/_formatAxisValueleftTitles 改为 Y 轴刻度模式

[0.99.76] - 2026-05-01

🐛 修复: 点击添加食材等待30秒 + 条形图左侧无坐标值

问题130秒延迟

问题 原因
添加食材卡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

🐛 修复: 食材对比条形图只显示一个食材

根因

问题 原因
只显示一个条形 第二个食材营养值全为0toY=(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始终调用APIAPI不返回nutrition字段覆盖了详情页传入的完整数据
选择面板食材无营养 搜索/浏览记录/快速选择的食材无nutritionAPI也补不上

修复方案

变更项 说明
源数据优先策略 addIngredient检测传入食材是否已有nutrition有则锁定不被API覆盖
API仅补充缺失字段 有营养数据时API只补充image/categoryName等nutrition保持源数据
无营养时本地补充 从选择面板添加的食材无nutritionmerge后从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 详情页底部增加复制+去搜索按钮,新增搜索引擎选择面板

公开组件使用方式

// 弹出搜索引擎选择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                 # (保留)

工具脚本

🔧 修复: layered_image.json 格式

  • 问题: 初始生成的 JSON 格式不符合华为规范,警告仍存在
  • 原因: 缺少外层 "layered-image" 包装键
  • 修复前 ():
    { "foreground": "...", "background": "...", "size": {...} }
    
  • 修复后 ():
    { "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 下载官方 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.jpgpic/{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内容空白中文字体渲染异常
  • 方案: 引入成熟的 pdfDart生态最流行的PDF生成库GitHub 2k+ stars
  • 适配: 纯Dart包仅改版本号 3.12.03.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.harFlutter引擎启动时逐一扫描全部模块资源
  • 排查过程:
    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