聊天历史记录跳转 + 会话内搜索 - 设计方案

December 27, 2025
4 min read
By devshan

Table of Contents

This is a list of all the sections in this post. Click on any of them to jump to that section.

范围: chat_page.dart, 后端 API, 新增日历组件


一、功能概述

功能入口描述
会话内搜索AppBar 胶囊按钮组新增搜索图标搜索当前会话的消息,支持上/下导航
历史日历跳转右上角菜单 → “历史记录”日历视图选择日期,跳转到当天消息

二、确定的设计决策

决策点选择
搜索图标位置放在编辑按钮旁边(胶囊按钮组内)
搜索范围文本消息 + 文件名都搜索
日历页面形式底部 Sheet,初始较高,可上拉成全屏(DraggableScrollableSheet)
搜索结果导航起点从最新结果开始

三、UI/UX 设计

3.1 正常模式 AppBar

┌──────────────────────────────────────────────┐
│ [≡] 标题 [🔍][✎][⋯] │
└──────────────────────────────────────────────┘
↑ 新增搜索图标

3.2 搜索模式 AppBar

┌──────────────────────────────────────────────┐
│ [←] [搜索框____________________] 2/5 [↑][↓][×]│
└──────────────────────────────────────────────┘
元素行为
退出搜索模式
搜索框实时搜索,输入时过滤消息
2/5当前第 2 个结果,共 5 个
上一个结果(更早的消息)
下一个结果(更新的消息)
×清空搜索词

3.3 菜单结构

┌──────────────────────────┐
│ ── 拖动手柄 │
├──────────────────────────┤
│ ⓘ 会话信息 │
│ 创建于 2024-12-20 │
├──────────────────────────┤
│ 📅 历史记录 【新增】 │
├──────────────────────────┤
│ 🗑️ 清空消息(橙色) │
├──────────────────────────┤
│ ❌ 删除会话(红色) │
└──────────────────────────┘

3.4 日历 Sheet(可拖拽)

初始状态(约 60% 屏幕高度):

┌──────────────────────────────────────┐
│ ── 拖动手柄(上拉可全屏) │
├──────────────────────────────────────┤
│ ← 2024年12月 → │
├──────────────────────────────────────┤
│ 日 一 二 三 四 五 六 │
├──────────────────────────────────────┤
│ 1 2 3 4 5 6 │ ← 浅灰(无消息)
│ 7 8 9 10 11 12 13 │
│ 14 [15] [16] 17 18 [19] 20 │ ← 深色(有消息)
│ 21 22 23 24 25 26 27 │
│[28] │ ← 今天(圆圈高亮)
│今天 │
└──────────────────────────────────────┘

上拉后(全屏):

  • 可以看到多个月份
  • 向上滚动查看更早月份

四、后端 API 设计

4.1 获取有消息的日期列表

GET /api/v1/send/sessions/:id/message-dates

响应

{
  "dates": ["2024-12-28", "2024-12-25", "2024-12-20", "2024-12-15"],
  "firstMessageDate": "2024-10-15",
  "lastMessageDate": "2024-12-28"
}

4.2 按日期查询消息(未来懒加载用)

GET /api/v1/send/sessions/:id/messages?date=2024-12-25&direction=forward&limit=50

五、实现计划

Phase 1: 会话内搜索(前端本地搜索)

不需要后端改动

  1. 在胶囊按钮组添加搜索图标
  2. 实现搜索模式 AppBar
  3. 本地搜索逻辑(文本 + 文件名)
  4. 上/下导航结果
  5. 高亮匹配关键词
  6. 滚动到目标消息

Phase 2: 历史日历跳转

需要后端 API

  1. 后端:实现 message-dates API
  2. 前端:菜单添加”历史记录”
  3. 前端:实现 DraggableScrollableSheet 日历
  4. 前端:日期选择后跳转到当天第一条消息

Phase 3: 懒加载

需要后端 API + 架构调整

  1. 后端:按日期/cursor 分页 API
  2. 前端:cursor 分页加载
  3. 前端:内存优化(消息回收)

六、技术细节

6.1 搜索状态变量

