Files
kitchen/CHANGELOG.md
Developer 4ec348b28e feat: 更新鸿蒙应用配置与功能优化
- 添加鸿蒙分层图标配置和生成脚本
- 修复数据导出JSON解析问题
- 优化关于页面和团队信息展示
- 更新应用版本至1.4.1
- 清理代码警告和冗余文件
- 添加字体和二维码测试脚本
- 完善鸿蒙适配文档和指南
2026-04-25 09:52:06 +08:00

41 KiB
Raw Blame History

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

生成位置

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.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数据报错

解决方案

  1. 从Google Fonts下载官方 NotoSansSC变量字体(TTF) —— 真正的TrueType格式有glyf表
  2. 使用fontTools.varLib.instancer提取特定字重实例Regular=400, Bold=700
  3. 使用fontTools API直接子集化非CLI保留glyf表
  4. 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-v8a64位ARM覆盖现代设备+ x86_64(模拟器)
  • 去除架构: armeabi-v7a32位ARM+ x8632位模拟器
  • 效果: APK从 100.2MB 减小到 96.9MB减少约3.3MB

实现方式(双重过滤)

  1. --target-platform: 排除Flutter引擎的v7编译libapp.solibflutter.so
  2. packaging.jniLibs.excludes: 排除第三方插件预编译的v7 .so文件
// build.gradle.kts
packaging {
    jniLibs {
        excludes += "lib/armeabi-v7a/**"
    }
}
# 打包命令
flutter build apk --release --target-platform android-arm64,android-x64

注意事项

  • ⚠️ 必须使用 --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个开关的内容超出限制时

  1. 触摸事件被对话框的边界裁剪
  2. 开关组件无法接收触摸事件
  3. 导致开关无法切换状态

修复方案

CupertinoDialog 改为 底部弹出面板 (Bottom Sheet):

// ❌ 修复前CupertinoAlertDialog有高度限制
showCupertinoDialog(
  builder: (ctx) => CupertinoAlertDialog(
    content: SingleChildScrollView(...), // 开关无法点击
    actions: [...],
  ),
)

// ✅ 修复后:底部弹出面板(无高度限制)
showCupertinoModalPopup(
  builder: (ctx) => Container(
    constraints: BoxConstraints(maxHeight: screenHeight * 0.85),
    child: Column([
      // 拖动指示器
      // 标题栏 + 取消按钮
      // 开关列表(可正常点击)
      // 导出按钮Word/PDF 并排)
    ]),
  ),
)

新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() 方法中的 ] 处理逻辑

// ❌ 错误代码 (修复前)
final lastBracket = content.lastIndexOf(']');
if (lastBracket >= 0 && lastBracket < content.length - 1) {
  final trailing = content.substring(lastBracket + 1).trim();
  if (trailing.isNotEmpty) {  // ← 只要非空就截取!
    content = content.substring(0, lastBracket + 1);  // 删除了最后的 }
  }
}

问题流程:

原始JSON结构:
{
  ...
  "browse_history": [...]   ← ] 在这里 (位置2000)
}                            ← } 在最后 (位置2002)

BUG触发:
1. 找到最后一个 ] (位置2000)
2. ] 之后的内容: "\n}" → trim后 → "}"
3. 判断: 非空 → 截断到位置2001
4. 结果: JSON变成 [...]\n  ← 缺少外层的 }
5. 解析失败: line 88 (就是 ]) 的第4个字符处意外结束

修复方案

] 的处理逻辑中,添加与 } 相同的合法性检查:

// ✅ 正确代码 (修复后)
if (trailing.isNotEmpty &&
    !trailing.startsWith(',') &&    // 允许逗号
    !trailing.startsWith('}') &&    // ← 新增:允许花括号
    !trailing.startsWith(']')) {    // ← 新增:允许方括号
  // 只有真正的脏数据才截取
}

修改文件

文件 行号 修改内容
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个诊断脚本来定位和验证问题

  1. test_json_import_diagnosis.dart - 基础格式诊断
  2. test_json_import_flow.dart - 完整流程模拟
  3. test_json_debug_clean.dart - 详细调试清理逻辑
  4. 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中的【简介】字段显示代码

修改内容

  1. 删除 PDF导出方法1中的【简介】显示代码原第1001-1008行
  2. 删除 PDF导出方法2中的【简介】显示代码原第1710-1717行
  3. 删除 相关变量声明:
    • final cleanIntro = _safeDisplayIntro;
    • final cleanIntro = _cleanPdfText(_displayIntro!);
  4. 清理 不再使用的方法:
    • _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

