# 错误反馈机制重构计划 > 版本:v1.0 > 日期:2026-05-12 > 状态:已完成 --- ## 1. 背景与目标 ### 1.1 当前问题 对照 PRD(上架登记模块 v1.0 + 装箱编号模块 v1.3)的要求,当前 Flutter 应用在错误反馈方面存在以下差距: | # | 问题 | 影响范围 | |---|------|---------| | 1 | **装箱模块完全没有声音和振动反馈** — `boxing_page.dart` 未引入 `SoundService` 和 `Vibration` | 装箱编号 | | 2 | **反馈横幅位置错误** — 当前在页面顶部显示反馈横幅,PRD 要求统一由底部状态栏承载所有反馈文案 | 上架登记、装箱编号 | | 3 | **底部状态栏缺少黄色(网络异常)和红色(错误)状态** — 注册页状态栏圆点仅使用蓝/橙/绿三色,错误信息只出现在顶部横幅 | 上架登记 | | 4 | **反馈信息重复显示** — 顶部横幅和底部状态栏同时显示类似内容,造成冗余 | 上架登记、装箱编号 | | 5 | **声音类型不足** — `SoundService` 只有 success/failure 两种,PRD 要求区分"扫描提示音"、"错误音"、"成功音"、"连续错误音" | 全局 | | 6 | **重复上架弹窗缺少声音差异化** — PRD 要求"连续错误音"+"连续振动",当前只是普通失败音 | 上架登记 | > LED 反馈暂时不实现,后续单独处理。 ### 1.2 重构目标 - **统一反馈入口**:创建集中的 `FeedbackService`,管理声音/振动三通道反馈 - **消除重复**:移除顶部反馈横幅,所有反馈文案统一由底部状态栏显示 - **补全缺失**:为装箱模块补齐声音/振动反馈 - **符合 PRD**:状态栏圆点颜色、文案、持续时间与 PRD 表格一一对应 --- ## 2. PRD 反馈要求汇总 ### 2.1 上架登记模块(PRD 第 7 节) | 事件 | 屏幕(底部状态栏) | 声音 | 振动 | |------|-------------------|------|------| | 扫入有效总排号 | 总排号字段填入,绿色高亮 | 短促提示音 | 短震 | | 扫入有效货位号 | 货位号字段填入,绿色高亮 | 短促提示音 | 短震 | | 扫入无效码 | 红色提示 2 秒:"无效码:{值}" | 错误音 | 短震 | | 提交成功 | 绿色提示 1.5 秒 | 成功音 | 长震 | | 提交失败 | 红色提示 2 秒 | 失败音 | 短震 | | 重复上架错误 | 红色错误弹窗(需手动关闭) | 连续错误音 | 连续震 | | 网络异常 | 黄色提示:"网络异常,请检查网络连接" | 失败音 | 短震 | ### 2.2 装箱编号模块(PRD 第 9 节) | 事件 | 屏幕(底部状态栏) | 声音 | 振动 | |------|-------------------|------|------| | 扫入有效总排号 | 显示排产号信息,绿色对勾 | 短促提示音 | 短震 | | 扫入无效码 | 红色提示:"无效码,请重新扫描" | 错误音 | 短震 | | 提交成功 | 绿色提示 1.5 秒 | 成功音 | 长震 | | 重复箱号 | amber 提示 + 输入框 amber 边框 | 失败音 | 短震 | | 网络异常 | 黄色提示:"网络异常,请检查网络连接" | 失败音 | 短震 | | 切换装箱模式 | 模式控件高亮切换 | 短促提示音 | — | ### 2.3 底部状态栏圆点颜色规范 | 状态 | 圆点颜色 | |------|---------| | 空闲等待 | 蓝色 | | 提交中 | 橙色 | | 成功 | 绿色 | | 错误(无效码/提交失败/重复) | 红色 | | 网络异常 | 黄色 | | 警告(重复箱号) | amber | --- ## 3. 架构设计 ### 3.1 新增 `FeedbackService` 创建 `lib/services/feedback_service.dart`,作为**唯一的反馈调度中心**。 ``` FeedbackService ├── trigger(FeedbackEvent event) ← 统一入口 ├── _playSound(SoundType type) ← 声音通道 ├── _vibrate(VibrationPattern p) ← 振动通道 └── 底部状态栏状态由各 Page State 管理,FeedbackService 不直接操控 Widget ``` **FeedbackEvent 枚举:** ```dart enum FeedbackEvent { scanValid, // 扫入有效码 → 短促提示音 + 短震 scanInvalid, // 扫入无效码 → 错误音 + 短震 submitSuccess, // 提交成功 → 成功音 + 长震 submitFailure, // 提交失败 → 失败音 + 短震 duplicateError, // 重复上架 → 连续错误音 + 连续震 networkError, // 网络异常 → 失败音 + 短震 duplicateBoxNo, // 重复箱号 → 失败音 + 短震 modeSwitch, // 模式切换 → 短促提示音 } ``` ### 3.2 声音扩展 扩展 `SoundService`,增加声音类型: | 声音类型 | 方法名 | 用途 | |---------|--------|------| | 短促提示音 | `playBeep()` | 有效扫码、模式切换 | | 错误音 | `playError()` | 无效码 | | 成功音 | `playSuccess()` | 提交成功(保留现有) | | 失败音 | `playFailure()` | 提交失败、网络异常(保留现有) | | 连续错误音 | `playAlertLoop()` | 重复上架弹窗(循环播放直到弹窗关闭) | **实现策略**: - 在 `SoundService` 中增加 `beep`、`error`、`alert` 三个可配置路径 - 设置页面增加对应的文件选择器 - 若路径未配置则静默跳过(不阻塞业务) ### 3.3 底部状态栏组件化 抽取 `lib/widgets/status_bar.dart` 为独立 Widget: ```dart enum StatusDotColor { blue, orange, green, red, yellow, amber } class StatusBar extends StatelessWidget { final StatusDotColor dotColor; final String text; // ... } ``` 所有页面统一使用此组件,消除重复代码。 --- ## 4. 文件变更清单 ### 4.1 新增文件 | 文件路径 | 说明 | |---------|------| | `lib/services/feedback_service.dart` | 反馈调度中心,管理声音/振动 | | `lib/widgets/status_bar.dart` | 底部状态栏可复用组件 | ### 4.2 修改文件 | 文件路径 | 变更内容 | |---------|---------| | `lib/services/sound_service.dart` | 增加 `beep`/`error`/`alert` 声音类型和配置 | | `lib/pages/registration_page.dart` | ① 移除顶部反馈横幅 ② 状态栏增加红/黄颜色 ③ 引入 FeedbackService ④ 重复上架弹窗调用连续错误音 | | `lib/pages/boxing_page.dart` | ① 移除顶部反馈横幅 ② 引入 FeedbackService ③ 增加声音/振动反馈 ④ 状态栏统一使用 StatusBar 组件 | | `lib/pages/settings_page.dart` | 增加声音文件配置项(beep/error/alert) | ### 4.3 不变文件 | 文件路径 | 原因 | |---------|------| | `lib/services/api_service.dart` | API 层只负责数据传输,不涉及反馈 | | `lib/services/code_parser.dart` | 解析逻辑不变 | | `lib/services/scanner_service.dart` | 扫码硬件层不变 | | `lib/pages/home_page.dart` | 不涉及作业反馈 | | `lib/pages/boxing_detail_page.dart` | 只读展示页,无反馈交互 | --- ## 5. 实施步骤 ### 步骤 1:创建 `FeedbackService`(核心) **文件**:`lib/services/feedback_service.dart` - 定义 `FeedbackEvent` 枚举 - 根据 event 映射声音类型、振动模式 - 调用 `SoundService`、`Vibration` **预估改动**:~70 行新代码 ### 步骤 2:扩展 `SoundService` **文件**:`lib/services/sound_service.dart` - 新增 `playBeep()`、`playError()`、`playAlertLoop()`、`stopAlert()` 方法 - 新增 `beep_path`、`error_path`、`alert_path` 配置存取 - 所有播放方法在路径未配置时静默返回 **预估改动**:~40 行新增 ### 步骤 3:创建 `StatusBar` 组件 **文件**:`lib/widgets/status_bar.dart` - 抽取底部状态栏为独立 Widget - 接收 `dotColor`(枚举)和 `text` 参数 - 统一样式:圆点 + 文字,背景色使用 `surfaceContainerHighest` **预估改动**:~40 行新代码 ### 步骤 4:重构上架登记页 **文件**:`lib/pages/registration_page.dart` 变更清单: 1. **删除**顶部反馈横幅相关代码(`_snackbarMessage`、`_snackbarColor`、`_showFeedback()` 方法中的横幅渲染) 2. **引入** `FeedbackService`,在以下时机调用 `trigger()`: - `_onScan` 无效码 → `scanInvalid` - `_onScan` 有效码 → `scanValid` - `_submitOne` 成功 → `submitSuccess` - `_submitOne` 失败 → `submitFailure` - `_submitOne` 重复 → `duplicateError` - catch 网络异常 → `networkError` 3. **修改**底部状态栏: - 状态栏圆点颜色增加红色和黄色 - 错误文案在状态栏中显示(不再在横幅中) - 移除 `_successMessage` 单独字段,统一由状态栏管理 4. **替换** 内联状态栏 Widget 为 `StatusBar` 组件 5. **修改** `_showDuplicateDialog`:打开时触发 `duplicateError`(连续音/震),关闭时停止 **预估改动**:净减 ~30 行(移除横幅 > 新增 trigger 调用) ### 步骤 5:重构装箱编号页 **文件**:`lib/pages/boxing_page.dart` 变更清单: 1. **删除**顶部反馈横幅相关代码 2. **删除** 内联底部状态栏代码,替换为 `StatusBar` 组件 3. **引入** `FeedbackService`,在以下时机调用 `trigger()`: - `_onScan` 无效码 → `scanInvalid` - `_queryBoxInfo` 成功 → `scanValid` - `_submit` 成功 → `submitSuccess` - `_submit` 失败 → `submitFailure` - `_submit` 重复箱号 → `duplicateBoxNo` - catch 网络异常 → `networkError` - `_cycleMode` → `modeSwitch` 4. **修改**状态栏颜色逻辑,增加红色/黄色/amber **预估改动**:净减 ~20 行 ### 步骤 6:设置页扩展**文件**:`lib/pages/settings_page.dart`- 新增三个声音文件选择器:提示音(beep)、错误音(error)、警报音(alert)- 保持与现有 success/failure 选择器一致的 UI 风格**预估改动**:~60 行新增 --- ## 6. 风险与约束 | 风险 | 缓解措施 | |------|---------| | LED 功能依赖设备硬件 | `LedService` 全面包裹 try-catch,不支持时静默降级 | | 声音文件路径未配置 | 所有 `play*()` 方法在路径为空时静默返回 | | 连续错误音循环播放 | `playAlertLoop()` 使用循环计数或时长上限,`stopAlert()` 确保可停止 | | 振动权限 | Android 需要 `VIBRATE` 权限,检查 `AndroidManifest.xml` 是否已声明 | --- ## 7. 不在本次范围内 - 后端 API 错误格式调整(PRD 已定义,后端自行对齐) - 操作日志与操作人员记录 - 离线缓存与联网同步 - 新模块开发 - LED 反馈(后续单独处理)