// 搜索模式
bool _isSearchMode = false;
String _searchQuery = '';
List<int> _searchResultIndices = [];  // 匹配消息的索引
int _currentSearchResultIndex = -1;   // 当前结果索引(-1 = 无结果)
final _searchController = TextEditingController();
final _searchFocus = FocusNode();

6.2 本地搜索逻辑

void _performLocalSearch(String query) {
  final q = query.trim().toLowerCase();
  if (q.isEmpty) {
    setState(() {
      _searchResultIndices = [];
      _currentSearchResultIndex = -1;
      _highlightMessageId = null;
    });
    return;
  }
  
  final results = <int>[];
  for (int i = 0; i < _messages.length; i++) {
    final msg = _messages[i];
    final text = msg.text?.toLowerCase() ?? '';
    final fileName = msg.fileName?.toLowerCase() ?? '';
    if (text.contains(q) || fileName.contains(q)) {
      results.add(i);
    }
  }
  
  setState(() {
    _searchQuery = q;
    _searchResultIndices = results;
    // 从最新结果开始(results 是按时间升序,所以最后一个是最新的)
    _currentSearchResultIndex = results.isEmpty ? -1 : results.length - 1;
    if (_currentSearchResultIndex >= 0) {
      _highlightMessageId = _messages[results[_currentSearchResultIndex]].id;
      _scrollToMessage(_highlightMessageId!);
    }
  });
}

6.3 DraggableScrollableSheet 日历

showModalBottomSheet(
  context: context,
  isScrollControlled: true,  // 关键:允许全屏
  builder: (context) => DraggableScrollableSheet(
    initialChildSize: 0.6,   // 初始 60% 高度
    minChildSize: 0.4,       // 最小 40%
    maxChildSize: 0.95,      // 最大 95%(接近全屏)
    expand: false,
    builder: (context, scrollController) => MessageHistoryCalendar(
      scrollController: scrollController,
      messageDates: _messageDates,
      onDateSelected: (date) {
        Navigator.pop(context);
        _jumpToDate(date);
      },
    ),
  ),
);

七、共享逻辑

共享代码使用场景
_highlightMessageId搜索高亮、日历跳转高亮、外部跳转高亮
_scrollToMessage()所有定位场景
消息高亮动画TweenAnimationBuilder 闪烁效果

八、文件变更预览

文件变更
chat_page.dart添加搜索模式、搜索 AppBar、菜单项
message_history_calendar.dart新建日历组件
server.go添加 message-dates API
api_client.dart添加 getMessageDates() 方法

十、实现进度

Phase 1: 会话内搜索 ✅ 已完成

实现摘要:

  1. 搜索状态变量: _isSearchMode, _searchQuery, _searchResultIndices, _currentSearchResultIndex
  2. 胶囊按钮组添加搜索图标 (TablerIcons.search)
  3. 搜索模式 AppBar: 返回按钮 + 搜索框 + 结果计数 + 上下导航
  4. 本地搜索逻辑: 搜索文本消息 + 文件名
  5. MessageBubble 添加 highlightQuery 参数,支持高亮关键词
  6. 菜单添加 “历史记录” 选项

Phase 2: 历史日历跳转 ✅ 已完成

实现摘要:

  1. 后端 API: GET /send/sessions/:id/message-dates 返回有消息的日期列表
  2. ApiClient: getMessageDates() 方法
  3. MessageHistoryCalendar 组件: DraggableScrollableSheet + 日历网格
  4. 日历特性:
    • 紧凑模式 (60%):单月视图,左右滑动切换月份 (PageView)
    • 展开模式 (95%):多月视图,上下滚动浏览 (ListView)
    • snap: true,snapSizes: [0.6, 0.95] 自动吸附
    • 有消息日期深色可点击,无消息日期浅灰
    • 今天框线高亮 + “今天” 标签
    • 月份导航 + “今天” 按钮
    • 展开模式滑到顶部继续下拉 -> 收回紧凑模式
  5. 点击日期跳转到当天第一条消息并高亮

Phase 3: 懒加载 ⚠️ 待实现