String? get _safeDisplayIntro {
  final raw = _displayIntro;
  if (raw == null || raw.isEmpty) return null;
  if (_containsGarbledChars(raw)) {
    debugPrint('⚠️ displayIntro 包含乱码字符,已隐藏');
    return null;  // 直接返回null不显示此字段
  }
  return raw;
}

效果:

  • 如果简介是正常中文 → 显示
  • 如果简介包含乱码字符 → 完全不显示(不是显示过滤后的内容)
  • 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中作者下方仍有菱形/交叉形状的乱码字符
  • 根因分析:
    1. recipe.displayIntro 包含 Private Use Area (PUA) 字符 (U+E000-U+F8FF)
    2. 可能包含 Variation Selectors (U+FE00-U+FE0F)
    3. 某些特殊Unicode字符不在已知符号列表中
    4. 整段文本可能就是乱码数据非CJK/非ASCII比例过高

修复方案: 三层防御机制

第一层 - 扩展字符过滤 (_shouldFilterChar):

// 新增过滤范围:
- U+E000 ~ U+F8FF (Private Use Area - 私用区)
- U+FFF0 ~ U+FFFB (Specials - 特殊字符)  
- U+FE00 ~ U+FE0F (Variation Selectors - 变体选择器)
- U+FDD0 ~ U+FDEF (Noncharacters - 非字符)

第二层 - 特殊符号黑名单 (_isSpecialSymbol):

  • 保持原有的 200+ 特殊符号过滤(方块、箭头、制表符等)

第三层 - 乱码文本检测算法 (_isGarbledText):

// 算法逻辑:
1. 统计文本中每个字符的类型
2. 有效字符 = CJK + ASCII字母 + 数字 + 空格 + 标点符号
3. 无效字符 = 其他所有字符
4. 计算无效字符占比
5. 若无效字符 > 40%,判定为乱码,返回空字符串

// 覆盖范围:
- CJK统一汉字: U+4E00~U+9FFF, U+3400~U+4DBF, U+20000~U+2CEAF
- CJK兼容汉字: U+F900~U+FAFF, U+2F800~U+2FA1F  
- ASCII字母: a-z, A-Z
- 数字: 0-9
- 标点: 中英文标点、括号、引号等

技术亮点

  • 不依赖正则表达式,逐字符精确判断
  • 支持所有 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 (RegExp(r'\.(pdf|docx)\.(pdf|docx)$').hasMatch(finalPath)) {
      finalPath = finalPath.replaceFirst(RegExp(r'\.(pdf|docx)$'), '');
    }
    
  • 保存前也清理: 文件名传入前先 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行 × 双字符宽度)
  • 解决方案:
    • 纠错等级: ML (减少模块数)
    • 字符: ██ (单字符)
    • 添加边框: ┌─┐│ │└─┘

效果: 二维码从 ~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.ratingRecipeRating 对象,不是数字
    • 错误代码: if (rating != null && rating > 0)
    • RecipeRating 没有 > 运算符重载
  • 修复: 使用对象属性判断
    // 错误 ❌
    if (rating > 0) ...
    
    // 正确 ✅
    if (rating.hasRating == true) ...
    items.add('评分: ${rating.displayText}');
    

文件变更

文件 操作 说明
recipe_export_button.dart 修改 _displayIntro清理 + rating对象修复

[0.99.50] - 2026-04-25

新增: PDF/Word 导出二维码功能

功能描述

  • PDF底部新增二维码区域显示「小妈厨房APP」扫码提示
  • Word文档底部新增扫码区域(文字+链接)
  • 二维码内容为菜品详情页URL

二维码URL格式

https://eat.wktyl.com/?id={recipeId}
  • recipe.id 格式为 cp033793 → URL中去掉cp前缀 → id=033793

PDF二维码区域样式

┌─────────────────────────────────┐
│  ─────────────────────────────  │
│       扫码查看菜谱详情           │
│  ┌──────────────┐               │
│  │              │               │
│  │   QR CODE    │  120x120      │
│  │              │               │
│  └──────────────┘               │
│  使用「小妈厨房」APP 扫码打开     │
│  https://eat.wktyl.com/?id=xxx  │
└─────────────────────────────────┘

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
  • 根因链:
    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