# Bug: 追问页面 UI 问题 日期:2026-04-08 状态:已确认(未修复) ## Bug 1: 追问页发送时顶部和消息下方重复加载UI ### 问题描述 在追问页面,用户输入信息发送后,顶部和消息下方同时出现加载UI,但顶部的加载UI是多余的。 ### 根因分析 **文件**:`apps/lib/features/divination/presentation/screens/follow_up_chat_screen.dart` **问题代码**: 1. 顶部 step 指示器(第 85-124 行): ```dart if (_sending && _currentStepName != null) Container( // 显示 step 进度,如 "解读中..."、"推理中..." child: Row( children: [ CircularProgressIndicator(...), Text(_stepLabel(_currentStepName!)), ], ), ) ``` 2. 消息下方 streaming placeholder(第 565-584 行): ```dart isStreamingPlaceholder ? Row( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator(...), Text(l10n.followUpGenerating), // "生成中..." ], ) ``` **事件触发流程**: | 顺序 | 事件 | 顶部指示器 | 消息下方 | |------|------|----------|---------| | 1 | `_submitText()` 调用 | 无 | streaming placeholder 显示 "生成中..." | | 2 | `STEP_STARTED(stepName='divination')` | 显示 "解读中..." | 继续显示 | | 3 | `STEP_FINISHED` | 消失 | 继续显示 | | 4 | `STEP_STARTED(stepName='worker')` | 显示 "推理中..." | **同时显示** "生成中..." | | 5 | `STEP_FINISHED` | 消失 | 继续显示 | | 6 | `TEXT_MESSAGE_END` | 无 | placeholder 消失,显示真实内容 | **问题**:在 worker 阶段(步骤 4),顶部显示 "推理中..." 同时消息下方显示 "生成中...",造成重复反馈。用户只需要一个加载反馈即可。 **修复方向**: 1. **方案A**:只在 divination 阶段显示顶部 step 指示器,worker 阶段隐藏(因为 streaming placeholder 已经提供反馈) ```dart // 第 85 行修改 if (_sending && _currentStepName != null && _currentStepName != 'worker') ``` 2. **方案B**:移除顶部 step 指示器,完全依赖 streaming placeholder 3. **方案C**:在 worker 阶段隐藏 streaming placeholder,只显示顶部指示器 推荐 **方案A**,因为: - divination 阶段没有 streaming placeholder,需要顶部指示器提供反馈 - worker 阶段有 streaming placeholder,不需要顶部指示器 - 改动最小,只加一个条件 --- ## Bug 2: 语音模式录制时缺少动画UI ### 问题描述 根据 social-app 的实现,按住说话模式下录音时应该有动画效果,但 eryao 的实现只有文字,没有动画。 ### 根因分析 **对比**: | 功能 | social-app | eryao | |------|------------|-------| | 录音中动画 | `recordingAnimation` widget(外部传入) | 只有文字 "录音中..." | | 录音提示文字 | 带动画的 widget | 只有文字 | | 录音中隐藏输入区域 | `IgnorePointer` + `Opacity` | 未实现 | **eryao 当前实现**(`message_composer.dart:159-172`): ```dart if (_isRecording) { if (!showRecordingInlineFeedback) { return Text(recordingText, ...); } return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox(width: 16, height: 16), // 空白占位 const SizedBox(height: AppSpacing.xs), Text(recordingText, ...), // "录音中..." const SizedBox(height: AppSpacing.xs), Text(recordingHintText, ...), // "上滑取消" ], ); } ``` **social-app 实现**(`message_composer.dart:222-239`): ```dart if (_isRecording) { if (!showRecordingInlineFeedback) { return Text(resolvedRecordingText, ...); } return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ recordingAnimation, // 动画widget(外部传入) const SizedBox(height: AppSpacing.xs), Text(resolvedRecordingText, ...), const SizedBox(height: AppSpacing.xs), Text(resolvedRecordingHintText, ...), ], ); } ``` **差异**: 1. `recordingAnimation` widget 缺失 - 需要外部传入,eryao 直接用空白占位 2. 录音中图标/按钮没有动画效果 **修复方向**: 1. 在 `FollowUpChatScreen` 或其父组件中创建 `recordingAnimation` widget 2. 将 `recordingAnimation` 通过 `MessageComposer` 传入 3. 参考 social-app 的实现,使用脉冲动画或波形动画 **参考实现**(需要添加 `recording_animation.dart`): ```dart class RecordingAnimation extends StatefulWidget { final double size; final Color color; @override State createState() => _RecordingAnimationState(); } class _RecordingAnimationState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _scaleAnimation; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(milliseconds: 1000), vsync: this, )..repeat(reverse: true); _scaleAnimation = Tween(begin: 1.0, end: 1.2).animate( CurvedAnimation(parent: _controller, curve: Curves.easeInOut), ); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _scaleAnimation, builder: (context, child) { return Transform.scale( scale: _scaleAnimation.value, child: Container( width: widget.size, height: widget.size, decoration: BoxDecoration( shape: BoxShape.circle, color: widget.color.withValues(alpha: 0.3), ), child: Icon(Icons.mic, color: widget.color, size: widget.size * 0.6), ), ); }, ); } } ``` --- ## Bug 2 & 3: 直接复用 social-app 输入框(不需要 plus 按钮) ### 需求 **直接复用** social-app 的 `MessageComposer`,但**不需要 plus 按钮**。 ### 复用清单 | 功能 | social-app | eryao | 处理 | |------|------------|-------|------| | `recordingAnimation` | required Widget | 空白占位 | **需要添加** | | 双层阴影 | 有 | 无 | **需要添加** | | `AppRadius.xxl` | 用于圆角 | 无此值 | **需要添加** | | Plus 按钮 | 有 | 无 | **不需要**,保持无 | | 图标 | `LucideIcons` | Material Icons | 保持 Material Icons | ### 实现步骤 #### 1. 添加 `AppRadius.xxl` 到 `design_tokens.dart` ```dart class AppRadius { static const double sm = 8; static const double md = 12; static const double lg = 16; static const double xl = 20; static const double xxl = 32; // 新增 static const double full = 999; } ``` #### 2. 创建 `RecordingAnimation` widget 放在 `apps/lib/shared/widgets/` 下,参考 social-app 的脉冲动画效果。 #### 3. 修改 `MessageComposer` - 添加 `recordingAnimation` 参数(required) - 添加双层阴影 - 使用 `AppRadius.xxl` 替代 `AppRadius.full` - 移除 plus 按钮(eryao 原有的无 plus 逻辑保持不变) - 保持 Material Icons #### 4. 修改 `FollowUpChatScreen` - 创建 `RecordingAnimation` 实例 - 传入 `MessageComposer` --- ## 相关文件 - `apps/lib/features/divination/presentation/screens/follow_up_chat_screen.dart` - `apps/lib/shared/widgets/message_composer.dart` - `apps/lib/shared/widgets/recording_animation.dart`(新建) - `apps/lib/shared/theme/design_tokens.dart` - `/home/qzl/Code/social-app/apps/lib/shared/widgets/message_composer.dart` ## 修复优先级 1. **高优先级**:Bug 1(重复加载UI) 2. **高优先级**:Bug 2 & 3(直接复用 social-app 输入框)