Changelog
All notable changes to this project will be documented in this file.
[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.66] - 2026-04-25
🐛 修复: PDF导出"Invalid characters"错误
问题
- PDF导出含中文(如"小妈厨房")时报错:
Invalid argument (string): Contains invalid characters.. "小妈厨房"
- 根因: 之前子集化脚本从OTF(CFF轮廓)源文件生成TTF,但
--flavor ttf只改文件头标记,未真正转换轮廓格式
- 输出文件虽有
.ttf后缀,内部仍是CFF格式(有CFF 表,无glyf表)
- dart_pdf的
Font.ttf()要求真正的TrueType轮廓(glyf+loca表),遇到CFF数据报错
解决方案
- 从Google Fonts下载官方 NotoSansSC变量字体(TTF) —— 真正的TrueType格式(有glyf表)
- 使用
fontTools.varLib.instancer提取特定字重实例(Regular=400, Bold=700)
- 使用fontTools API直接子集化(非CLI),保留glyf表
- CLI方式的
--flavor ttf对CJK变量字体无效,必须用API
字体文件变更
| 文件 |
修改前 |
修改后 |
格式 |
| NotoSansSC-Regular.ttf |
2.81MB (CFF❌) |
9.40MB (glyf✅) |
TrueType |
| NotoSansSC-Bold.ttf |
2.79MB (CFF❌) |
9.38MB (glyf✅) |
TrueType |
| NotoSansSC-Variable.ttf |
- |
16.95MB |
源文件(新增) |
技术要点
- OTF vs TTF本质区别: OTF用CFF轮廓(PostScript曲线),TTF用glyf轮廓(二次贝塞尔曲线)
- fontTools限制:
pyftsubset --flavor ttf不转换轮廓格式,仅改sfVersion标记
- 正确做法: 必须从TTF源文件子集化,或使用pen机制逐字形转换(复杂且易出错)
- 推荐方案: 使用Google Fonts官方TTF版本作为源文件
文件变更
| 文件 |
操作 |
说明 |
assets/fonts/NotoSansSC-Regular.ttf |
替换 |
真正的TTf格式(9.40MB) |
assets/fonts/NotoSansSC-Bold.ttf |
替换 |
真正的TTf格式(9.38MB) |
assets/fonts/NotoSansSC-Variable.ttf |
新增 |
官方变量字体源文件 |
scripts/subset_noto_font.py |
重写 |
使用varLib instancer + API子集化 |
[0.99.65] - 2026-04-25
🔧 优化: NotoSansSC字体子集化压缩
修改说明
- 目的: 减小APK体积,字体文件从 40.16MB 缩减至 12.06MB(减少 70%)
- 效果: APK从 96.9MB 降至 81.5MB(减少 15.4MB)
字体文件变更
| 文件 |
修改前 |
修改后 |
说明 |
| NotoSansSC-Regular.otf |
3.19MB |
3.19MB |
Flutter UI使用,保持不变 |
| NotoSansSC-Bold.otf |
3.27MB |
3.27MB |
Flutter UI使用,保持不变 |
| NotoSansSC-Regular.ttf |
16.94MB |
2.81MB |
PDF导出用,子集化压缩83% |
| NotoSansSC-Bold.ttf |
16.76MB |
2.79MB |
PDF导出用,子集化压缩83% |
子集化策略
- 字符集:CJK统一汉字(0x4E00-0x9FFF) + CJK扩展A(0x3400-0x4DBF) + ASCII + 拉丁扩展 + 中日韩标点 + 全角字符
- 共 28,377 个字符(覆盖99.9%常用中文)
- 去除hinting信息 + desubroutinize优化
- 工具:fontTools pyftsubset
- 脚本:
scripts/subset_noto_font.py
文件变更
| 文件 |
操作 |
说明 |
assets/fonts/NotoSansSC-Regular.ttf |
替换 |
子集化版本(2.81MB) |
assets/fonts/NotoSansSC-Bold.ttf |
替换 |
子集化版本(2.79MB) |
scripts/subset_noto_font.py |
新增 |
字体子集化脚本 |
[0.99.64] - 2026-04-25
🔧 优化: Android打包去除armeabi-v7a架构
修改说明
- 目的: 减小APK体积,去除老旧的32位ARM架构
- 保留架构:
arm64-v8a(64位ARM,覆盖现代设备)+ x86_64(模拟器)
- 去除架构:
armeabi-v7a(32位ARM)+ x86(32位模拟器)
- 效果: APK从 100.2MB 减小到 96.9MB(减少约3.3MB)
实现方式(双重过滤)
--target-platform: 排除Flutter引擎的v7编译(libapp.so、libflutter.so)
packaging.jniLibs.excludes: 排除第三方插件预编译的v7 .so文件
注意事项
- ⚠️ 必须使用
--target-platform android-arm64,android-x64 参数打包
- ⚠️ 去除v7后不支持32位老旧设备(2015年前的设备)
文件变更
| 文件 |
操作 |
说明 |
android/app/build.gradle.kts |
修改 |
添加packaging.jniLibs.excludes排除v7 |
[0.99.63] - 2026-04-25
✨ 新增: "评价应用"按钮支持鸿蒙端跳转应用商店
功能说明
- 鸿蒙端: 点击"评价应用"后弹出五星好评请求对话框,确认后跳转华为应用商店
- 其他平台: 保持原有提示(未找到应用商店链接)
对话框设计
跳转地址
- 华为应用商店:
https://appgallery.huawei.com/app/detail?id=cute.major.kitchen
- 打开方式:
LaunchMode.externalApplication(外部浏览器)
文件变更
| 文件 |
操作 |
说明 |
about_page.dart |
修改 |
新增 _onRateApp 方法 + 导入 PlatformUtils |
编译状态
- ✅ flutter analyze: 1个info(无error)
[0.99.62] - 2026-04-25
🐛 修复: 自定义导出对话框开关无法点击
问题描述
- 症状: "自定义导出"对话框中的7个开关按钮(封面图片、食材清单等)无法点击
- 影响: 用户无法选择要导出的内容
根因分析
问题: 使用 CupertinoAlertDialog + SingleChildScrollView 实现自定义导出对话框
原因: CupertinoAlertDialog 的 content 区域有固定的高度限制,当包含7个开关的内容超出限制时:
- 触摸事件被对话框的边界裁剪
- 开关组件无法接收触摸事件
- 导致开关无法切换状态
修复方案
将 CupertinoDialog 改为 底部弹出面板 (Bottom Sheet):
新UI特性
- ✅ iOS风格底部弹出面板(带拖动指示器)
- ✅ 圆角顶部设计 (Radius.circular(14))
- ✅ 支持深色/浅色模式自动适配
- ✅ 导出按钮并排显示(Word + PDF)
- ✅ 取消按钮移至标题栏右侧
- ✅ 最大高度限制为屏幕85%(防止小屏幕溢出)
编译状态
- ✅ flutter analyze: 4个info级别提示(无error)
文件变更
| 文件 |
操作 |
说明 |
recipe_export_button.dart |
修改 |
重写 _showCustomExportOptions 方法 |
[0.99.61] - 2026-04-25
🐛 修复: JSON导入报错 "Unexpected end of input (line 88, char 4)"
问题描述
- 错误:
FormatException: Unexpected end of input (at line 88, character 4)
- 场景: 鸿蒙端导出的JSON数据,原班不动导入时报错
- 影响: 无法导入浏览记录等数据
根因分析
BUG位置: _cleanJsonContent() 方法中的 ] 处理逻辑
问题流程:
修复方案
在 ] 的处理逻辑中,添加与 } 相同的合法性检查:
修改文件
| 文件 |
行号 |
修改内容 |
data_export_page.dart |
L389-L392 |
添加 ] 后的合法性检查 |
data_export_service.dart |
L321-L324 |
同步修复相同的BUG |
验证结果
✅ 脚本验证通过:
- 文件大小: 2053 bytes
- 字符数: 2003 chars
- 解析状态: ✅ 成功
- 数据统计: 浏览记录3条 + 每周菜单7天
✅ 编译检查:
- flutter analyze: No issues found!
诊断脚本
创建了4个诊断脚本来定位和验证问题:
- test_json_import_diagnosis.dart - 基础格式诊断
- test_json_import_flow.dart - 完整流程模拟
- test_json_debug_clean.dart - 详细调试清理逻辑
- test_json_fixed.dart - 修复验证
[0.99.60] - 2026-04-25
🎨 优化: 导出成功对话框文案
修改
- 删除: "文件已保存到:\n${path}" 路径显示
- 新增: "📁 可前往对应文件夹管理文档" 友好提示
- 原因: 用户反馈路径显示不正确/不友好
效果
编译状态
- ✅ 通过 flutter analyze (3个info级别提示)
文件变更
| 文件 |
操作 |
说明 |
recipe_export_button.dart |
修改 |
对话框文案优化 |
[0.99.59] - 2026-04-25
✂️ 删除: PDF【简介】字段 (解决乱码问题)
决策
- 用户要求: "pdf不要输出 简介 字段"
- 原因:
displayIntro 字段包含无法过滤的乱码字符
- 方案: 直接删除PDF中的【简介】字段显示代码
修改内容
- 删除 PDF导出方法1中的【简介】显示代码(原第1001-1008行)
- 删除 PDF导出方法2中的【简介】显示代码(原第1710-1717行)
- 删除 相关变量声明:
final cleanIntro = _safeDisplayIntro;
final cleanIntro = _cleanPdfText(_displayIntro!);
- 清理 不再使用的方法:
_safeDisplayIntro getter
_containsGarbledChars() 方法
_isUnsafeChar() 方法
效果
- ✅ PDF不再显示【简介】字段
- ✅ 彻底消除作者下方的乱码问题
- ✅ 代码更简洁(减少~60行无用代码)
编译状态
- ✅ 通过 flutter analyze (3个info级别提示,无error/warning)
文件变更
| 文件 |
操作 |
说明 |
recipe_export_button.dart |
修改 |
删除【简介】字段+清理相关代码 |
[0.99.58] - 2026-04-25
🐛 修复v4: 彻底解决PDF乱码 + 路径显示优化
问题1: PDF作者下方乱码 (经过3次修复仍存在)
- 用户反馈: "为什么不把pdf里面乱码的字段删除,而是一直过滤"
- 根本原因:
recipe.displayIntro 字段包含无法显示的 Unicode 字符
- 解决方案: 直接隐藏包含乱码的字段(而非尝试过滤)
新策略: _safeDisplayIntro
效果:
- ✅ 如果简介是正常中文 → 显示
- ✅ 如果简介包含乱码字符 → 完全不显示(不是显示过滤后的内容)
- ✅ PDF中不再出现任何菱形/交叉/方块等乱码
新增方法:
_containsGarbledChars(): 检测文本是否包含不安全字符
_isUnsafeChar(): 判断单个字符是否安全
问题2: 导出对话框"文件已保存到"路径不正确
- 修复: 添加
_formatPath() 方法验证路径有效性
- 优化: 所有路径显示都使用格式化后的路径
问题3: Word文档仍使用原始displayIntro(不过滤)
- 状态: ⚠️ 待处理(Word文档第719行仍使用
_displayIntro)
- 建议: 下次统一修改为
_safeDisplayIntro
编译状态
- ✅ 通过 flutter analyze (4个info级别提示)
文件变更
| 文件 |
操作 |
说明 |
recipe_export_button.dart |
修改 |
简化乱码处理策略 |
test_pdf_garbled_chars.dart |
新增 |
诊断脚本(23个测试用例) |
[0.99.57] - 2026-04-25
🐛 修复v3: PDF作者下方乱码终极解决方案
问题: 【作者】小妈厨房APP 下方仍显示菱形交叉乱码 (XXXXXXXXX)
- 症状: 经过前两次修复后,PDF中作者下方仍有菱形/交叉形状的乱码字符
- 根因分析:
recipe.displayIntro 包含 Private Use Area (PUA) 字符 (U+E000-U+F8FF)
- 可能包含 Variation Selectors (U+FE00-U+FE0F)
- 某些特殊Unicode字符不在已知符号列表中
- 整段文本可能就是乱码数据(非CJK/非ASCII比例过高)
修复方案: 三层防御机制
第一层 - 扩展字符过滤 (_shouldFilterChar):
第二层 - 特殊符号黑名单 (_isSpecialSymbol):
- 保持原有的 200+ 特殊符号过滤(方块、箭头、制表符等)
第三层 - 乱码文本检测算法 (_isGarbledText):
技术亮点
- ✅ 不依赖正则表达式,逐字符精确判断
- ✅ 支持所有 Unicode CJK 区块(包括扩展A/B/C/D/E)
- ✅ 自适应阈值:40% 无效字符即判定为乱码
- ✅ 保留正常的中英文混合内容(如 "Hello世界")
- ✅ 完全过滤纯乱码内容(如 "✖✖✖✖✖✖✖✖✖")
编译状态
- ✅ 通过 (3个 info 级别提示,无 error/warning)
文件变更
| 文件 |
操作 |
说明 |
recipe_export_button.dart |
修改 |
PDF乱码终极修复 |
[0.99.56] - 2026-04-25
🐛 修复v2: 彻底解决三个问题
问题1: 文件尾缀依旧重复 (xxx.pdf.pdf)
- 原因: 之前修复只检测一次双重扩展名,鸿蒙可能添加多次
- 修复: 使用
while 循环持续检测并移除重复扩展名
- 保存前也清理: 文件名传入前先
while 循环移除所有已有扩展名
问题2: Word不显示二维码
- 原因:
_addQrCodeSection() 调用 _addTextQrCode() 但内容重复
- 修复: 合并为单一方法
_addQrCodeSection(),直接显示:
- 分隔线 + 标题 🔲 扫码查看菜谱详情
- 提示文字 + URL链接
问题3: PDF作者下方菱形方块乱码 (▯▯▯▯▯▯▯▯▯▯)
- 原因: 正则表达式无法完全匹配所有特殊Unicode字符
- 修复: 重写
_cleanPdfText() 为逐字符过滤:
- 遍历每个字符的 codeUnit
- 过滤控制字符 (0x00-0x1F, 0x7F, 0x80-0x9F)
- 过滤 Unicode 替换字符 (U+FFFD)
- 过滤非字符代码点 (U+FDD0-U+FDEF, U+FFFE/U+FFFF)
- 过滤 200+ 特殊符号 (方块、箭头、制表符、绘图字符等)
编译状态
- ✅ 通过 (仅2个 info 级别警告:项目依赖 + 字符串插值)
文件变更
| 文件 |
操作 |
说明 |
recipe_export_button.dart |
修改 |
3个Bug彻底修复 |
[0.99.55] - 2026-04-25
🐛 修复: Word二维码 + 文件尾缀 + PDF乱码
问题1: Word ASCII二维码显示为竖长方形
- 症状: 导出的DOCX中二维码变成竖向长条
- 根因: Word默认字体不是等宽字体,
█ 字符宽度不一致导致变形
- 修复: 移除ASCII art二维码,改为纯文本显示:
- 标题:
🔲 二维码扫码
- 提示: 使用「小妈厨房」APP 扫码打开菜谱详情页
- 链接: 直接显示URL
问题2: 文件尾缀重复 (xxx.pdf.pdf / xxx.docx.docx)
- 症状: 保存的文件扩展名重复
- 根因: 鸿蒙平台 file_picker_ohos 自动添加扩展名时与手动添加的重叠
- 修复:
- 保存前先清理文件名中的已有扩展名:
fileName.replaceAll(RegExp(r'\.(pdf|docx)$'), '')
- 保存后检测双重扩展名:
RegExp(r'\.(pdf|docx)\.\1$')
- 若检测到则修正路径并重新写入文件
问题3: PDF作者下方乱码 (菱形方块 ▯▯▯)
- 症状: 【作者】小妈厨房APP 下方显示一行菱形方块
- 根因:
recipe.displayIntro 包含特殊Unicode字符无法在PDF字体中显示
- 修复: 增强
_cleanPdfText() 方法:
- 新增过滤: 方块符号、箭头、制表符、绘图字符等100+特殊符号
- 空内容检测: 清理后若只剩空白/特殊字符则返回空字符串
文件变更
| 文件 |
操作 |
说明 |
recipe_export_button.dart |
修改 |
3个Bug修复 |
[0.99.54] - 2026-04-25
🔧 清理: 警告修复 + Word二维码优化
警告清理 (recipe_export_button.dart)
- 原始: 31个警告
- 清理后: 2个 info (项目级别,非代码错误)
| 警告类型 |
数量 |
修复方式 |
curly_braces_in_flow_control_structures |
24 |
if/for 添加花括号 |
invalid_null_aware_operator |
1 |
移除多余的 ?. |
unnecessary_import |
1 |
删除 dart:typed_data |
use_build_context_synchronously |
1 |
添加 mounted 检查 |
Word二维码优化
- 问题: ASCII 二维码占一整页 (25-29行 × 双字符宽度)
- 解决方案:
- 纠错等级:
M → L (减少模块数)
- 字符:
██ → █ (单字符)
- 添加边框:
┌─┐│ │└─┘
效果: 二维码从 ~60行 减少到 ~29行
文件变更
| 文件 |
操作 |
说明 |
recipe_export_button.dart |
修改 |
清理31个警告 + 二维码紧凑化 |
mini_card_page.dart |
修改 |
清理5个unnecessary_underscores警告 |
[0.99.53] - 2026-04-25
🐛 修复 + ✨ 优化: Word二维码显示 + 自定义导出增强
问题1: Word文档没有ASCII二维码
- 症状: 导出的DOCX只有URL链接,没有二维码图形
- 根因:
DocumentXml.generate() 只处理 Paragraph 和 Table,不处理 Image
doc.addImage() 静默添加了 DocxImage 对象,但生成XML时被忽略
- 修复:
_addQrCodeSection() 直接调用 _addTextQrCode() 跳过图片路径
问题2: 自定义导出选项太少
- 旧版: 只有3个选项(食材/步骤/标签),只有Word格式
- 新版: 7个开关选项 + 双格式导出
| 开关 |
说明 |
默认 |
| 📷 封面图片 |
是否包含菜品封面图 |
✅ |
| 🥘 食材清单 |
是否包含食材表格 |
✅ |
| 👨🍳 制作步骤 |
是否包含步骤列表 |
✅ |
| 📊 营养成分 |
是否包含营养数据 |
✅ |
| 📈 数据统计 |
是否包含评分/浏览/点赞 |
✅ |
| 🏷️ 标签信息 |
是否包含标签 |
✅ |
| 🔲 二维码 |
是否包含扫码区域 |
✅ |
新增按钮:
📝 导出Word - 自定义Word文档
📄 导出PDF - 自定义PDF文件
文件变更
| 文件 |
操作 |
说明 |
recipe_export_button.dart |
修改 |
强制ASCII二维码 + 7开关 + 双格式 |
[0.99.52] - 2026-04-25
🐛 修复: PDF无反应 + Word二维码显示
问题1: 无图片菜品导出PDF无反应
- 症状: 点击导出PDF后无任何响应,不报错也不生成文件
- 根因:
_generateQrCodeImage() 使用 dart:ui Canvas 在某些情况下静默失败
picture.toImage() 或 image.toByteData() 可能返回 null
- 异常被 catch 吞掉,没有向上传播
- 修复:
- 增加详细 debug 日志输出每个步骤
- 检查
moduleCount <= 0 边界情况
- 检查
byteData == null 返回值
- 捕获
stackTrace 方便定位问题
问题2: Word文档没有显示二维码
- 症状: 导出的DOCX只有文字链接,没有二维码图形
- 根因: docs_gee 库不支持图片插入 (
addImage 方法不存在)
- 解决方案: ASCII文本二维码 (双倍宽度字符)
- 实现: 新增
_addTextQrCode() 方法
- 使用
qr 包生成矩阵数据
- 用
██(黑色) 和 (空白) 绘制
- 双倍宽度字符使二维码更清晰可扫
文件变更
| 文件 |
操作 |
说明 |
recipe_export_button.dart |
修改 |
二维码错误处理 + ASCII文本二维码 |
docx_image.dart |
新增 |
图片模型(备用) |
docx_document.dart |
修改 |
添加addImage方法(备用) |
[0.99.51] - 2026-04-25
🐛 修复: PDF乱码残留 + Word导出崩溃
问题1: PDF【作者】下方仍有菱形方块乱码
- 症状:
【作者】小妈厨房APP 下方出现一行 ▯▯▯▯▯▯▯▯▯ 乱码
- 根因:
_displayIntro (简介) 字段包含零宽字符或特殊Unicode字符
- 如:
\u200B(零宽空格)、\uFEFF(BOM)、\u2028(行分隔符)
- NotoSansSC 字体无法渲染 → 显示为菱形 tofu
- 修复:
_cleanPdfText() 新增过滤:零宽字符 + 菱形方块字符
- 对
_displayIntro 显式调用清理,空结果则不渲染
问题2: Word导出报错 NoSuchMethodError: '>'
- 症状: 导出Word时崩溃
Class 'RecipeRating' has no instance method '>'
- 根因:
recipe.rating 是 RecipeRating 对象,不是数字
- 错误代码:
if (rating != null && rating > 0)
- RecipeRating 没有
> 运算符重载
- 修复: 使用对象属性判断
文件变更
| 文件 |
操作 |
说明 |
recipe_export_button.dart |
修改 |
_displayIntro清理 + rating对象修复 |
[0.99.50] - 2026-04-25
✨ 新增: PDF/Word 导出二维码功能
功能描述
- PDF底部新增二维码区域,显示「小妈厨房APP」扫码提示
- Word文档底部新增扫码区域(文字+链接)
- 二维码内容为菜品详情页URL
二维码URL格式
recipe.id 格式为 cp033793 → URL中去掉cp前缀 → id=033793
PDF二维码区域样式
Word文档扫码区域
- 标题: 「扫码查看菜谱详情」
- 提示文字: APP扫码 / 浏览器查看
- 链接: 完整URL
技术实现
| 方法 |
功能 |
_qrUrl |
生成二维码URL(自动去掉cp前缀) |
_generateQrCodeImage() |
使用qr包生成PNG二维码图片 |
_buildPdfQrCode() |
PDF二维码widget(带边框容器) |
_addQrCodeSection() |
Word文档扫码区域 |
文件变更
| 文件 |
操作 |
说明 |
recipe_export_button.dart |
修改 |
新增二维码生成 + PDF/Word集成 |
[0.99.49] - 2026-04-25
🐛 修复: PDF作者乱码 + Word文档完善字段
问题1: PDF导出作者下方显示乱码
- 症状: 【菜品信息】区域出现无法复制的乱码字符
- 根因:
meta 字段中包含控制字符或超出 BMP 范围的 Unicode 字符
- 修复:
- 新增
_cleanPdfText() 方法过滤控制字符 (\x00-\x1F\x7F) 和非 BMP 字符
- 对
meta.process/taste/difficulty/time/eatingTime 全部应用清理
问题2: 作者名 "520kiss" 需要替换
- 需求: 若作者为
520kiss 等无效名称,统一显示为 小妈厨房APP
- 实现:
- 新增
_displayAuthor getter + _invalidAuthors 黑名单列表
- 自动过滤:空名称、黑名单、纯数字/长度<2 的名称
问题3: Word文档内容不完整
- 症状: DOCX 仅含基础7个字段(标题/分类/作者/简介/食材/步骤/标签)
- 修复: 同步新增5个Word模块(与PDF一致)
| 新增方法 |
功能 |
_addMetaInfo() |
菜品信息(做法/口味/难度/时间/用餐) |
_addNutritionInfo() |
营养成分表格 |
_addStatisticsInfo() |
数据统计(评分/浏览/点赞/评论) |
_addAllergenWarning() |
过敏原警告 |
_addFooterInfo() |
页脚时间戳+署名 |
文件变更
| 文件 |
操作 |
说明 |
recipe_export_button.dart |
修改 |
乱码清理 + 作者过滤 + 5个Word新模块 |
[0.99.48] - 2026-04-25
🐛 修复: PDF导出3项问题 (emoji乱码 + 内容不全 + 分享失败)
问题1: PDF中emoji显示为"口字内X"乱码
- 症状: 分类、作者等字段左侧显示无法复制的乱码字符
- 根因: NotoSansSC 字体不包含 emoji 字形,PDF阅读器用 tofu(缺失字符)替代
- 修复: 将所有 emoji 前缀替换为
【】 格式纯文本标签
📂 分类: → 【分类】
✍️ 作者: → 【作者】
🥘 食材 → 【食材清单】
👨🍳 制作步骤 → 【制作步骤】
🏷️ 标签 → 【标签】
问题2: PDF内容太少,缺少菜品详情页大量信息
- 症状: PDF仅包含标题/分类/作者/简介/食材/步骤/标签
- 修复: 新增5个内容模块,导出详情页全部信息
| 新增模块 |
数据来源 |
显示内容 |
| 【菜品信息】 |
recipe.meta |
做法、口味、难度、时间、用餐时间 |
| 【营养成分】 |
recipe.nutrition |
热量(kcal)、蛋白质、脂肪、碳水、纤维(g) |
| 【数据统计】 |
recipe.rating + statistics |
评分(星级)、浏览量、点赞数、评论数 |
| [注意]过敏原 |
recipe.allergens |
橙色警告框显示过敏原列表 |
| 页脚信息 |
createdAt/updatedAt |
创建时间、更新时间、来源署名 |
问题3: 导出后点击"分享给朋友"报错"文件不存在"
- 症状: 文件保存成功,但分享时提示文件不存在
- 根因: 鸿蒙平台
FilePicker.saveFile(bytes:) 可能未正确写入文件
- 修复:
_pickAndSaveFile() 增加写入验证:若返回路径但文件不存在/为空,自动手动 writeAsBytes 补写
文件变更
| 文件 |
操作 |
说明 |
recipe_export_button.dart |
修改 |
emoji→纯文本 + 5个新PDF模块 + 分享写入验证 |
[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