diff --git a/AGENTS.md b/AGENTS.md index e36ddbe..e21c413 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,7 +21,7 @@ api接口部分,可在本地使用接口请求验证,确保接口正常响 每次对代码修改,功能的增删必须写日志记录和功能变更,变化大的需修改版本号, CHANGELOG.md 里面必须包含说明文档,不可删除CHANGELOG.md CHANGELOG.md的内容需确保更换其他ai coder后也能看懂当前项目, -CHANGELOG.md仅保留5个版本号信息,去除较早的版本号, +CHANGELOG.md 保留5-10个版本号信息,去除较早的版本号, 已去除的版本号写入软件特性功能,已开发完成或开发中在开发进度中, 若多次提到的功能需提升优先级,优先级值1-5。 diff --git a/CHANGELOG.md b/CHANGELOG.md index 846c011..390142f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,536 @@ 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)分层图标 @@ -56,789 +586,6 @@ ohos/entry/src/main/resources/base/media/ --- -## [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-v8a`(64位ARM,覆盖现代设备)+ `x86_64`(模拟器) -- **去除架构**: `armeabi-v7a`(32位ARM)+ `x86`(32位模拟器) -- **效果**: APK从 100.2MB 减小到 96.9MB(减少约3.3MB) - -#### 实现方式(双重过滤) -1. **`--target-platform`**: 排除Flutter引擎的v7编译(`libapp.so`、`libflutter.so`) -2. **`packaging.jniLibs.excludes`**: 排除第三方插件预编译的v7 .so文件 - -```kotlin -// build.gradle.kts -packaging { - jniLibs { - excludes += "lib/armeabi-v7a/**" - } -} -``` - -```bash -# 打包命令 -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)**: - -```dart -// ❌ 修复前: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()` 方法中的 `]` 处理逻辑 - -```dart -// ❌ 错误代码 (修复前) -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个字符处意外结束 -``` - -#### 修复方案 -在 `]` 的处理逻辑中,添加与 `}` 相同的合法性检查: - -```dart -// ✅ 正确代码 (修复后) -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](scripts/test_json_import_diagnosis.dart) - 基础格式诊断 -2. [test_json_import_flow.dart](scripts/test_json_import_flow.dart) - 完整流程模拟 -3. [test_json_debug_clean.dart](scripts/test_json_debug_clean.dart) - 详细调试清理逻辑 -4. [test_json_fixed.dart](scripts/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`** -```dart -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`)**: -```dart -// 新增过滤范围: -- 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`)**: -```dart -// 算法逻辑: -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` 循环持续检测并移除重复扩展名 - ```dart - 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行 × 双字符宽度) -- **解决方案**: - - 纠错等级: `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 没有 `>` 运算符重载 -- **修复**: 使用对象属性判断 - ```dart - // 错误 ❌ - 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 diff --git a/assets/icons/icon_320x320.png b/assets/icons/icon_320x320.png new file mode 100644 index 0000000..d00a06e Binary files /dev/null and b/assets/icons/icon_320x320.png differ diff --git a/docs/api/doc/_body.html b/docs/api/doc/_body.html new file mode 100644 index 0000000..9e74cf5 --- /dev/null +++ b/docs/api/doc/_body.html @@ -0,0 +1,125 @@ +