Explorar o código

chore: 从仓库中移除 doc 目录,后续不再追踪

537yaha hai 8 meses
pai
achega
c72086dfb6

+ 0 - 823
doc/CHANGELOG.md

@@ -1,823 +0,0 @@
-# 开发日志
-
-> 📋 待实现需求清单请查看:[待实现需求清单.md](./待实现需求清单.md)
-
-## 2025-01-13 12:00:00 UTC
-
-### 完成的工作
-1. **实现客服个人资料管理功能**
-   - **后端**:
-     - User 模型增加字段:`avatar_url`(头像URL)、`nickname`(昵称)、`email`(邮箱)、`created_at`、`updated_at`
-     - 创建文件存储服务(`backend/infra/storage.go`):本地存储服务,可扩展为云存储
-     - 创建个人资料服务(`backend/service/profile_service.go`):提供获取、更新个人资料和上传头像功能
-     - 创建个人资料控制器(`backend/controller/profile_controller.go`):处理 HTTP 请求
-     - 新增接口:
-       - `GET /agent/profile/:user_id`:获取个人资料
-       - `PUT /agent/profile/:user_id`:更新个人资料(昵称、邮箱)
-       - `POST /agent/avatar/:user_id`:上传头像(支持 jpg、png、gif,最大 10MB)
-     - 配置静态文件服务:`/uploads` 路径用于访问上传的头像等文件
-   - **前端**:
-     - 创建个人资料 API 服务(`frontend/features/agent/services/profileApi.ts`)
-     - 创建个人资料 Hook(`frontend/features/agent/hooks/useProfile.ts`):管理个人资料状态
-     - 创建个人资料弹窗组件(`frontend/components/dashboard/ProfileModal.tsx`):
-       - 显示和编辑昵称、邮箱
-       - 上传头像(支持预览、上传进度、错误提示)
-       - 实时更新个人资料
-     - 更新 DashboardHeader:显示头像和设置按钮,点击打开个人资料弹窗
-     - 创建头像工具函数(`frontend/utils/avatar.ts`):
-       - `getAvatarUrl`:拼接完整的头像 URL
-       - `getAvatarColor`:根据种子值生成头像颜色
-       - `getAvatarInitial`:获取头像显示文本(首字母)
-     - 集成个人资料功能到 DashboardShell
-   - **功能特性**:
-     - 支持头像上传(jpg、png、gif,最大 10MB)
-     - 支持修改昵称和邮箱
-     - 头像实时预览
-     - 如果没有上传头像,显示彩色圆形头像(基于用户ID生成颜色)
-     - 头像显示在 DashboardHeader 中
-   - **技术要点**:
-     - 文件存储采用可扩展设计,目前使用本地存储,后续可切换为云存储(OSS、S3 等)
-     - 头像 URL 拼接逻辑:如果后端返回相对路径,前端自动拼接 API_BASE_URL
-     - 头像上传支持文件类型和大小验证
-     - 个人资料更新实时刷新 UI
-
----
-
-## 2025-11-12 19:35:00 UTC
-
-### 完成的工作
-1. **修复访客端只有回复消息时才标记客服消息为已读的问题**
-   - 问题:访客收到客服的消息后,如果访客不回复,消息就一直显示未读;只有访客回复后,客服端才能看到自己的消息变成了已读
-   - 原因:
-     - 访客端设置了 `disableAutoScroll={true}`,导致滚动监听被禁用
-     - 只有在发送消息时(触发自动滚动到底部),才会标记消息为已读
-     - 如果访客不回复,即使已经在底部附近查看消息,也不会标记为已读
-   - 解决方案:
-     - 移除 `disableAutoScroll` 在滚动监听 `useEffect` 中的检查,即使 `disableAutoScroll` 为 true,也应该允许通过滚动来标记消息为已读
-     - 优化消息列表更新时的已读标记逻辑:即使没有自动滚动,如果用户已经在底部附近,也应该标记为已读
-     - 这样确保:
-       - 访客通过滚动到底部查看消息时,会自动标记为已读
-       - 访客接收到新消息且已经在底部附近时,会自动标记为已读
-       - 访客发送消息时,也会标记为已读(保持原有行为)
-   - 实现方式:
-     - `frontend/components/dashboard/MessageList.tsx`:
-       - 移除 `disableAutoScroll` 在第一个滚动监听 `useEffect` 中的检查
-       - 优化第二个消息列表更新 `useEffect` 中的已读标记逻辑:如果用户已经在底部附近(`isNearBottom`),即使没有自动滚动,也应该标记为已读
-
----
-
-## 2025-11-12 19:30:00 UTC
-
-### 完成的工作
-1. **修复客服端已读状态有时显示不准确的问题**
-   - 问题:客服端发送消息后,访客明明已经看过了,但有时候显示已读,有时候还是未读状态
-   - 原因:
-     - 当消息已存在时,`handleNewMessage` 直接返回,不会更新消息的已读状态
-     - 如果 `new_message` 事件在 `messages_read` 事件之后到达,会覆盖已读状态
-     - `handleMessagesReadBroadcast` 没有检查是否有需要更新的消息,可能进行不必要的状态更新
-   - 解决方案:
-     - 优化 `handleNewMessage`:当消息已存在时,更新消息内容(包括已读状态),确保保持最新的已读状态
-     - 优化 `handleMessagesReadBroadcast`:增加检查是否有需要更新的消息,避免不必要的状态更新
-     - 这样确保:
-       - 如果消息已经被标记为已读,即使后续收到 `new_message` 事件,也会保持已读状态
-       - 如果消息列表中没有需要更新的消息,不会触发不必要的状态更新
-   - 实现方式:
-     - `frontend/features/agent/hooks/useMessages.ts`:
-       - 在 `handleNewMessage` 中,当消息已存在时,更新消息内容(包括已读状态)
-       - 在 `handleMessagesReadBroadcast` 中,增加检查是否有需要更新的消息,避免不必要的状态更新
-
----
-
-## 2025-11-12 15:25:00 UTC
-
-### 完成的工作
-1. **修复客服端已读状态不实时更新的问题**
-   - 问题:客服端发送消息后,必须手动刷新网页,消息才会变成已读状态
-   - 原因:
-     - 后端在广播 `messages_read` 事件时,payload 中没有包含 `conversation_id`
-     - 前端的 `handleMessagesReadBroadcast` 在更新消息列表时,没有检查 `conversation_id` 是否匹配当前对话
-   - 解决方案:
-     - 后端:在 `messages_read` 事件的 payload 中添加 `conversation_id` 字段
-     - 前端:在 `handleMessagesReadBroadcast` 中,只有当 `conversation_id === conversationId` 时才更新消息列表
-     - 这样确保只有当前对话的消息才会被更新,避免误更新其他对话的消息
-   - 实现方式:
-     - `backend/service/message_service.go`:
-       - 在 `MarkMessagesRead` 方法中,在广播 `messages_read` 事件时,在 payload 中添加 `conversation_id` 字段
-     - `frontend/features/agent/hooks/useMessages.ts`:
-       - 在 `handleMessagesReadBroadcast` 中,添加 `conversation_id === conversationId` 的检查,只有当匹配时才更新消息列表
-
----
-
-## 2025-11-12 15:20:00 UTC
-
-### 完成的工作
-1. **修复访客端发送消息后不自动滚动的问题**
-   - 问题:访客端发送完消息后不会自动滚动到底部
-   - 原因:访客端使用了 `disableAutoScroll={true}`,导致整个滚动逻辑被禁用
-   - 解决方案:
-     - 修改 `disableAutoScroll` 的行为:不再完全禁用滚动逻辑
-     - 当 `disableAutoScroll` 为 true 时,只禁用"收到对方消息时的自动滚动"
-     - 当最后一条消息是自己发送的时,无论 `disableAutoScroll` 是什么值,都会自动滚动到底部
-     - 这样确保:
-       - 查看历史消息时(`disableAutoScroll` 为 true),收到对方消息不会自动滚动
-       - 自己发送消息后,无论 `disableAutoScroll` 是什么值,都会自动滚动到底部
-   - 实现方式:
-     - `frontend/components/dashboard/MessageList.tsx`:
-       - 移除 `disableAutoScroll` 在 `useEffect` 开头的早期返回
-       - 修改滚动判断逻辑:`shouldAutoScroll = hasNewMessage && (isLastMessageFromCurrentUser || (!disableAutoScroll && isNearBottom))`
-       - 这样确保自己发送的消息总是会触发滚动,而对方发送的消息只有在 `disableAutoScroll` 为 false 且在底部附近时才会滚动
-
----
-
-## 2025-11-12 15:15:00 UTC
-
-### 完成的工作
-1. **优化滚动逻辑,改善查看历史消息的体验**
-   - 问题:在查看历史消息时,收到对方发送的新消息会自动滚动到底部,打断用户的浏览
-   - 需求:
-     - 查看历史消息时,收到对方消息不应该自动滚动到底部
-     - 自己发送消息后,应该自动滚动到底部
-   - 解决方案:
-     - 在消息更新时,通过比较消息ID和数量检查是否有新消息
-     - 使用 `requestAnimationFrame` 确保 DOM 已更新后再检查位置
-     - 在 DOM 更新后检查当前位置(距离底部 < 100px 视为在底部附近)
-     - 滚动逻辑:
-       1. 如果最后一条消息是自己发送的,无论在哪里都自动滚动到底部
-       2. 如果最后一条消息是对方发送的,只有在底部附近时才自动滚动到底部(保持底部状态)
-       3. 如果没有新消息(例如只是消息状态更新),不改变滚动位置
-     - 这样确保:
-       - 查看历史消息时(不在底部),收到对方消息不会自动滚动,不会打断浏览
-       - 自己发送消息后,会自动滚动到底部,可以看到自己发送的消息
-       - 如果已经在底部查看最新消息,收到对方消息时保持滚动到底部
-   - 实现方式:
-     - `frontend/components/dashboard/MessageList.tsx`:
-       - 添加 `lastMessageIdRef` 和 `lastMessageCountRef` 来跟踪最后一条消息
-       - 在消息更新时检查是否有新消息(通过比较消息ID和数量)
-       - 使用 `requestAnimationFrame` 确保 DOM 已更新后再检查位置和决定是否滚动
-       - 优化滚动逻辑,区分自己发送的消息和对方发送的消息
-       - 在 `requestAnimationFrame` 回调中从 `containerRef.current` 重新获取容器,确保使用最新的 DOM 元素
-
----
-
-## 2025-11-12 15:00:00 UTC
-
-### 完成的工作
-1. **修复已读状态逻辑问题**
-   - 问题1:访客端的已读状态不更新,即使客服已经读取了消息,访客端仍然显示未读,需要刷新页面才能显示已读
-   - 问题2:客服端的已读逻辑不正确,加载消息时自动标记为已读,但实际上应该在滚动到底部时才标记为已读
-   - 问题3:访客端收到客服消息时自动标记为已读,但实际上应该在滚动到底部时才标记为已读
-   - 解决方案:
-     - 移除加载消息时自动标记为已读的逻辑(客服端和访客端)
-     - 移除收到新消息时自动标记为已读的逻辑(客服端和访客端)
-     - 在 `MessageList` 组件中添加滚动检测逻辑,当用户滚动到底部附近(距离底部 < 100px)时,延迟 500ms 后标记未读消息为已读
-     - 当消息列表更新且自动滚动到底部时,延迟 800ms 后标记未读消息为已读(避免频繁调用,至少间隔 2 秒)
-     - 修复访客端的 WebSocket `messages_read` 事件处理,确保正确更新已读状态
-     - 修复客服端的 WebSocket `messages_read` 事件处理,确保只更新客服消息的已读状态(当 `reader_is_agent === false` 时)
-   - 实现方式:
-     - `frontend/app/chat/page.tsx`:移除自动标记为已读的逻辑,添加 `onMarkMessagesRead` 回调
-     - `frontend/app/agent/chat/[conversationId]/page.tsx`:移除自动标记为已读的逻辑,修复 `handleMessagesReadEvent` 函数
-     - `frontend/features/agent/hooks/useMessages.ts`:移除自动标记为已读的逻辑
-     - `frontend/components/dashboard/MessageList.tsx`:添加滚动检测逻辑,当滚动到底部时标记消息为已读
-     - `frontend/components/dashboard/DashboardShell.tsx`:传递 `onMarkMessagesRead` 回调给 `MessageList` 组件
-
----
-
-## 2025-11-12 14:45:00 UTC
-
-### 完成的工作
-1. **优化日志记录,仅保留关键错误**
-   - 问题:日志过于详细,包含大量正常流程的日志,影响性能和可读性
-   - 解决方案:移除详细日志,仅保留关键错误和警告日志
-   - 实现方式:
-     - 后端:移除正常流程的详细日志(收到发送消息请求、消息创建成功、客户端连接、客户端断开、广播消息等)
-     - 后端:保留关键错误日志(创建消息失败、WebSocket Hub 为空、广播消息失败、发送消息失败、WebSocket 读取错误、WebSocket 写入错误、发送 ping 失败)
-     - 后端:保留关键警告日志(客户端断开时未找到、客户端断开时对话不存在、发送消息失败)
-     - 前端:移除正常流程的详细日志(发送消息、消息发送成功、收到 WebSocket 消息、处理 WebSocket 消息、处理新消息、处理已读事件、添加新消息等)
-     - 前端:保留关键错误日志(发送消息失败、解析 WebSocket 消息失败、WebSocket 错误、创建 WebSocket 连接失败、WebSocket 重连次数已达上限)
-     - 前端:移除连接成功的日志(WebSocket 连接成功、WebSocket 连接关闭等)
-
-2. **修复通道关闭问题**
-   - 问题:通道可能被重复关闭,导致 panic
-   - 解决方案:在关闭通道前检查通道是否已经关闭
-   - 实现方式:
-     - 在 `Hub.unregister` 中,使用 `select` 检查通道是否已经关闭
-     - 如果通道已经关闭,不再关闭
-     - 如果通道未关闭,关闭它
-
-3. **优化 WebSocket 连接管理**
-   - 问题:在开发模式下,大量 WebSocket 连接可能导致问题
-   - 解决方案:改进断开连接逻辑,确保连接正确关闭
-   - 实现方式:
-     - 在 `WSClient.disconnect` 中,设置 `reconnectAttempts = maxReconnectAttempts` 避免重连
-     - 在关闭连接前,检查连接状态
-     - 移除断开连接的详细日志
-
----
-
-## 2025-11-12 14:30:00 UTC
-
-### 完成的工作
-1. **添加详细日志用于调试消息广播问题**
-   - 问题:用户报告访客端发送消息后,客服端没有收到(后续确认消息发送正常)
-   - 解决方案:添加详细的日志来跟踪消息发送和广播过程,便于未来调试
-   - 注意:后续优化为仅保留关键错误日志(见 2025-11-12 14:45:00 UTC)
-
-### 技术细节
-- 修改文件:
-  - `backend/controller/message_controller.go`:添加消息创建日志
-  - `backend/service/message_service.go`:添加消息广播日志
-  - `backend/websocket/hub.go`:添加广播消息日志、修复通道关闭问题
-  - `backend/websocket/client.go`:添加 WebSocket 发送消息日志、修复通道关闭问题
-  - `frontend/features/agent/services/messageApi.ts`:添加消息发送日志
-  - `frontend/app/chat/page.tsx`:添加消息发送和处理日志
-  - `frontend/features/agent/hooks/useMessages.ts`:添加消息处理日志
-  - `frontend/lib/websocket.ts`:添加 WebSocket 消息接收日志、优化断开连接逻辑
-
-### 调试步骤
-1. **测试消息发送**:
-   - 在访客端发送消息
-   - 查看浏览器控制台日志:
-     - 应该看到 `📨 开始发送消息: 对话ID=X, 内容="..."`
-     - 应该看到 `📤 发送消息: 对话ID=X, 是客服=false, 发送者ID=0, 内容长度=X`
-     - 应该看到 `✅ 消息发送成功: 对话ID=X`
-   - 查看后端日志:
-     - 应该看到 `📨 收到发送消息请求: 对话ID=X, 发送者ID=0, 是客服=false, 内容长度=X`
-     - 应该看到 `✅ 消息创建成功: 消息ID=X, 对话ID=X, 已广播`
-     - 应该看到 `📤 准备通过 WebSocket 广播消息: 消息ID=X, 对话ID=X`
-     - 应该看到 `📤 准备广播消息: 对话ID=X, 类型=new_message`
-     - 应该看到 `📢 广播消息: 对话ID=X, 类型=new_message, 客户端数=X`
-     - 应该看到 `📤 WebSocket 消息已发送: 对话ID=X, 类型=new_message, 是访客=false`(客服端)
-     - 应该看到 `✅ 消息广播完成: 对话ID=X, 成功=X, 失败=0`
-
-2. **测试消息接收**:
-   - 在客服端查看浏览器控制台日志:
-     - 应该看到 `📨 收到 WebSocket 消息: 对话ID=X, 类型=new_message`
-     - 应该看到 `📨 处理 WebSocket 消息(客服端): 对话ID=X, 类型=new_message`
-     - 应该看到 `📨 处理新消息(客服端): {...}`
-     - 应该看到 `✅ 添加新消息: 消息ID=X, 内容="..."`
-
-3. **如果消息没有发送**:
-   - 检查浏览器控制台是否有错误
-   - 检查网络请求(Network 标签)是否有 `POST /messages` 请求
-   - 检查请求状态码(应该是 200)
-
-4. **如果消息发送了但没有广播**:
-   - 检查后端日志是否有 `📤 准备通过 WebSocket 广播消息` 日志
-   - 检查后端日志是否有 `📢 广播消息` 日志
-   - 检查后端日志是否有 `⚠️ WebSocket Hub 为空` 日志(如果有,说明 Hub 没有正确初始化)
-
-5. **如果消息广播了但没有收到**:
-   - 检查后端日志是否有 `📤 WebSocket 消息已发送` 日志
-   - 检查后端日志是否有 `⚠️ 发送消息失败` 日志
-   - 检查前端日志是否有 `📨 收到 WebSocket 消息` 日志
-   - 检查 WebSocket 连接是否正常(查看浏览器 Network 标签中的 WebSocket 连接)
-
-### 后续优化
-- 检查开发模式下大量 WebSocket 连接的问题(可能是 Next.js 热重载导致的)
-- 优化连接管理,减少重复连接
-- 添加连接数限制,防止连接数过多
-
----
-
-## 2025-11-12 14:00:00 UTC
-
-### 完成的工作
-1. **修复 WebSocket 错误处理问题**
-   - 问题:WebSocket 连接错误显示为 `{}`,错误信息不够详细
-   - 解决方案:改进错误处理,提供更详细的错误信息
-   - 实现方式:
-     - 在 `onerror` 事件中检查 `readyState` 和 `url`,提供详细的错误信息
-     - 在 `onclose` 事件中获取关闭代码和原因,提供详细的关闭信息
-     - 只有在非正常关闭时才尝试重连(避免在开发模式下频繁重连)
-     - 使用 `useRef` 存储回调函数,避免因回调函数变化导致重新连接
-     - 在连接前检查是否已存在连接,避免重复连接
-
-### 技术细节
-- 修改文件:
-  - `frontend/lib/websocket.ts`:改进错误处理和关闭处理
-  - `frontend/features/agent/hooks/useWebSocket.ts`:使用 `useRef` 存储回调函数
-  - `frontend/app/chat/page.tsx`:明确设置 `isVisitor: true`
-- 实现原理:
-  - 在 `onerror` 事件中,检查 `readyState` 和 `url`,提供详细错误信息
-  - 在 `onclose` 事件中,获取关闭代码(`code`)、原因(`reason`)和是否干净关闭(`wasClean`)
-  - 只有在 `!wasClean && code !== 1000` 时才尝试重连
-  - 使用 `useRef` 存储回调函数,避免因回调函数变化导致 `useEffect` 重新执行
-  - 在 `connect()` 方法中,检查是否已存在连接,如果存在则先断开
-- ✅ `npm run lint`(frontend,无警告)
-
-### 错误处理改进
-- ✅ 提供详细的错误信息(状态、URL等)
-- ✅ 提供详细的关闭信息(关闭代码、原因、是否干净关闭)
-- ✅ 避免在开发模式下频繁重连
-- ✅ 避免因回调函数变化导致重新连接
-- ✅ 避免重复连接
-
-### 测试状态
-✅ 手动验证:WebSocket 错误处理和关闭处理正常,错误信息详细
-
----
-
-## 2025-11-12 13:30:00 UTC
-
-### 完成的工作
-1. **修复输入框失去焦点问题**
-   - 问题:在访客端和客服端,发送完一条消息后,输入框失去焦点,需要再次点击输入框才能继续输入
-   - 解决方案:在 `MessageInput` 组件中添加自动聚焦功能
-   - 实现方式:
-     - 使用 `useRef` 引用输入框元素
-     - 使用 `useEffect` 监听 `sending` 状态变化
-     - 当 `sending` 从 `true` 变为 `false` 时(发送完成),自动聚焦到输入框
-     - 使用 `setTimeout` 确保 DOM 更新完成后再聚焦
-
-### 技术细节
-- 修改文件:
-  - `frontend/components/dashboard/MessageInput.tsx`:添加自动聚焦功能
-- 实现原理:
-  - 使用 `useRef` 创建输入框引用 `inputRef`
-  - 使用 `useRef` 记录上一次的 `sending` 状态 `prevSendingRef`
-  - 在 `useEffect` 中监听 `sending` 状态变化
-  - 当 `prevSendingRef.current === true && sending === false` 时,说明刚刚发送完成
-  - 调用 `inputRef.current?.focus()` 聚焦到输入框
-  - 使用 `setTimeout(..., 0)` 确保 DOM 更新完成后再聚焦
-- ✅ `npm run lint`(frontend,无警告)
-
-### 测试状态
-✅ 手动验证:发送消息后,输入框自动聚焦,可以直接继续输入
-
-### 用户体验改进
-- ✅ 发送消息后,输入框自动聚焦,无需再次点击
-- ✅ 用户可以连续发送多条消息,无需每次点击输入框
-- ✅ 提升聊天体验,减少操作步骤
-
----
-
-## 2025-11-12 13:00:00 UTC
-
-### 完成的工作
-1. **更新测试指南文档**
-   - 添加完整的测试指南,覆盖所有已实现功能
-   - 添加访客端测试流程(8个测试项)
-   - 添加客服端测试流程(16个测试项)
-   - 添加实时通信测试(WebSocket 实时推送、已读状态同步、在线状态更新)
-   - 添加搜索功能测试(关键词高亮、自动定位)
-   - 添加访客信息测试(信息收集、联系信息编辑)
-   - 添加界面交互测试(滚动行为、响应式布局)
-   - 添加错误处理测试(网络错误、数据验证)
-   - 添加性能测试(加载性能、实时性能)
-   - 添加兼容性测试(浏览器兼容、数据持久化)
-   - 添加快速测试流程(核心功能验证,约30分钟)
-   - 添加高级测试场景(8个场景:多访客、并发、长时间连接、网络中断、大量消息、搜索性能、并发编辑、多访客状态)
-   - 添加调试技巧(浏览器开发者工具、控制台、网络请求、后端日志)
-   - 添加常见问题(10个常见问题及解决方案)
-   - 添加完整测试检查清单(8个测试类别,100+ 测试项)
-   - 添加测试结果记录模板
-
-### 技术细节
-- 修改文件:
-  - `doc/测试指南.md`:完整重写,添加所有已实现功能的测试指南
-- 文档结构:
-  - 一、准备工作(数据库配置、快速开始)
-  - 二、启动后端
-  - 三、启动前端
-  - 四、访客端测试流程(8个测试项)
-  - 五、客服端测试流程(16个测试项)
-  - 六、调试技巧
-  - 七、常见问题(10个常见问题)
-  - 八、完整测试检查清单(8个测试类别)
-  - 九、快速测试流程(核心功能验证)
-  - 十、高级测试场景(8个场景)
-  - 十一、测试结果记录
-
-### 测试覆盖
-- ✅ 基础功能测试(访客端、客服端)
-- ✅ 实时通信测试(WebSocket 实时推送、已读状态同步、在线状态更新)
-- ✅ 搜索功能测试(关键词高亮、自动定位)
-- ✅ 访客信息测试(信息收集、联系信息编辑)
-- ✅ 界面交互测试(滚动行为、响应式布局)
-- ✅ 错误处理测试(网络错误、数据验证)
-- ✅ 性能测试(加载性能、实时性能)
-- ✅ 兼容性测试(浏览器兼容、数据持久化)
-- ✅ 高级测试场景(多访客、并发、长时间连接、网络中断、大量消息、搜索性能、并发编辑、多访客状态)
-
-### 后续优化
-- 添加自动化测试(E2E 测试)
-- 添加性能测试报告
-- 添加测试覆盖率报告
-
----
-
-## 2025-11-12 12:00:00 UTC
-
-### 完成的工作
-1. **对话状态管理(在线/离线)实时更新**
-   - 后端:WebSocket 连接建立时标记访客在线,断开时标记离线
-   - 后端:通过 WebSocket 推送 `visitor_status_update` 事件到客服端
-   - 后端:在 `ConversationService` 中添加 `UpdateVisitorOnlineStatus` 和 `UpdateLastSeenAt` 方法
-   - 后端:在 `Hub` 中添加回调机制,在客户端连接/断开时调用回调函数
-   - 后端:在 `Client` 中添加 `isVisitor` 字段,区分访客和客服
-   - 前端:WebSocket 客户端添加 `isVisitor` 参数,默认值为 `true`
-   - 前端:客服端 WebSocket 连接设置 `isVisitor=false`
-   - 前端:客服端接收 `visitor_status_update` 事件,刷新对话详情
-   - 前端:在对话列表中显示在线/离线图标(绿色圆点表示在线)
-
-### 技术细节
-- 修改文件:
-  - 后端:`backend/service/conversation_service.go`、`backend/websocket/hub.go`、`backend/websocket/client.go`、`backend/websocket/handler.go`、`backend/main.go`
-  - 前端:`frontend/lib/websocket.ts`、`frontend/features/agent/hooks/useWebSocket.ts`、`frontend/features/agent/hooks/useMessages.ts`、`frontend/features/agent/types.ts`、`frontend/components/dashboard/ConversationListItem.tsx`
-- ✅ `npm run lint`(frontend,无警告)
-- ✅ `gofmt`(backend,无错误)
-
-### 实现原理
-- 当访客连接 WebSocket 时,后端会调用 `UpdateVisitorOnlineStatus(conversationID, true)` 更新在线状态
-- 当访客断开 WebSocket 时,后端会检查该对话是否还有其他访客连接,如果没有,则调用 `UpdateVisitorOnlineStatus(conversationID, false)` 更新离线状态
-- 后端通过 WebSocket 广播 `visitor_status_update` 事件到该对话的所有客户端(包括客服)
-- 客服端在收到 `visitor_status_update` 事件时,刷新当前对话详情,更新在线状态
-- 对话列表中显示在线/离线图标(基于 `status === "open"` 判断)
-
-### 测试状态
-✅ 手动验证:访客连接/断开 WebSocket 时,客服端实时更新在线状态
-
-### 后续优化
-- 实现心跳机制(定期更新 `last_seen_at`)
-- 根据 `last_seen_at` 判断是否在线(例如,如果 `last_seen_at` 在最近 60 秒内,则认为在线)
-- 在 `ConversationSummary` 中添加 `last_seen_at` 字段,以便在对话列表中显示最后活跃时间
-- 定期轮询对话列表,更新所有对话的状态
-
----
-
-## 2025-11-12 11:10:00 UTC
-
-### 完成的工作
-1. **修复已读状态同步问题**
-   - 访客端:修复 `handleMessagesReadEvent` 未判断 `reader_is_agent`,导致客服读取访客消息后,访客端无法更新已读状态
-   - 客服端:修复 `handleMessagesReadBroadcast` 未判断 `reader_is_agent`,导致访客读取客服消息后,客服端无法更新已读状态
-   - 访客端:只有当 `reader_is_agent === true` 时,才更新访客消息(`sender_is_agent === false`)的已读状态
-   - 客服端:只有当 `reader_is_agent === false` 时,才更新客服消息(`sender_is_agent === true`)的已读状态
-
-### 技术细节
-- 修改文件:`frontend/app/chat/page.tsx`、`frontend/features/agent/hooks/useMessages.ts`
-- ✅ `npm run lint`(frontend,无警告)
-
-### 问题原因
-- 后端通过 WebSocket 推送 `messages_read` 事件时,会包含 `reader_is_agent` 字段,表示读取者是客服还是访客
-- 前端在接收 `messages_read` 事件时,没有判断 `reader_is_agent`,导致错误地更新了消息的已读状态
-- 对于访客端:只有当客服读取了访客的消息(`reader_is_agent === true`)时,才应该更新访客消息的已读状态
-- 对于客服端:只有当访客读取了客服的消息(`reader_is_agent === false`)时,才应该更新客服消息的已读状态
-
-### 测试状态
-✅ 手动验证:访客发送消息后,客服查看消息,访客端显示双对勾(已读状态)
-
----
-
-## 2025-11-11 08:00:00 UTC
-
-### 完成的工作
-1. **访客联系信息编辑闭环**
-   - 后端新增 `PUT /conversations/:id/contact` 接口,`ConversationService.UpdateConversationContact` 落库邮箱/电话/备注
-   - 前端 `VisitorDetailPanel` 增加弹窗编辑,支持新增、修改、清空邮箱/电话/备注并即时刷新
-2. **服务与 Hook 扩展**
-   - `conversationApi.updateConversationContact` 封装更新接口,统一返回结构
-   - `useMessages` 暴露 `updateContactInfo`,`DashboardShell` 和 `VisitorDetailPanel` 通过钩子完成联动
-
-### 技术细节
-- 新增/修改文件:`backend/controller/conversation_controller.go`、`backend/service/conversation_service.go`、`backend/router/router.go`、`backend/service/types.go`
-- 前端涉及文件:`features/agent/services/conversationApi.ts`、`features/agent/hooks/useMessages.ts`、`components/dashboard/VisitorDetailPanel.tsx`
-- ✅ `npm run lint`(frontend)
-
-### 测试状态
-✅ 手动验证:客服工作台编辑邮箱/电话/备注,数据保存后右栏即时更新
-
----
-
-## 2025-11-10 07:30:00 UTC
-
-### 完成的工作
-1. **客服工作台前端架构拆分**
-   - `app/agent/dashboard/page.tsx` 只保留页面入口,改由 `DashboardShell` 负责布局编排
-   - 新增 `components/dashboard/*`,将导航栏、会话列表、消息区、访客详情拆分为独立组件
-   - 新增 `features/agent/hooks` 与 `features/agent/services`,分别承载状态逻辑与 API 调用
-   - 新增 `utils/format.ts`、`utils/highlight.tsx`、`utils/storage.ts`,统一时间格式、关键词高亮与本地存储操作
-
-2. **会话/消息状态管理优化**
-   - `useAuth` 统一处理本地登录信息与退出逻辑
-   - `useConversations` 负责对话列表、搜索、防抖与排序
-   - `useMessages` + `useWebSocket` 负责消息拉取、已读回执、WebSocket 广播与高亮定位
-
-3. **TypeScript 类型补全**
-   - `features/agent/types.ts` 汇总会话、消息、用户等公共类型
-   - `lib/websocket.ts`、`useWebSocket`、`useMessages` 改用强类型定义,消除 `any`
-
-4. **旧版客服聊天页迁移**
-   - `/agent/chat/[conversationId]` 复用统一的消息组件、输入框与 WebSocket 逻辑
-   - 接入服务层 API 与 Hook,移除旧版冗余状态/样式代码
-   - 支持快速返回工作台,交互体验与四栏布局保持一致
-
-5. **访客聊天页重构**
-   - `/chat` 页面改用统一的 `MessageList`、`MessageInput` 组件和消息服务
-   - 对话初始化、WebSocket、已读回执与客服端共享实现,减少重复逻辑
-   - 保留访客视角的气泡样式与默认提示,UI/状态与客服端保持一致
-
-### 技术细节
-- 新增目录:`components/dashboard/`、`features/agent/hooks/`、`features/agent/services/`、`utils/`
-- 复用工具函数:消息预览截断、时间格式化、关键词高亮、localStorage 操作
-- ESLint:`npm run lint` 通过
-
-### 测试状态
-✅ `npm run lint`(frontend,无警告)
-
-### 下一步计划
-- 继续补充自动化测试(组件级、hook 级单元测试)
-- 为 `services` 层补充基础错误处理与重试策略
-- 为文件上传、AI 客服等后续功能预留组件与 Hook 模板
-
-## 2025-11-10 05:00:00 UTC
-
-### 完成的工作
-1. **访客信息采集落地**
-   - 后端 `InitConversation` 接口接收并保存网站、来源、浏览器、系统、语言、IP 等信息
-   - 访客前端自动采集页面 URL、Referrer、User-Agent、语言信息并随初始化请求提交
-   - 对话表新增字段,支持持久化访客技术信息及联系信息占位
-   - 会话 `last_seen_at` 字段初始化,便于后续在线状态展示
-
-2. **系统消息写入与展示**
-   - 消息表新增 `message_type` 字段,区分普通消息与系统消息
-   - 新对话自动生成系统消息(访问入口、来源页面)
-   - 客服工作台中,系统消息以灰色气泡居中展示,支持关键词高亮与定位
-
-3. **客服工作台访客详情完善**
-   - 新增 `GET /conversations/:id` 接口返回完整访客信息
-   - 右侧访客详情面板展示网站、来源、浏览器、系统、语言、IP、最后活跃等数据
-   - 联系信息区域展示实际数据(暂无信息时提供提示),保留后续编辑入口
-   - 聊天头部显示更准确的 last seen 信息
-
-4. **搜索体验优化**
-   - 搜索匹配的系统消息支持高亮与自动定位
-   - WebSocket 收到新消息时自动刷新会话详情,保持访客信息实时
-
-5. **消息已读/未读状态(基础版)**
-   - 数据库新增 `is_read` / `read_at` 字段,支持已读记录
-   - 新增 `PUT /messages/read` 接口及 WebSocket `messages_read` 事件,同步状态
-   - 客服端/访客端聊天气泡显示单/双对勾,已读回执实时可见
-   - 对话列表同步显示最后一条消息的已读状态
-
-### 技术细节
-- MySQL 表结构:`conversations` 新增多项访客字段,`messages` 新增 `message_type`
-- 后端:新增 `ConversationDetailRes`、`GetConversationDetail`,并统一时间格式输出
-- 前端:新增访客详情状态管理、系统消息渲染分支、技术信息展示组件
-- WebSocket:收到新消息后同步刷新会话详情,确保右侧数据与消息流一致
-
-### 测试状态
-✅ 通过手动测试:访问 `/chat` 生成新对话,确认数据库记录访客信息与系统消息  
-✅ 通过客服工作台验证:系统消息样式正常,访客详情与数据库数据一致  
-✅ 搜索“关键词”后点击结果,可自动定位系统消息并高亮
-
-### 下一步计划
-- 实现客服端联系信息的手动添加/编辑
-- 基于 `last_seen_at` 和 WebSocket 心跳完成在线状态实时更新
-- 继续扩展系统消息(如客服加入/离开、对话状态变化)
-- 访客位置字段对接外部服务(基于 IP 定位)
-
-## 2025-01-16 18:00:00 UTC
-
-### 完成的工作
-1. **客服工作台四栏布局实现**
-   - 实现最左侧导航菜单栏(固定宽度 64px,浅灰色背景)
-   - 实现左侧对话列表栏(固定宽度 320px,显示所有对话)
-   - 实现中间聊天内容栏(自适应宽度,集成完整聊天功能)
-   - 实现右侧访客详情栏(固定宽度 320px,显示联系信息和技术信息)
-   - 统一顶部栏高度(h-16),确保三栏对齐
-
-2. **中间栏聊天功能集成**
-   - 集成消息显示功能(客服消息在右,访客消息在左)
-   - 集成消息发送功能(支持实时发送)
-   - 集成 WebSocket 实时通信(自动接收新消息)
-   - 实现自动滚动到底部(新消息自动可见)
-   - 实现智能时间格式化(今天显示时间,更早显示日期+时间)
-   - 实现消息加载状态和发送状态
-
-3. **右侧栏访客详情实现**
-   - 显示访客头像(基于 visitor_id 生成颜色)
-   - 显示在线/离线状态(基于对话状态)
-   - 显示联系信息(邮箱、电话、备注,支持添加按钮)
-   - 显示技术信息(网站、来源、位置等,占位待实现)
-   - 实现刷新按钮(可刷新消息)
-
-4. **UI 优化**
-   - 移除右侧栏重复的基础信息(对话ID、状态等已在左侧显示)
-   - 统一联系信息的交互方式(邮箱、电话、备注都有"+ Add"按钮)
-   - 优化导航栏颜色(与聊天内容背景一致)
-   - 优化对话列表显示(头像、ID、状态、时间)
-
-### 技术细节
-- 使用 React 状态管理多个对话和消息
-- 使用 `useEffect` 实现对话切换时自动加载消息
-- 使用 WebSocket 实现实时消息推送
-- 使用 `useRef` 实现自动滚动功能
-- 使用 Tailwind CSS 实现响应式布局
-- 对话切换时自动清空消息列表并重新加载
-
-### 用户体验
-- 无需跳转页面,在同一页面内切换对话
-- 实时接收新消息,无需手动刷新
-- 消息发送后立即显示,体验流畅
-- 界面布局清晰,信息层次分明
-- 右侧栏专注于显示左侧看不到的详细信息
-
-### 测试状态
-✅ 功能测试通过:四栏布局、对话切换、消息发送、实时通信均正常工作
-
-### 下一步计划
-- 实现会话搜索功能(左侧栏搜索框)
-- 实现客服个人资料管理(头像上传、信息修改)
-- 实现访客信息自动收集(技术信息、来源页面等)
-- 实现对话状态管理(在线/离线状态实时更新)
-
-## 2025-01-15 21:00:00 UTC
-
-### 完成的工作
-1. **WebSocket 实时通信功能**
-   - 实现后端 WebSocket Hub 管理器:管理所有客户端连接,按对话ID分组
-   - 实现 WebSocket 客户端处理:处理连接、心跳检测、消息发送
-   - 实现 WebSocket 路由处理:升级 HTTP 连接为 WebSocket 连接
-   - 集成消息推送:消息创建后自动通过 WebSocket 推送给所有相关客户端
-   - 实现前端 WebSocket 客户端:封装连接、自动重连、消息接收
-   - 访客端和客服端都支持实时消息推送
-
-2. **技术实现**
-   - 后端使用 `gorilla/websocket` 库
-   - 前端使用原生 WebSocket API
-   - 实现心跳检测(Ping/Pong)保持连接活跃
-   - 实现自动重连机制(最多 5 次)
-   - 消息去重,避免重复显示
-
-3. **文档更新**
-   - 创建 `doc/WebSocket学习笔记.md`:详细解释 WebSocket 工作原理
-   - 包含前后端代码示例和完整流程说明
-
-### 技术细节
-- WebSocket 路由:`/ws?conversation_id=<对话ID>`
-- 消息格式:`{ type: "new_message", conversation_id: number, data: Message }`
-- 连接升级:HTTP 连接升级为 WebSocket 连接(101 状态码)
-- 心跳间隔:每 54 秒发送一次 Ping
-- 重连策略:断开后等待 3 秒,最多重试 5 次
-
-### 用户体验改进
-- ✅ 访客发送消息后,客服立即看到(无需刷新)
-- ✅ 客服发送消息后,访客立即看到(无需刷新)
-- ✅ 真正的实时双向通信
-- ✅ 连接断开后自动重连
-
-### 测试状态
-✅ 功能实现完成,待测试
-
-## 2025-01-15 20:00:00 UTC
-
-### 完成的工作
-1. **客服端功能完整实现**
-   - 实现客服登录页面(`/`):使用默认管理员账号(admin/admin123)登录
-   - 实现对话列表页面(`/agent/conversations`):显示所有未关闭的对话,支持点击进入聊天
-   - 实现客服聊天页面(`/agent/chat/[conversationId]`):客服可以查看和回复访客消息
-   - 实现登录状态检查:未登录自动跳转到登录页
-   - 实现退出登录功能:清除登录状态并跳转
-
-2. **消息显示优化**
-   - 客服端消息布局:客服消息在右侧(蓝色气泡),访客消息在左侧(白色气泡)
-   - 从客服视角优化 UI,符合客服使用习惯
-
-3. **后端功能完善**
-   - 实现默认管理员账号自动创建(首次启动时创建 admin/admin123)
-   - 实现对话列表查询接口(`GET /conversations`):返回所有未关闭的对话
-   - 实现创建客服账号接口(`POST /admin/users`):管理员可以创建新的客服/管理员账号
-   - 实现退出登录接口(`POST /logout`):用于前端清除登录状态
-
-4. **文档更新**
-   - 更新测试指南:添加完整的客服端测试流程
-   - 更新 CHANGELOG:记录客服端开发过程
-
-### 技术细节
-- 使用 `localStorage` 存储客服登录信息(`agent_user_id`、`agent_username`、`agent_role`)
-- 使用 Next.js 路由保护:检查登录状态,未登录自动跳转
-- 客服聊天页面复用访客聊天页面的核心逻辑,调整消息显示位置
-- 后端使用 `initDefaultAdmin` 函数在首次启动时自动创建默认管理员
-
-### 用户体验
-- 客服登录流程简单:使用默认账号即可登录
-- 对话列表清晰:显示对话状态、访客ID、时间等信息
-- 客服聊天界面直观:客服消息在右,访客消息在左
-- 登录状态管理完善:未登录自动跳转,退出登录清除状态
-
-### 测试状态
-✅ 功能测试通过:登录、对话列表、客服聊天、退出登录均正常工作
-
-### 下一步计划
-- 添加实时消息推送(WebSocket)
-- 添加对话状态管理(关闭对话、重新打开等)
-- 优化 UI 和用户体验
-
-## 2025-01-15 18:00:00 UTC
-
-### 完成的工作
-1. **访客聊天页面完整实现**
-   - 实现消息发送功能(`POST /messages`)
-   - 实现消息拉取功能(`GET /messages`)
-   - 实现消息列表展示(区分访客和客服消息)
-   - 实现自动滚动到底部(新消息自动可见)
-   - 实现时间格式化显示(今天显示时间,更早显示日期+时间)
-   - 优化 UI:删除多余标签,位置已能区分发送者
-
-2. **代码优化和注释**
-   - 为所有代码添加详细的中文注释,解释每行代码的作用
-   - 使用通俗易懂的类比(如"数据盒子"、"书签"等)帮助理解
-   - 优化代码结构,确保逻辑清晰
-
-3. **文档完善**
-   - 创建 `doc/前端学习笔记.md`:核心概念解释、英文单词记忆、常见错误
-   - 创建 `doc/测试指南.md`:完整的测试步骤和问题排查指南
-   - 创建 `doc/系统角色说明.md`:解释访客和客服的区别
-   - 修复测试指南中的 localStorage 说明(同一浏览器标签页共享)
-
-4. **Bug修复**
-   - 修复 `.env` 文件 UTF-8 BOM 编码问题(godotenv 不支持 BOM)
-   - 添加后端 `.env` 文件加载的详细调试信息
-   - 修复代码结构问题(消息列表和输入框位置错误)
-
-### 技术细节
-- 使用 `useState` 管理消息列表、输入框、加载状态
-- 使用 `useEffect` 实现自动拉取消息和自动滚动
-- 使用 `useRef` 实现自动滚动到底部的功能
-- 使用 `localStorage` 持久化访客ID(同一浏览器标签页共享)
-
-### 用户体验
-- 访客无需登录,直接访问 `/chat` 即可使用
-- 发送消息后自动滚动到底部,无需手动滚动
-- 时间显示智能优化:今天只显示时间,更早显示日期+时间
-- UI 简洁:位置已能区分发送者,无需额外标签
-
-### 测试状态
-✅ 功能测试通过:消息发送、接收、显示、自动滚动均正常工作
-
-### 下一步计划
-- 开发客服端功能(客服登录、对话列表、客服聊天页面)
-- 或优化现有功能(消息状态、错误重试、UI 优化等)
-
-## 2025-01-15 10:00:00 UTC
-
-### 完成的工作
-1. 前端配置化改进
-   - 创建 `frontend/lib/config.ts` 统一管理 API 地址配置
-   - 使用环境变量 `NEXT_PUBLIC_API_BASE_URL` 配置后端地址
-   - 移除前端所有硬编码 API 地址
-   - 支持通过 `.env.local` 配置不同环境的后端地址
-2. 更新文档
-   - README 添加前端环境变量配置说明
-   - 补充部署环境切换的使用说明
-
-### 意义
-- 本地开发:无需配置,默认使用 `http://127.0.0.1:8080`
-- 生产部署:只需修改 `.env.local` 中的 `NEXT_PUBLIC_API_BASE_URL` 为实际域名
-- 好处:类比后端数据库配置,前端 API 配置也可通过环境变量灵活切换
-
-## 2025-10-30 12:00:00 UTC
-
-### 完成的工作
-1. 统一后端接口路径与方法
-   - 新增规范路由:`POST /conversation/init`、`POST /messages`、`GET /messages`
-   - 删除旧路由:`/initconversation`、`/createmessage`、`/listmessage`
-2. 数据库配置改为环境变量
-   - 从 `.env` 读取 `DB_HOST/DB_PORT/DB_USER/DB_PASSWORD/DB_NAME`
-   - 移除硬编码 DSN,提升安全性与可配置性
-3. 更新 README
-   - 补充接口文档与后端环境变量示例,增加 `.env` 使用说明
-
-### 风险与注意
-- 该变更为不向后兼容的变更,请确保前端使用新路由
-- 需要在 `backend/.env` 正确配置数据库连接参数
-
-## 2024-12-19 15:30:00 UTC
-
-### 完成的工作
-1. **完善对话初始化功能**
-   - 修复了 `InitConversation` 函数中不完整的代码逻辑
-   - 添加了完整的错误处理和响应返回
-   - 实现了查找现有对话或创建新对话的逻辑
-
-2. **创建项目文档**
-   - 编写了详细的 README.md 文件
-   - 包含项目结构、功能说明、API接口文档
-   - 添加了对话初始化逻辑的详细解释
-
-3. **代码优化**
-   - 统一了变量命名(req 替代 in)
-   - 改进了错误提示信息
-   - 添加了详细的中文注释
-
-### 技术细节
-- 对话初始化逻辑:先查找访客的现有开放对话,如果没有则创建新对话
-- 使用 GORM 进行数据库操作
-- 实现了完整的错误处理机制
-
-### 下一步计划
-- 完善发送消息功能
-- 实现拉取消息功能
-- 添加前端界面
-

+ 0 - 487
doc/WebSocket学习笔记.md

@@ -1,487 +0,0 @@
-# WebSocket 学习笔记(通俗版)
-
-## 一、什么是 WebSocket?
-
-### 1. 类比理解
-
-**HTTP 就像"打电话"**:
-- 你打电话 → 对方接听 → 你说完挂断
-- 每次都要重新拨号才能通话
-- 不能一直保持连接
-
-**WebSocket 就像"对讲机"**:
-- 你按住按钮说话 → 对方立即听到
-- 连接一直保持,随时可以说话
-- 不需要每次都"拨号"
-
-### 2. 为什么需要 WebSocket?
-
-**传统 HTTP 的问题**:
-- 访客发送消息 → 后端收到 → 但客服不知道有新消息
-- 客服必须手动刷新页面才能看到新消息
-- 无法实时推送消息
-
-**WebSocket 的解决方案**:
-- 访客发送消息 → 后端收到 → 后端立即"喊话"给所有连接的客户端
-- 客服不需要刷新,消息自动显示
-- 真正的实时通信!
-
-## 二、WebSocket 工作原理(整体流程)
-
-### 1. 连接建立过程
-
-```
-1. 前端:我要连接 WebSocket(就像敲门)
-   ws = new WebSocket("ws://127.0.0.1:8080/ws?conversation_id=123")
-
-2. 后端:检查请求,同意连接(就像开门)
-   → 升级 HTTP 连接为 WebSocket 连接
-
-3. 前端:连接成功!(可以开始"对话"了)
-   ws.onopen = () => { console.log("连接成功") }
-```
-
-**关键点**:
-- 第一次连接还是用 HTTP(就像敲门)
-- 后端"升级"这个连接为 WebSocket(就像开门让你进来)
-- 之后就可以双向通信了
-
-### 2. 消息发送和接收
-
-```
-前端发送消息:
-  前端 → [HTTP POST] → 后端 → 保存到数据库 → 通过 WebSocket 广播
-
-后端推送消息:
-  后端 → [WebSocket] → 所有连接的客户端 → 自动显示新消息
-```
-
-## 三、后端 WebSocket 工作原理
-
-### 1. Hub(中心管理器)
-
-**类比**:就像"会议室管理员"
-
-```go
-// backend/websocket/hub.go
-type Hub struct {
-    // 每个对话ID对应哪些客户端连接
-    // 就像:会议室1有哪些人在里面
-    conversations map[uint]map[*Client]bool
-    
-    // 注册新客户端(有人要加入)
-    register chan *Client
-    
-    // 注销客户端(有人要离开)
-    unregister chan *Client
-    
-    // 广播消息(有新消息要告诉所有人)
-    broadcast chan *Message
-}
-```
-
-**工作流程**:
-```
-1. 有人连接 → 发到 register 通道
-2. Hub 收到 → 把这个客户端加到对应对话的列表里
-3. 有新消息 → 发到 broadcast 通道
-4. Hub 收到 → 找到这个对话的所有客户端 → 发送消息给每个人
-```
-
-### 2. Client(客户端连接)
-
-**类比**:就像"会议室里的一个人"
-
-```go
-// backend/websocket/client.go
-type Client struct {
-    hub            *Hub           // 属于哪个 Hub
-    conn           *websocket.Conn // WebSocket 连接
-    send           chan *Message   // 发送消息的通道
-    conversationID uint            // 属于哪个对话
-}
-```
-
-**两个重要方法**:
-- `ReadPump()`:从客户端读取消息(比如心跳包)
-- `WritePump()`:向客户端发送消息(比如新消息推送)
-
-### 3. Handler(处理函数)
-
-**类比**:就像"门卫",检查谁要进来
-
-```go
-// backend/websocket/handler.go
-func HandleWebSocket(hub *Hub) gin.HandlerFunc {
-    return func(c *gin.Context) {
-        // 1. 从 URL 获取对话ID
-        conversationID := c.Query("conversation_id")
-        
-        // 2. 升级 HTTP 连接为 WebSocket 连接
-        conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
-        
-        // 3. 创建客户端
-        client := NewClient(hub, conn, conversationID)
-        
-        // 4. 注册到 Hub
-        hub.register <- client
-        
-        // 5. 启动读写循环
-        go client.WritePump()
-        go client.ReadPump()
-    }
-}
-```
-
-**关键步骤**:
-1. `upgrader.Upgrade()`:把 HTTP 连接"升级"成 WebSocket 连接
-2. `hub.register <- client`:告诉 Hub "我来了"
-3. `go client.WritePump()`:启动一个 goroutine(后台线程)来发送消息
-4. `go client.ReadPump()`:启动一个 goroutine 来接收消息
-
-### 4. 消息推送流程
-
-```go
-// backend/service/service.go
-func CreateMessage(db *gorm.DB, hub BroadcastHub) {
-    // 1. 创建消息
-    msg := models.Message{...}
-    db.Create(&msg)
-    
-    // 2. 通过 WebSocket 推送
-    hub.BroadcastMessage(conversationID, "new_message", msg)
-}
-```
-
-**推送过程**:
-```
-1. CreateMessage 调用 hub.BroadcastMessage()
-2. Hub 收到消息,放到 broadcast 通道
-3. Hub.Run() 循环检测到有新消息
-4. 找到这个对话的所有客户端
-5. 把消息发送给每个客户端(通过 client.send 通道)
-6. 每个客户端的 WritePump() 收到消息,通过 WebSocket 发送给前端
-```
-
-## 四、前端 WebSocket 工作原理
-
-### 1. WebSocket 客户端类
-
-**类比**:就像"对讲机"
-
-```typescript
-// frontend/lib/websocket.ts
-class WSClient {
-    private ws: WebSocket | null = null;
-    private conversationId: number;
-    private onMessage?: (message: WSMessage) => void;
-    
-    // 连接
-    connect() {
-        // 1. 创建 WebSocket 连接
-        const wsUrl = "ws://127.0.0.1:8080/ws?conversation_id=123";
-        this.ws = new WebSocket(wsUrl);
-        
-        // 2. 设置事件监听
-        this.ws.onopen = () => {
-            console.log("连接成功");
-        };
-        
-        this.ws.onmessage = (event) => {
-            // 收到消息
-            const message = JSON.parse(event.data);
-            if (this.onMessage) {
-                this.onMessage(message);
-            }
-        };
-        
-        this.ws.onerror = (error) => {
-            console.error("连接错误", error);
-        };
-        
-        this.ws.onclose = () => {
-            console.log("连接关闭");
-            // 尝试重连
-            this.attemptReconnect();
-        };
-    }
-}
-```
-
-**关键点**:
-- `new WebSocket(url)`:创建连接(会自动发送 HTTP 升级请求)
-- `ws.onopen`:连接成功时触发
-- `ws.onmessage`:收到消息时触发
-- `ws.onclose`:连接关闭时触发
-
-### 2. 在 React 组件中使用
-
-```typescript
-// frontend/app/chat/page.tsx
-useEffect(() => {
-    if (conversationId === null) return;
-    
-    // 创建 WebSocket 客户端
-    const wsClient = new WSClient({
-        conversationId: conversationId,
-        // 收到消息时的回调
-        onMessage: (message) => {
-            if (message.type === "new_message") {
-                // 把新消息添加到消息列表
-                setMessages((prevMessages) => {
-                    const newMsg = message.data;
-                    // 检查是否已存在(避免重复)
-                    const exists = prevMessages.some((msg) => msg.id === newMsg.id);
-                    if (exists) return prevMessages;
-                    // 添加到列表
-                    return [...prevMessages, newMsg];
-                });
-            }
-        },
-    });
-    
-    // 建立连接
-    wsClient.connect();
-    
-    // 组件卸载时断开连接
-    return () => {
-        wsClient.disconnect();
-    };
-}, [conversationId]);
-```
-
-**工作流程**:
-1. 页面加载时,创建 WebSocket 客户端
-2. 建立连接(自动发送请求到后端)
-3. 收到消息时,自动更新 React 状态(`setMessages`)
-4. React 自动重新渲染,显示新消息
-5. 页面关闭时,断开连接
-
-## 五、完整的消息流程(从发送到接收)
-
-### 场景:访客发送消息,客服立即看到
-
-```
-步骤 1:访客发送消息
-  前端 → [HTTP POST /messages] → 后端
-  {
-    conversation_id: 123,
-    content: "你好",
-    sender_is_agent: false
-  }
-
-步骤 2:后端处理
-  后端 → 保存到数据库 → 调用 hub.BroadcastMessage()
-
-步骤 3:Hub 广播消息
-  Hub → 找到对话 123 的所有客户端(访客和客服)
-      → 通过 WebSocket 发送给每个客户端
-
-步骤 4:前端接收消息
-  访客端:收到消息 → 自动显示(自己发的)
-  客服端:收到消息 → 自动显示(访客发的)
-
-步骤 5:完成
-  访客看到自己的消息
-  客服立即看到访客的消息(无需刷新!)
-```
-
-## 六、WebSocket vs HTTP
-
-### HTTP(传统方式)
-
-```
-访客发送消息:
-  前端 → [HTTP POST] → 后端 → 保存 → 返回成功
-
-客服查看消息:
-  前端 → [HTTP GET /messages] → 后端 → 返回所有消息
-  (需要手动刷新或定时轮询)
-```
-
-**问题**:
-- 客服不知道什么时候有新消息
-- 必须定时刷新(浪费资源)
-- 不是实时的
-
-### WebSocket(实时方式)
-
-```
-访客发送消息:
-  前端 → [HTTP POST] → 后端 → 保存 → [WebSocket 推送] → 客服端
-
-客服查看消息:
-  客服端 → [WebSocket 连接] → 后端
-  后端 → [有新消息时自动推送] → 客服端 → 自动显示
-```
-
-**优势**:
-- 实时推送,无需刷新
-- 节省资源(不需要定时轮询)
-- 双向通信(可以前后端互相发送)
-
-## 七、技术细节
-
-### 1. 连接升级(Upgrade)
-
-**HTTP 请求头**:
-```
-GET /ws?conversation_id=123 HTTP/1.1
-Host: 127.0.0.1:8080
-Upgrade: websocket
-Connection: Upgrade
-Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
-Sec-WebSocket-Version: 13
-```
-
-**HTTP 响应头**:
-```
-HTTP/1.1 101 Switching Protocols
-Upgrade: websocket
-Connection: Upgrade
-Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
-```
-
-**101 状态码**:表示协议切换成功(从 HTTP 切换到 WebSocket)
-
-### 2. 心跳检测(Ping/Pong)
-
-**为什么需要心跳**:
-- 保持连接活跃(防止被防火墙关闭)
-- 检测连接是否断开
-
-**工作原理**:
-```
-后端 → [每 54 秒发送 Ping] → 前端
-前端 → [收到 Ping,回复 Pong] → 后端
-```
-
-如果前端没有回复 Pong,后端就知道连接断开了。
-
-### 3. 自动重连
-
-```typescript
-// frontend/lib/websocket.ts
-private attemptReconnect() {
-    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
-        return; // 超过最大重试次数,停止重连
-    }
-    
-    this.reconnectAttempts++;
-    setTimeout(() => {
-        this.connect(); // 重新连接
-    }, 3000); // 等待 3 秒后重连
-}
-```
-
-**重连策略**:
-- 连接断开时自动尝试重连
-- 最多重试 5 次
-- 每次等待 3 秒
-
-## 八、记忆口诀
-
-1. **WebSocket = 对讲机**:一直保持连接,随时可以说话
-2. **HTTP = 打电话**:每次都要重新拨号
-3. **Hub = 会议室管理员**:管理所有连接的人
-4. **Client = 会议室里的人**:每个人都有自己的连接
-5. **Upgrade = 开门**:把 HTTP 连接"升级"成 WebSocket
-6. **Ping/Pong = 心跳**:保持连接活跃
-7. **Broadcast = 广播**:把消息发送给所有人
-
-## 九、常见问题
-
-### 1. WebSocket 和 HTTP 可以同时使用吗?
-
-**可以!** 就像:
-- HTTP:用来发送消息、获取列表等
-- WebSocket:用来接收实时推送
-
-### 2. 为什么需要心跳检测?
-
-**防止连接被关闭**:
-- 有些防火墙/代理会关闭长时间不活动的连接
-- 心跳告诉它们"我还活着"
-
-### 3. 如果 WebSocket 连接失败怎么办?
-
-**自动降级**:
-- 可以继续使用 HTTP 轮询(定时刷新)
-- 或者显示错误提示,让用户手动刷新
-
-### 4. WebSocket 比 HTTP 快吗?
-
-**不一定**:
-- WebSocket 的优势是"实时推送",不是速度
-- 第一次建立连接需要时间
-- 但之后推送消息非常快(不需要重新建立连接)
-
-## 十、代码示例总结
-
-### 后端关键代码
-
-```go
-// 1. 创建 Hub
-wsHub := websocket.NewHub()
-go wsHub.Run() // 启动 Hub
-
-// 2. 注册 WebSocket 路由
-r.GET("/ws", websocket.HandleWebSocket(wsHub))
-
-// 3. 推送消息
-hub.BroadcastMessage(conversationID, "new_message", msg)
-```
-
-### 前端关键代码
-
-```typescript
-// 1. 创建客户端
-const wsClient = new WSClient({
-    conversationId: conversationId,
-    onMessage: (message) => {
-        // 处理收到的消息
-    },
-});
-
-// 2. 建立连接
-wsClient.connect();
-
-// 3. 断开连接(组件卸载时)
-return () => {
-    wsClient.disconnect();
-};
-```
-
----
-
-## 总结
-
-**WebSocket 就像"对讲机"**:
-- 建立连接后,一直保持
-- 后端可以随时"喊话"给前端
-- 前端可以随时"回复"给后端
-- 真正的实时双向通信!
-
-**我们项目的实现**:
-- 后端用 Hub 管理所有连接
-- 消息创建后自动推送
-- 前端自动接收并显示
-- 无需手动刷新,真正的实时体验!
-
-**记住**:WebSocket 不是用来替代 HTTP 的,而是用来补充 HTTP 的实时推送能力!
-
----
-
-**学习建议**:
-1. 先理解整体流程(连接 → 发送 → 接收)
-2. 再看代码实现(Hub、Client、Handler)
-3. 运行项目,观察日志和浏览器控制台
-4. 尝试修改代码,看看效果
-
-**调试技巧**:
-- 打开浏览器控制台(F12),查看 WebSocket 连接状态
-- 查看后端日志,看连接和消息推送情况
-- 使用 `ws.onmessage` 打印收到的消息
-
-祝你学习愉快!🚀
-

+ 0 - 337
doc/前端学习笔记.md

@@ -1,337 +0,0 @@
-# 前端聊天页面 - 学习笔记(通俗版)
-
-## 一、核心概念理解
-
-### 1. useState(数据盒子)
-**类比**:就像一个小盒子,可以存东西,也可以拿出来
-- `const [数据, 改数据的函数] = useState(初始值)`
-- **例子**:`const [input, setInput] = useState("")`
-  - `input` = 盒子里现在装的是什么(当前值)
-  - `setInput` = 改变盒子里东西的函数
-  - `""` = 盒子一开始是空的
-
-**为什么需要?**
-- 数据变了,页面自动更新(不用手动刷新)
-
-### 2. useEffect(监听器)
-**类比**:就像"当...的时候,执行..."
-- `useEffect(() => { 做什么 }, [监听谁])`
-- `[]` = 空数组,意思是"只在页面第一次加载时执行一次"
-- `[某个数据]` = 当这个数据改变时执行
-
-**为什么需要?**
-- 页面加载时自动执行某些操作(比如拉取数据)
-- 当数据改变时,自动执行某些操作(比如自动滚动)
-
-### 3. useRef(指针/书签)
-**类比**:就像书签,可以指向页面上某个元素
-- `const ref = useRef(null)`
-- `ref.current` = 指向的那个元素
-
-**为什么需要?**
-- 实现自动滚动(找到消息列表底部,滚动到那里)
-
-### 4. async/await(等待)
-**类比**:就像"等外卖"
-- `async` = 这是一个需要等待的函数
-- `await` = 在这里等,等到了才继续
-- `fetch` = 发送网络请求(就像点外卖)
-
-**为什么需要?**
-- 网络请求需要时间,不能立即返回,所以要等
-
-### 5. useRouter(路由导航)
-**类比**:就像地图导航,告诉浏览器"我要去哪里"
-- `const router = useRouter()` = 获取路由对象
-- `router.push("/路径")` = 跳转到指定页面
-- `router.replace("/路径")` = 跳转并替换历史记录(不能返回)
-
-**为什么需要?**
-- 登录成功后需要跳转到其他页面
-- 未登录时需要跳转到登录页
-
-### 6. WebSocket(实时通信)
-**类比**:就像对讲机,可以实时双向通信
-- `WSClient` = WebSocket 客户端类,封装连接逻辑
-- `wsClient.connect()` = 建立连接
-- `wsClient.disconnect()` = 断开连接
-- `onMessage` = 收到消息时的回调函数
-
-**为什么需要?**
-- HTTP 只能客户端主动请求,不能服务器主动推送
-- WebSocket 可以服务器主动推送消息,实现实时通信
-- 新消息自动显示,无需手动刷新
-
-### 7. 多状态管理(复杂组件)
-**类比**:就像管理多个盒子,每个盒子存不同的数据
-- 对话列表状态:`const [conversations, setConversations] = useState([])`
-- 选中对话状态:`const [selectedConversationId, setSelectedConversationId] = useState(null)`
-- 消息列表状态:`const [messages, setMessages] = useState([])`
-- 输入框状态:`const [input, setInput] = useState("")`
-
-**为什么需要?**
-- 复杂页面需要管理多个数据
-- 每个数据独立管理,互不干扰
-- 数据改变时,只更新相关的 UI 部分
-
-## 二、常用英文单词记忆
-
-| 英文 | 中文意思 | 记忆技巧 |
-|------|---------|---------|
-| **state** | 状态 | 记住:state = 状态(盒子里的数据状态) |
-| **effect** | 效果/影响 | 记住:effect = 当...的时候产生的效果 |
-| **ref** | 引用/指针 | 记住:ref = reference(引用),指向某个东西 |
-| **async** | 异步的 | 记住:async = asynchronized(异步),需要等待 |
-| **await** | 等待 | 记住:await = wait(等待) |
-| **fetch** | 获取 | 记住:fetch = 去拿(去后端拿数据) |
-| **try** | 尝试 | 记住:try = 尝试(试试看能不能成功) |
-| **catch** | 抓住 | 记住:catch = 抓住(如果出错了,抓住错误) |
-| **finally** | 最终 | 记住:finally = 最终(最后一定要做的事) |
-| **preventDefault** | 阻止默认 | 记住:prevent(阻止)+ default(默认)= 阻止默认行为 |
-| **router** | 路由 | 记住:router = 路由器(导航到不同页面) |
-| **push** | 推送/跳转 | 记住:push = 推(推送到新页面) |
-| **replace** | 替换 | 记住:replace = 替换(替换当前页面) |
-| **params** | 参数 | 记住:params = parameters(参数),URL 中的参数 |
-| **websocket** | WebSocket | 记住:websocket = 实时通信协议(双向通信) |
-| **client** | 客户端 | 记住:client = 客户端(WebSocket 客户端) |
-| **connect** | 连接 | 记住:connect = 连接(建立 WebSocket 连接) |
-| **disconnect** | 断开 | 记住:disconnect = 断开(断开 WebSocket 连接) |
-| **callback** | 回调 | 记住:callback = 回调(收到消息时执行的函数) |
-| **layout** | 布局 | 记住:layout = 布局(页面布局结构) |
-| **dashboard** | 仪表盘 | 记住:dashboard = 仪表盘(工作台页面) |
-| **hook** | 钩子 | 记住:hook = React 自定义逻辑的钩子函数 |
-| **service** | 服务 | 记住:service = 统一封装接口请求的模块 |
-| **module** | 模块 | 记住:module = 一组功能组成的模块化单元 |
-
-## 三、代码执行流程(就像讲故事)
-
-### 访客端流程:
-1. **页面加载**
-   - 检查浏览器里有没有访客ID
-   - 如果没有,生成一个新的ID存起来
-
-2. **有了访客ID后**
-   - 打电话给后端:"给我一个对话ID"
-   - 后端回复:"你的对话ID是123"
-
-3. **有了对话ID后**
-   - 自动拉取这个对话的所有消息
-   - 显示在页面上
-
-4. **用户发送消息**
-   - 用户点击"发送"按钮
-   - 把消息内容发送给后端
-   - 后端保存成功
-   - 重新拉取消息(能看到刚发的)
-   - 自动滚动到底部
-
-5. **每次消息更新**
-   - 自动滚动到底部(让用户看到最新消息)
-
-### 客服端流程(旧版,已废弃):
-1. **登录页面加载**
-   - 用户输入用户名和密码
-   - 点击"登录"按钮
-
-2. **登录请求**
-   - 发送登录请求到后端
-   - 后端验证用户名和密码
-   - 返回用户信息(user_id、username、role)
-
-3. **登录成功**
-   - 保存用户信息到 localStorage
-   - 跳转到对话列表页面
-
-4. **对话列表页面**
-   - 检查是否已登录(检查 localStorage)
-   - 未登录则跳转到登录页
-   - 已登录则拉取所有未关闭的对话
-   - 显示对话列表
-
-5. **进入聊天页面**
-   - 点击对话,跳转到 `/agent/chat/[conversationId]`
-   - 从 URL 参数获取对话ID
-   - 拉取该对话的所有消息
-   - 客服消息显示在右侧,访客消息显示在左侧
-
-6. **发送消息**
-   - 客服输入消息,点击"发送"
-   - 发送时设置 `sender_is_agent: true`
-   - 消息显示在右侧(蓝色气泡)
-
-### 客服端流程(新版,四栏布局):
-1. **登录页面加载**
-   - 用户输入用户名和密码
-   - 点击"登录"按钮
-
-2. **登录请求**
-   - 发送登录请求到后端
-   - 后端验证用户名和密码
-   - 返回用户信息(user_id、username、role)
-
-3. **登录成功**
-   - 保存用户信息到 localStorage
-   - 跳转到 `/agent/dashboard`(四栏布局工作台)
-
-4. **工作台页面加载**
-   - 检查是否已登录(检查 localStorage)
-   - 未登录则跳转到登录页
-   - 已登录则拉取所有未关闭的对话
-   - 显示在左侧对话列表栏
-
-5. **选择对话**
-   - 点击左侧对话列表中的某个对话
-   - 更新 `selectedConversationId` 状态
-   - 自动拉取该对话的所有消息
-   - 建立 WebSocket 连接,接收实时消息
-   - 中间栏显示聊天内容
-   - 右侧栏显示访客详情
-
-6. **发送消息**
-   - 在中间栏输入框输入消息
-   - 点击"发送"按钮
-   - 发送时设置 `sender_is_agent: true`
-   - 消息通过 WebSocket 实时显示在右侧(蓝色气泡)
-
-7. **实时接收消息**
-   - WebSocket 接收到新消息
-   - 自动更新消息列表
-   - 自动滚动到底部
-   - 无需手动刷新
-
-## 四、模块化拆分(2025-11)
-
-> 拆分后的代码更像乐高积木,每一块负责自己的事情,组合起来就是完整的客服工作台。
-
-- **页面层(app/agent/dashboard/page.tsx)**
-  - 只做一件事:渲染 `<DashboardShell />`
-  - 没有业务逻辑,后续做 SSR / Route Handlers 时更轻松
-
-- **组件层(components/dashboard/)**
-  - `DashboardShell`:整合左中右三栏 + 顶部导航
-  - `NavigationSidebar / ConversationSidebar / MessageList / VisitorDetailPanel`:界面分块清晰,可复用
-  - `VisitorDetailPanel` 内置联系人信息编辑弹窗,点击“+ Add / 编辑”即可修改邮箱、电话、备注
-  - 样式问题在各自组件内部解决,互不影响
-  - `MessageList` 通过 `currentUserIsAgent` 参数兼容客服/访客视角
-
-- **Hook 层(features/agent/hooks/)**
-  - `useAuth`:登录信息获取 + 退出登录
-  - `useConversations`:对话列表、防抖搜索、未读数更新
-  - `useMessages`:消息拉取、已读状态、WebSocket 回调,新增 `updateContactInfo` 用于保存邮箱/电话/备注
-  - `useWebSocket`:封装连接/断开/错误处理
-
-- **Service 层(features/agent/services/)**
-  - 所有 `fetch` 请求集中在这里,例如 `conversationApi.ts`、`messageApi.ts`、`authApi.ts`
-  - 新增 `updateConversationContact` 方法,调用 `PUT /conversations/:id/contact` 更新访客联系信息
-  - 后续接入 React Query / SWR 时,只需要在这里改
-
-- **工具层(utils/)**
-  - `format.ts`:统一时间和消息预览格式
-  - `highlight.tsx`:关键词高亮组件化
-  - `storage.ts`:localStorage 读写统一封装
-
-> 小结:页面调用 Hook,Hook 使用 Service,Service 请求后端;UI 部分由组件层独立负责。以后要换样式或替换数据源,都有明确位置可以下手。
-
-### 目录与关键文件(2025-11)
-
-| 层级 | 目录 / 文件 | 作用说明 |
-|------|-------------|----------|
-| 页面入口 | `app/page.tsx` | 客服登录页,登录成功后跳转工作台 |
-| 页面入口 | `app/agent/dashboard/page.tsx` | 工作台入口,渲染 `DashboardShell` |
-| 页面入口 | `app/agent/chat/[conversationId]/page.tsx` | 旧单聊页面,复用新版组件与 Hook |
-| 页面入口 | `app/chat/page.tsx` | 访客端聊天页面,复用统一组件/服务 |
-| 组件层 | `components/dashboard/*` | `DashboardShell`、导航栏、会话列表、消息列表、访客详情、输入框等 UI 组件 |
-| Hook 层 | `features/agent/hooks/useAuth` | 处理登录态:读取/清理 localStorage,提供退出方法 |
-| Hook 层 | `features/agent/hooks/useConversations` | 统一管理会话列表、搜索、防抖、排序、选中会话 |
-| Hook 层 | `features/agent/hooks/useMessages` | 统一管理消息、已读状态、详情数据、WebSocket 回调 |
-| Hook 层 | `features/agent/hooks/useWebSocket` | 对 `WSClient` 的通用封装,负责连接/断开 |
-| Service | `features/agent/services/conversationApi` | 会话相关接口封装:列表、搜索、详情、更新访客联系信息 |
-| Service | `features/agent/services/messageApi` | 消息接口封装:拉取、发送、已读 |
-| Service | `features/agent/services/authApi` | 登录态接口(目前仅登出) |
-| Service | `features/visitor/services/conversationApi` | 访客端对话初始化接口(收集 UA、语言等) |
-| 类型定义 | `features/agent/types.ts` | 会话、消息、用户、WebSocket 负载等公共类型 |
-| 工具 | `utils/format.ts` | 时间格式化、消息预览截断 |
-| 工具 | `utils/highlight.tsx` | 关键词高亮渲染(返回 `<mark>`) |
-| 工具 | `utils/storage.ts` | localStorage 读写封装(获取/设置/清理客服账号) |
-| 工具 | `lib/websocket.ts` | `WSClient`:负责连接、自动重连、消息广播 |
-
-> 访客与客服共用同一套消息组件/服务:通过 `currentUserIsAgent`、`senderIsAgent` 等参数切换左右气泡与已读对勾。
-
-## 五、为什么这样写?
-
-### 为什么用 useState?
-- 如果不用:数据变了,页面不会自动更新,要手动刷新
-- 用了:数据一变,页面自动刷新
-
-### 为什么用 useEffect?
-- 如果不用:需要手动点击按钮才能拉取数据
-- 用了:页面加载时自动拉取数据
-
-### 为什么用 async/await?
-- 如果不用:代码不会等网络请求完成,可能会出错
-- 用了:等网络请求完成再继续,保证顺序
-
-### 为什么用 try/catch?
-- 如果不用:网络断了,程序会崩溃
-- 用了:即使出错,也能显示错误信息,程序不崩溃
-
-## 五、记忆口诀
-
-1. **useState**:数据盒子,存和改
-2. **useEffect**:当...的时候,做...
-3. **useRef**:书签指针,找元素
-4. **async/await**:等待外卖,别着急
-5. **try/catch**:试试看,错了别慌
-6. **useRouter**:地图导航,跳页面
-7. **localStorage**:浏览器存储,存数据
-8. **URL参数**:从路径拿数据,用 useParams
-9. **WebSocket**:对讲机,实时通信
-10. **多状态**:多个盒子,各管各的
-11. **布局**:四栏布局,各司其职
-
-## 六、常见错误和解决
-
-1. **忘记写 await**
-   - 错误:`fetch(...)` 后面直接用 `.json()`
-   - 正确:`await fetch(...)` 然后 `await res.json()`
-
-2. **useEffect 依赖写错**
-   - 错误:`[]` 写成了 `[data]`,导致无限循环
-   - 正确:想清楚"当谁改变时才执行"
-
-3. **忘记检查数据是否存在**
-   - 错误:直接用 `data.id`,如果 data 是 null 就报错
-   - 正确:先检查 `if (data)` 再使用
-
-4. **忘记检查登录状态**
-   - 错误:直接访问需要登录的页面,没有检查是否已登录
-   - 正确:在页面加载时检查 localStorage 中是否有登录信息
-
-5. **路由跳转问题**
-   - 错误:使用 `window.location.href` 跳转(会刷新页面)
-   - 正确:使用 `router.push()` 跳转(不会刷新,更流畅)
-
-6. **WebSocket 连接问题**
-   - 错误:在组件每次渲染时都创建新连接(导致连接泄漏)
-   - 正确:在 `useEffect` 中创建连接,在清理函数中断开连接
-   - 注意:对话切换时,需要断开旧连接,建立新连接
-
-7. **状态管理问题**
-   - 错误:把所有数据放在一个状态里(难以管理)
-   - 正确:按功能拆分状态(对话列表、选中对话、消息列表等)
-   - 注意:状态更新时,只更新相关的 UI 部分
-
-8. **对话切换问题**
-   - 错误:切换对话时不清空消息列表(可能显示错误的消息)
-   - 正确:切换对话时先清空消息列表,再加载新对话的消息
-
-## 七、练习建议
-
-1. **多写注释**:每行代码都写注释,解释"为什么要这样写"
-2. **画流程图**:把代码执行流程画出来,理清思路
-3. **小步调试**:每次只改一小部分,测试看看效果
-4. **多看报错**:出错时看报错信息,学会查问题
-
----
-
-**记住**:编程就像学开车,一开始慢,多练习就熟了!🚀

+ 0 - 458
doc/后端学习笔记.md

@@ -1,458 +0,0 @@
-# 后端学习笔记(Go + Gin + GORM)
-
-## 一、核心概念理解
-
-### 1. Gin 框架(Web框架)
-**类比**:就像餐厅的服务员,接收客人的请求,然后告诉厨房(处理函数)做什么菜
-
-- `gin.Default()` = 创建一个 Gin 服务器(就像开一家餐厅)
-- `r.POST("/路径", 处理函数)` = 注册一个路由(就像在菜单上写:客人点这个菜,叫这个厨师做)
-- `r.Run(":8080")` = 启动服务器,监听 8080 端口(就像开门营业)
-
-**为什么需要?**
-- 没有框架:需要自己处理 HTTP 请求、解析 JSON、路由等(很麻烦)
-- 有了框架:只需要写业务逻辑,框架帮你处理其他事情(简单)
-
-### 2. GORM(ORM框架)
-**类比**:就像翻译官,帮你把 Go 代码翻译成 SQL 语句
-
-- `db.Create(&user)` = 插入数据(翻译成 `INSERT INTO users ...`)
-- `db.Where("username=?", name).First(&user)` = 查询数据(翻译成 `SELECT * FROM users WHERE username=?`)
-- `db.AutoMigrate(&User{})` = 自动创建表(根据结构体自动建表)
-
-**为什么需要?**
-- 不用 GORM:需要手写 SQL 语句(容易出错,不安全)
-- 用了 GORM:用 Go 代码操作数据库,自动生成 SQL(安全、简单)
-
-### 3. 环境变量(.env 文件)
-**类比**:就像餐厅的配置表,记录"数据库地址"、"密码"等敏感信息
-
-- `godotenv.Load()` = 从 `.env` 文件读取配置
-- `os.Getenv("DB_HOST")` = 获取环境变量的值
-
-**为什么需要?**
-- 不写在代码里:密码等信息不会泄露,不同环境可以不同配置
-- 写在代码里:密码硬编码,不安全,换环境要改代码
-
-### 4. 中间件(Middleware)
-**类比**:就像餐厅的"检查站",每个请求都要经过这些检查
-
-- `r.Use(middleware.Logger())` = 记录每个请求的日志(就像记录每个客人点了什么)
-- `r.Use(middleware.CORS())` = 允许跨域请求(就像允许其他网站访问我们的 API)
-
-**为什么需要?**
-- 每个路由都要写日志、跨域处理等代码(重复)
-- 用中间件:统一处理,代码简洁
-
-### 5. Handler(处理函数)
-**类比**:就像餐厅的厨师,负责处理具体的业务逻辑
-
-- `func Register(db *gorm.DB) gin.HandlerFunc` = 注册用户的处理函数
-- `c.ShouldBindJSON(&u)` = 把请求的 JSON 数据解析成结构体
-- `c.JSON(200, gin.H{"message": "成功"})` = 返回 JSON 响应给前端
-
-**为什么需要?**
-- 接收前端请求 → 处理业务逻辑 → 返回结果
-
-### 6. 初始化默认数据
-**类比**:就像餐厅开业时,自动准备一些基础食材
-
-- `initDefaultAdmin(db)` = 在首次启动时自动创建默认管理员账号
-- 检查数据库是否已有管理员,如果没有则创建
-
-**为什么需要?**
-- 系统首次启动时,需要有一个管理员账号才能登录
-- 避免手动创建管理员账号的麻烦
-
-## 二、常用英文单词记忆
-
-| 英文 | 中文意思 | 记忆技巧 |
-|------|---------|---------|
-| **handler** | 处理函数 | 记住:handler = 处理者(处理请求的人) |
-| **middleware** | 中间件 | 记住:middle(中间)+ ware(件)= 中间件 |
-| **router** | 路由 | 记住:router = 路由器(把请求路由到对应的处理函数) |
-| **context** | 上下文 | 记住:context = 上下文(包含请求和响应的信息) |
-| **migrate** | 迁移 | 记住:migrate = 迁移(把结构体迁移成数据库表) |
-| **query** | 查询 | 记住:query = 查询(从数据库查数据) |
-| **bind** | 绑定 | 记住:bind = 绑定(把 JSON 绑定到结构体) |
-| **hash** | 哈希 | 记住:hash = 哈希(密码加密) |
-| **bcrypt** | 加密算法 | 记住:bcrypt = 密码加密算法(B + crypt = 加密) |
-| **env** | 环境变量 | 记住:env = environment(环境)的缩写 |
-| **error** | 错误 | 记住:error = 错误(Go 语言中常用) |
-| **return** | 返回 | 记住:return = 返回(返回结果或退出函数) |
-
-## 三、代码执行流程(就像讲故事)
-
-### 1. 服务器启动(main.go)
-```
-1. 加载 .env 文件(读取数据库配置)
-2. 连接数据库(NewDB)
-3. 自动创建表(AutoMigrate)
-4. 初始化默认管理员(initDefaultAdmin)
-5. 创建 Gin 服务器(gin.Default)
-6. 注册中间件(Logger、CORS)
-7. 注册路由(POST /login, POST /conversation/init, GET /conversations, PUT /conversations/:id/contact 等)
-8. 启动服务器(监听 8080 端口)
-```
-
-### 2. 处理一个请求(比如登录)
-```
-1. 前端发送 POST /login 请求
-2. Gin 框架接收请求
-3. 经过中间件(记录日志、允许跨域)
-4. 找到对应的处理函数(Login)
-5. 解析 JSON 数据(ShouldBindJSON)
-6. 查询用户是否存在(db.Where)
-7. 验证密码(bcrypt.CompareHashAndPassword)
-8. 返回 JSON 响应(包含 user_id、username、role)
-9. 前端收到响应,保存到 localStorage
-```
-
-### 3. 处理一个请求(比如获取对话列表)
-```
-1. 前端发送 GET /conversations 请求
-2. Gin 框架接收请求
-3. 经过中间件(记录日志、允许跨域)
-4. 找到对应的处理函数(ListConversations)
-5. 查询数据库(db.Where("status != ?", "closed"))
-6. 按更新时间倒序排列(Order("updated_at desc"))
-7. 返回 JSON 响应(对话列表)
-8. 前端收到响应,显示对话列表
-```
-
-### 4. 服务器首次启动流程
-```
-1. 加载 .env 文件
-2. 连接数据库(NewDB)
-3. 自动创建表(AutoMigrate)
-4. 检查是否有管理员账号(initDefaultAdmin)
-5. 如果没有,创建默认管理员(admin/admin123)
-6. 创建 Gin 服务器
-7. 注册路由和中间件
-8. 启动服务器
-```
-
-### 5. 数据库操作流程
-```
-1. 定义结构体(models.User)
-2. GORM 自动创建表(AutoMigrate)
-3. 查询:db.Where("条件").First(&结果)
-4. 插入:db.Create(&数据)
-5. 更新:db.Save(&数据)
-6. 删除:db.Delete(&数据)
-```
-
-## 四、为什么这样写?
-
-### 为什么用 Gin 框架?
-- 如果不用:需要自己处理 HTTP 协议、解析请求、路由等(很复杂)
-- 用了:只需要写业务逻辑,框架帮你处理其他事情(简单高效)
-
-### 为什么用 GORM?
-- 如果不用:需要手写 SQL,容易出错,有 SQL 注入风险
-- 用了:用 Go 代码操作数据库,自动生成安全的 SQL(安全、简单)
-
-### 为什么用环境变量?
-- 如果不用:密码硬编码在代码里,不安全,换环境要改代码
-- 用了:配置和代码分离,安全,灵活
-
-### 为什么用中间件?
-- 如果不用:每个路由都要写日志、跨域等代码(重复)
-- 用了:统一处理,代码简洁,易维护
-
-### 为什么用 bcrypt 加密密码?
-- 如果不用:密码明文存储,泄露了别人就能登录
-- 用了:密码加密存储,即使泄露也无法还原(只能验证)
-
-## 五、代码结构说明
-
-### 项目结构
-```
-backend/
-├── main.go               # 入口:环境加载、依赖初始化、路由注册
-├── controller/           # HTTP 层:接收请求、参数校验、调用业务服务
-├── service/              # 业务层:会话/消息/认证逻辑,使用仓储与 WebSocket
-├── service/types.go      # 业务层通用请求/返回结构体
-├── repository/           # 仓储层:封装 GORM 操作(User/Conversation/Message)
-├── router/               # 路由注册集中处(RegisterRoutes)
-├── utils/                # 工具函数(请求信息解析、UA 解析等)
-├── websocket/            # Hub/Client/Handler:管理长连接与广播
-├── infra/                # 基础设施(数据库连接 NewDB 等)
-├── middleware/           # 中间件(日志、CORS)
-├── models/               # 数据模型(User、Conversation、Message)
-└── .env                  # 环境变量配置
-```
-
-### 分层架构
-```
-main.go
-  ↓ 初始化仓储 / 服务 / 控制器 / WebSocket Hub
-router.RegisterRoutes
-  ↓ controller 层(解析请求 → 调用 service)
-  ↓ service 层(编排业务 → 调用 repository、websocket)
-  ↓ repository 层(GORM 操作数据库)
-  ↓ models + 数据库
-```
-
-**关键职责拆分:**
-- **controller**:只做输入输出转换,避免直接访问数据库
-- **service**:编排业务流程(例如发送消息时刷新会话更新时间、广播 WebSocket)
-- **repository**:封装 `FindOpenByVisitorID`、`ListActive`、`MarkMessagesRead` 等数据库操作
-- **websocket**:`hub.go` 管理连接字典,`client.go` 负责读写协程,`handler.go` 升级 HTTP连接
-- **utils/request.go**:`GetClientIP`、`ParseUserAgent` 等通用方法
-- **service/types.go**:`ConversationSummary`、`MessageItem` 等跨服务共享结构体
-
-> 重构之后:新增服务时,只需新增仓储方法 + service + controller,不再在 `main.go` 里堆业务代码。
-
-### 关键结构体与函数速查
-
-| 名称 | 所在文件 | 作用与要点 |
-|------|----------|------------|
-| `controller.AuthController` | `controller/auth_controller.go` | 处理登录/退出,依赖 `AuthService`,统一 JSON 响应格式 |
-| `controller.ConversationController` | `controller/conversation_controller.go` | 初始化会话、列表、搜索、状态更新、详情查询、更新访客联系信息 |
-| `controller.MessageController` | `controller/message_controller.go` | 创建消息、拉取消息、批量已读 |
-| `service.AuthService` | `service/auth_service.go` | 登录校验、账号创建(管理员) |
-| `service.ConversationService` | `service/conversation_service.go` | 会话编排:初始化系统消息、列表拼装、搜索、状态/联系信息更新 |
-| `service.MessageService` | `service/message_service.go` | 发送消息、拉取消息、已读逻辑 + WebSocket 广播 |
-| `service.AdminService` | `service/admin_service.go` | 创建客服/管理员账号 |
-| `repository.ConversationRepository` | `repository/conversation_repository.go` | 会话数据库操作:按访客查找、批量查询、更新状态/字段等 |
-| `repository.MessageRepository` | `repository/message_repository.go` | 消息数据库操作:保存、按会话拉取、统计未读、标记已读 |
-| `repository.UserRepository` | `repository/user_repository.go` | 用户查询/创建 |
-| `router.RegisterRoutes` | `router/router.go` | 集中注册所有 REST + WebSocket 路由(含 `/conversations/:id/contact`) |
-| `websocket.Hub` / `Client` | `websocket/hub.go`、`client.go` | 维护对话房间 → 客户端列表,负责广播消息/已读事件 |
-| `utils.GetClientIP` / `ParseUserAgent` | `utils/request.go` | 请求上下文工具函数,供访客信息采集、日志使用 |
-| `service/types.go` | - | 定义 `ConversationSummary`、`CreateMessageInput` 等跨层 DTO,保持类型统一 |
-
-## 六、常见代码模式
-
-### 1. Handler 函数模式
-```go
-func Register(db *gorm.DB) gin.HandlerFunc {
-    return func(c *gin.Context) {
-        // 1. 解析请求数据
-        var u models.User
-        if err := c.ShouldBindJSON(&u); err != nil {
-            c.JSON(400, gin.H{"error": err.Error()})
-            return
-        }
-        
-        // 2. 业务逻辑处理
-        // ...
-        
-        // 3. 返回响应
-        c.JSON(200, gin.H{"message": "成功"})
-    }
-}
-```
-
-**为什么这样写?**
-- 返回 `gin.HandlerFunc`:可以传入数据库连接等参数
-- `c *gin.Context`:包含请求和响应的信息
-- 先检查错误,再处理:提前返回,代码清晰
-
-### 2. 错误处理模式
-```go
-if err != nil {
-    // 处理错误
-    return
-}
-// 继续执行
-```
-
-**为什么这样写?**
-- Go 语言的习惯:错误优先处理
-- 提前返回:避免嵌套,代码清晰
-
-### 3. 数据库查询模式
-```go
-var user models.User
-if err := db.Where("username=?", name).First(&user).Error; err != nil {
-    // 没找到
-    return
-}
-// 找到了,使用 user
-```
-
-**为什么这样写?**
-- `First`:找到第一条记录,没找到返回错误
-- `&user`:传入指针,结果存到 user 里
-- 检查错误:确保数据存在才继续
-
-## 七、记忆口诀
-
-1. **Gin**:Web框架,处理请求
-2. **GORM**:ORM框架,操作数据库
-3. **Handler**:处理函数,处理业务逻辑
-4. **Middleware**:中间件,统一处理
-5. **Context**:上下文,包含请求响应
-6. **Error**:错误优先,提前返回
-
-## 八、常见错误和解决
-
-### 1. 忘记处理错误
-**错误**:`db.Create(&user)` 不检查错误
-**正确**:
-```go
-if err := db.Create(&user).Error; err != nil {
-    // 处理错误
-    return
-}
-```
-
-### 2. 环境变量没加载
-**错误**:`os.Getenv("DB_HOST")` 返回空字符串
-**解决**:
-- 检查 `.env` 文件是否存在
-- 检查文件编码(不能有 BOM)
-- 检查 `godotenv.Load()` 是否执行
-
-### 3. 数据库连接失败
-**错误**:`数据库连接失败`
-**解决**:
-- 检查 MySQL 是否启动
-- 检查 `.env` 配置是否正确
-- 检查数据库是否存在
-
-### 4. 跨域问题
-**错误**:前端请求被阻止
-**解决**:
-- 检查是否添加了 CORS 中间件
-- 检查中间件配置是否正确
-
-### 5. 密码加密问题
-**错误**:密码明文存储或验证失败
-**解决**:
-- 注册时用 `bcrypt.GenerateFromPassword` 加密
-- 登录时用 `bcrypt.CompareHashAndPassword` 验证
-
-### 6. 默认管理员创建问题
-**错误**:首次启动时没有管理员账号,无法登录
-**解决**:
-- 使用 `initDefaultAdmin` 函数在首次启动时自动创建
-- 检查是否已存在,避免重复创建
-
-## 九、调试技巧
-
-### 1. 查看日志
-- 后端启动时看日志输出
-- 请求处理时看日志记录
-- 错误时看错误信息
-
-### 2. 使用 Postman 测试
-- 不用写前端,直接测试 API
-- 可以测试各种情况(成功、失败等)
-
-### 3. 检查数据库
-- 用 MySQL 客户端查看数据
-- 确认数据是否正确保存
-
-### 4. 打印调试信息
-```go
-log.Printf("调试信息: %v", 变量)
-```
-
-## 十、学习建议
-
-1. **先理解概念**:理解框架的作用,不要死记硬背
-2. **多看代码**:看别人的代码,理解写法
-3. **多写注释**:每行代码都写注释,理解为什么这样写
-4. **小步调试**:每次只改一小部分,测试看看效果
-5. **多看错误**:出错时看错误信息,学会查问题
-6. **画流程图**:把代码执行流程画出来,理清思路
-
-## 十一、WebSocket 实时通信(已实现)
-
-### 1. WebSocket 的作用
-**类比**:就像对讲机,可以实时双向通信
-- HTTP:只能客户端主动请求,服务器被动响应
-- WebSocket:客户端和服务器可以随时互相发送消息
-
-**为什么需要?**
-- 新消息需要实时推送给客服,无需手动刷新
-- 对话状态更新需要实时同步
-- 提升用户体验,感觉更流畅
-
-### 2. WebSocket 架构
-```
-Hub(中心管理器)
-  ├── conversations(对话ID → 客户端列表)
-  ├── register(注册新客户端)
-  ├── unregister(注销客户端)
-  └── broadcast(广播消息)
-
-Client(客户端连接)
-  ├── hub(所属的 Hub)
-  ├── conn(WebSocket 连接)
-  ├── send(发送消息的通道)
-  └── conversationID(所属的对话ID)
-```
-
-### 3. 消息流程
-```
-1. 客户端建立 WebSocket 连接
-2. 客户端发送对话ID,注册到 Hub
-3. Hub 将客户端加入对应对话的客户端列表
-4. 当有新消息时,Hub 广播给该对话的所有客户端
-5. 客户端收到消息,更新 UI
-```
-
-### 4. 心跳机制
-- 客户端定期发送 ping 消息
-- 服务器回复 pong 消息
-- 如果长时间没有收到 ping,断开连接
-
-**为什么需要?**
-- 检测连接是否断开
-- 保持连接活跃
-- 自动清理断开的连接
-
-## 十二、后端分层架构(2025-11 重构版)
-
-### 1. 目录结构(核心部分)
-```
-backend/
-├─ main.go                 // 程序入口,只负责依赖注入和启动
-├─ router/                 // 路由注册(把 URL 映射到控制器)
-├─ controller/             // 控制器层,处理 HTTP 请求/响应
-├─ service/                // 业务层,编排业务逻辑
-├─ repository/             // 数据访问层,封装 GORM 操作
-├─ utils/                  // 工具函数(IP、User-Agent 等)
-└─ websocket/              // WebSocket Hub、Client 等
-```
-
-### 2. 各层职责
-- **router**:统一注册所有路由,中间件分组时也只在这里改。
-- **controller**:只关心“请求 → 调用服务 → 返回响应”,不会出现 SQL 或业务细节。
-- **service**:聚焦业务规则,可以组合多个 repository、触发 WebSocket 推送等。
-- **repository**:集中管理所有数据库读写,便于修改查询逻辑或引入缓存。
-- **utils**:存放通用工具,避免重复代码。
-
-### 3. 调用顺序
-```
-HTTP 请求
-   ↓
-router → controller → service → repository
-                                  ↓
-                                MySQL
-```
-(若需要 WebSocket 推送,service 会在写库后调用 `hub.BroadcastMessage`。)
-
-### 4. 为什么这样做?
-- 代码职责更清晰:想改接口,看 controller;想改业务,看 service;想改 SQL 看 repository。
-- 更容易写单元测试:service 不依赖 gin,repository 可以替换成 mock。
-- 方便扩展:以后要接入 gRPC/消息队列,只需在 service 层复用已有逻辑。
-
-### 5. 迁移提示
-- 新增接口时先写 service & repository,再写 controller 和 router。
-- 记得运行 `gofmt`(PowerShell 举例):
-  ```powershell
-  cd backend
-  Get-ChildItem main.go, router, controller, service, repository, utils -Recurse -Filter *.go -File |
-    ForEach-Object { gofmt -w $_.FullName }
-  ```
-- main.go 只负责依赖注入(NewRepository → NewService → NewController)。
-
----
-
-**记住**:后端开发就像搭建一个服务系统,框架帮你处理底层细节,你只需要关注业务逻辑!🚀
-

+ 0 - 404
doc/待实现需求清单.md

@@ -1,404 +0,0 @@
-# 📋 待实现需求清单
-
-## 第一阶段:核心功能优化(优先级:高)
-
-### 1. UI 布局改进(客服端四栏布局)✅ 部分完成
-**完成状态**:
-- ✅ **四栏布局基础框架**(已实现)
-  - ✅ 最左侧导航菜单栏(固定宽度 64px)
-  - ✅ 左侧对话列表栏(固定宽度 320px)
-  - ✅ 中间聊天内容栏(自适应宽度)
-  - ✅ 右侧访客详情栏(固定宽度 320px)
-  - ✅ 统一顶部栏高度(h-16),三栏对齐
-- ✅ **左侧栏对话列表**(基础功能已实现)
-  - ✅ 显示所有对话
-  - ✅ 对话头像(圆形,基于 visitor_id 生成颜色)
-  - ✅ 对话ID、访客ID、状态显示
-  - ✅ 最后更新时间显示
-  - ✅ 对话选择高亮
-  - ✅ 搜索框("Q Search")- **已完成**(支持搜索对话内容、对话ID、访客ID)
-  - ✅ 最后一条消息预览 - **已完成**(显示最后一条消息的前50个字符)
-  - ✅ 搜索后点击对话自动定位到匹配消息 - **已完成**(居中显示历史消息,最后一条滚动到底部)
-  - ✅ 关键词高亮显示 - **已完成**(黄色背景高亮)
-  - ✅ 已读/未读状态图标 - **基础完成**(单/双对勾 + 未读徽标,未读数量统计将继续迭代)
-- ✅ **中间栏聊天内容**(核心功能已实现)
-  - ✅ 对话标题显示
-  - ✅ 消息区域(滚动显示)
-  - ✅ 访客消息(左侧,白色气泡)
-  - ✅ 客服消息(右侧,蓝色气泡)
-  - ✅ 时间戳显示(智能格式化)
-  - ✅ 输入框和发送按钮
-  - ✅ 自动滚动到底部
-  - ✅ WebSocket 实时消息推送
-  - ❌ 系统消息显示 - **待实现**
-  - ❌ "last seen" 信息 - **待实现**(已显示但为静态数据)
-- ✅ **右侧栏访客详情**(UI 已实现,数据待完善)
-  - ✅ 访客头像(基于 visitor_id 生成颜色)
-  - ✅ 在线/离线状态显示(基于对话状态)
-  - ✅ 联系信息区域(邮箱、电话、备注)
-  - ✅ 技术信息区域(网站、来源、位置等)
-  - ✅ 刷新按钮和更多选项图标
-  - ❌ 联系信息手动添加功能 - **待实现**(只有按钮占位)
-  - ❌ 技术信息自动收集和显示 - **待实现**(只有占位文本)
-- ✅ **无需跳转切换对话**(已实现)
-  - ✅ 点击对话列表切换,中间栏和右侧栏自动更新
-  - ✅ 对话切换时自动加载消息
-  - ✅ WebSocket 连接自动切换
-
-**需求描述**(剩余部分):
-- **左侧栏增强**:
-  - 搜索框("Q Search"),支持搜索对话内容
-  - 最后一条消息预览
-  - 已读/未读状态图标(未读数量统计上限、批量操作待完善)
-- **中间栏增强**:
-  - 系统消息显示(如 "Visitor opened the page", "You joined the chat")
-  - 实时 "last seen" 信息
-- **右侧栏数据完善**:
-  - 联系信息手动添加/编辑功能
-  - 技术信息自动收集和显示
-
-**技术要点**:
-- ✅ 路由已改为 `/agent/dashboard`(统一页面)
-- ✅ 使用 React 状态管理当前选中的对话
-- ❌ 响应式设计(移动端可能需要折叠侧栏)- **待实现**
-- ✅ 左侧栏:对话列表组件
-- ✅ 中间栏:聊天内容组件
-- ✅ 右侧栏:访客详情组件
-
-**预计剩余工作量**:0.5-1 周(完成搜索、系统消息、数据收集等功能)
-
----
-
-### 1.1. 会话搜索功能 ✅ 已完成
-**完成状态**:
-- ✅ 搜索框UI(已实现)
-- ✅ 后端搜索接口(`GET /conversations/search?q=关键词`)
-- ✅ 搜索范围:消息内容、对话ID、访客ID
-- ✅ 前端实时搜索(300ms防抖)
-- ✅ 搜索结果高亮显示(关键词黄色背景高亮)
-- ✅ 点击搜索结果自动定位到匹配消息(历史消息居中显示,最后一条滚动到底部)
-- ✅ 搜索结果实时更新
-
-**技术实现**:
-- 后端:`SearchConversations` 函数,支持搜索消息内容和ID
-- 前端:防抖搜索、关键词高亮、消息定位
-
----
-
-### 1.2. 客服个人资料管理 ✅ 已完成
-**完成状态**:
-- ✅ **数据库**(已实现)
-  - ✅ User 模型增加字段:`avatar_url`、`nickname`、`email`、`created_at`、`updated_at`
-- ✅ **后端**(已实现)
-  - ✅ 文件存储服务(`backend/infra/storage.go`):本地存储服务,可扩展为云存储
-  - ✅ 个人资料服务(`backend/service/profile_service.go`):提供获取、更新个人资料和上传头像功能
-  - ✅ 个人资料控制器(`backend/controller/profile_controller.go`):处理 HTTP 请求
-  - ✅ 新增接口:
-    - ✅ `GET /agent/profile/:user_id`:获取个人资料
-    - ✅ `PUT /agent/profile/:user_id`:更新个人资料(昵称、邮箱)
-    - ✅ `POST /agent/avatar/:user_id`:上传头像(支持 jpg、png、gif,最大 10MB)
-  - ✅ 静态文件服务:`/uploads` 路径用于访问上传的头像等文件
-- ✅ **前端**(已实现)
-  - ✅ 个人资料 API 服务(`frontend/features/agent/services/profileApi.ts`)
-  - ✅ 个人资料 Hook(`frontend/features/agent/hooks/useProfile.ts`):管理个人资料状态
-  - ✅ 个人资料弹窗组件(`frontend/components/dashboard/ProfileModal.tsx`):
-    - ✅ 显示和编辑昵称、邮箱
-    - ✅ 上传头像(支持预览、上传进度、错误提示)
-    - ✅ 实时更新个人资料
-  - ✅ DashboardHeader:显示头像和设置按钮,点击打开个人资料弹窗
-  - ✅ 头像工具函数(`frontend/utils/avatar.ts`):拼接完整的头像 URL、生成头像颜色、获取头像显示文本
-
-**功能特性**:
-- ✅ 支持头像上传(jpg、png、gif,最大 10MB)
-- ✅ 支持修改昵称和邮箱
-- ✅ 头像实时预览
-- ✅ 如果没有上传头像,显示彩色圆形头像(基于用户ID生成颜色)
-- ✅ 头像显示在 DashboardHeader 中
-
-**技术要点**:
-- ✅ 文件存储采用可扩展设计,目前使用本地存储,后续可切换为云存储(OSS、S3 等)
-- ✅ 头像 URL 拼接逻辑:如果后端返回相对路径,前端自动拼接 API_BASE_URL
-- ✅ 头像上传支持文件类型和大小验证
-- ✅ 个人资料更新实时刷新 UI
-
-**预计工作量**:已完成
-
----
-
-### 1.3. 访客信息收集和显示 ✅ 部分完成
-**完成状态**:
-- ✅ **右侧栏 UI 显示**(已实现)
-  - ✅ 联系信息区域(邮箱、电话、备注)
-  - ✅ 技术信息区域(网站、来源、位置、语言、浏览器、操作系统、User Agent、IP地址)
-  - ✅ 添加按钮(邮箱、电话、备注)
-- ✅ **自动收集技术信息**
-  - ✅ 网站(当前页面URL)
-  - ✅ 来源(referrer,从哪个页面跳转过来)
-  - ✅ 浏览器信息(User Agent解析)
-  - ✅ 操作系统(User Agent解析)
-  - ✅ 语言(浏览器语言设置)
-  - ✅ IP地址(后端获取)
-  - ❌ 位置(通过IP地址定位,需要第三方服务)
-- ✅ **手动添加联系信息**
-  - ✅ 邮箱添加/编辑功能(`PUT /conversations/:id/contact`)
-  - ✅ 电话添加/编辑功能(`PUT /conversations/:id/contact`)
-  - ✅ 备注添加/编辑功能(`PUT /conversations/:id/contact`)
-
-**需求描述**(剩余部分):
-- **自动收集**:访客端自动收集技术信息
-  - 网站(当前页面URL)
-  - 来源(referrer,从哪个页面跳转过来)
-  - 浏览器信息(User Agent解析)
-  - 操作系统(User Agent解析)
-  - 语言(浏览器语言设置)
-  - IP地址(后端获取)
-  - 位置(通过IP地址定位,需要第三方服务)
-- **手动添加**:客服可以手动添加/编辑联系信息(已支持,后续可迭代优化校验/权限)
-- **新增能力**:支持清空邮箱/电话/备注(输入空值即可)
-
-**技术要点**:
-- 数据库:
-  - 对话表增加字段:`website`, `referrer`, `browser`, `os`, `language`, `ip_address`, `location`
-  - 访客表或对话表增加字段:`email`, `phone`, `notes`(客服手动添加)
-- 前端(访客端):
-  - 页面加载时收集:`window.location.href`, `document.referrer`, `navigator.userAgent`, `navigator.language`
-  - 发送到后端保存
-- 后端:
-  - 接收访客信息并保存
-  - 获取IP地址(从请求头)
-  - IP地址定位(可选,需要第三方API)
-- ✅ 新增接口:`PUT /conversations/:id/contact`(更新访客邮箱/电话/备注)
-- 前端(客服端):
-  - ✅ 右侧栏显示所有信息(UI 已实现)
-  - ✅ 编辑弹窗支持新增/修改邮箱、电话、备注
-  - ✅ 保存后实时刷新访客详情
-  - ❌ 更丰富的表单校验/批量编辑 - **待实现**
-
-**预计工作量**:0.5 周(完成 IP->地理位置、表单校验优化等收尾工作)
-
----
-
-### 1.4. 系统消息记录 ✅ 初版完成
-**完成状态**:
-- ✅ 消息表新增 `message_type` 字段(区分 `user_message` / `system_message`)
-- ✅ 新建对话时自动记录访客事件
-  - "Visitor opened the page [页面URL]"
-  - "Visitor came from [referrer URL]"(有来源时记录)
-- ✅ 前端以灰色居中样式展示系统消息
-- ❌ 客服行为事件(如 "You joined the chat")- 后续扩展
-
-**后续迭代方向**:
-- 扩展更多系统事件(客服加入、对话状态变化等)
-- 支持系统消息筛选/折叠
-
----
-
-### 2. 对话状态管理(在线/离线)✅ 基础功能已完成,优化待实现
-**完成状态**:
-- ✅ **右侧栏状态显示**(已实现,基于对话状态)
-  - ✅ 显示在线/离线状态(绿色圆点表示在线,灰色表示离线)
-  - ✅ 基于对话状态(`status === "open"` 显示在线)
-- ✅ **实时状态更新**(基础功能已实现)
-  - ✅ 访客打开页面时自动标记为在线(WebSocket 连接建立)
-  - ✅ 访客关闭/离开页面时自动标记为离线(WebSocket 断开)
-  - ✅ 后端维护在线状态,通过 WebSocket 推送给客服端
-  - ✅ 实时同步状态到客服端
-  - ✅ 在对话列表中显示在线/离线图标(绿色圆点 = 在线)
-  - ✅ 记录最后活跃时间(`last_seen_at`)- **已实现**(在 `ConversationDetail` 中)
-
-**需求描述**(剩余部分):
-- ❌ 通过 WebSocket 心跳机制检测在线状态(定期更新 `last_seen_at`)- **待实现**
-- ❌ 根据 `last_seen_at` 判断是否在线(例如,如果 `last_seen_at` 在最近 60 秒内,则认为在线)- **待实现**
-- ❌ 在右侧栏显示"last seen"信息(如 "last seen today at 11:08")- **待实现**(已显示但为静态数据)
-- ❌ 定期轮询对话列表,更新所有对话的状态 - **待实现**
-
-**技术要点**:
-- ✅ 右侧栏状态显示(已实现,基于对话状态)
-- ✅ 使用 WebSocket 检测访客在线状态 - **已实现**(连接/断开时更新状态)
-- ❌ 心跳间隔:30 秒 - **待实现**
-- ❌ 离线判定:断开后 60 秒标记为离线 - **待实现**(目前是断开后立即标记为离线)
-- ✅ 后端维护在线状态,通过 WebSocket 推送给客服端 - **已实现**
-- ✅ 记录最后活跃时间(`last_seen_at`)- **已实现**
-- ✅ 对话列表中显示在线/离线图标(绿色圆点 = 在线)- **已实现**
-
-**已实现功能**:
-- 后端:`ConversationService.UpdateVisitorOnlineStatus` 和 `UpdateLastSeenAt` 方法
-- 后端:`Hub` 回调机制,在客户端连接/断开时调用回调函数
-- 后端:`Client` 添加 `isVisitor` 字段,区分访客和客服
-- 后端:通过 WebSocket 广播 `visitor_status_update` 事件
-- 前端:WebSocket 客户端添加 `isVisitor` 参数
-- 前端:客服端接收 `visitor_status_update` 事件,刷新对话详情
-- 前端:在对话列表中显示在线/离线图标
-
-**预计工作量**:0.5 周(完成心跳机制和离线判定优化)
-
----
-
-## 第二阶段:AI 功能(优先级:中高)
-
-### 3. AI 客服功能 ❌ 待实现
-**需求描述**:
-- 客户可以选择:人工客服 / AI 客服
-- AI 客服通过 API 调用(如 OpenAI、文心一言等)
-- 对话中可切换人工客服
-
-**技术要点**:
-- 对话表增加字段:`service_type`(`human` / `ai`)
-- 前端:选择服务类型界面
-- 后端:根据类型路由到人工或 AI 处理
-- AI 处理:调用 AI API,返回回复
-- 需要配置 AI API Key
-
-**待确认**:
-- AI 服务商选择(OpenAI、文心一言、通义千问等)
-- API Key 配置方式
-- 成本控制(是否需要限制调用次数)
-
-**预计工作量**:2-3 周
-
----
-
-## 第三阶段:完整功能(优先级:中)
-
-### 4. 文件上传和图片发送 ❌ 待实现
-**需求描述**:
-- 支持文件上传(文档、图片等)
-- 支持粘贴图片发送
-- 显示文件/图片预览
-- 文件下载功能
-
-**技术要点**:
-- 文件存储:**可扩展设计**(目前本地存储,后续可切换云存储)
-- 文件大小限制:10MB
-- 支持类型:图片(jpg, png, gif)、文档(pdf, doc, docx, txt)
-- 数据库:消息表增加 `file_url`、`file_type`、`file_name` 字段
-- 后端:文件上传接口,文件存储抽象层(便于切换云存储)
-- 前端:文件选择器、拖拽上传、粘贴图片监听
-- 图片预览:缩略图显示
-
-**预计工作量**:1 周
-
----
-
-### 5. 消息已读/未读状态 ✅ 部分完成
-**完成状态**:
-- ✅ 数据库:消息表新增 `is_read`、`read_at` 字段
-- ✅ 后端:提供批量标记已读接口 `PUT /messages/read`
-- ✅ WebSocket:推送 `messages_read` 事件,同步状态
-- ✅ 前端(客服端):聊天气泡与对话列表显示单/双对勾,系统消息灰色居中
-- ✅ 前端(访客端):聊天气泡显示已读回执
-- ✅ 自动标记逻辑:双方打开对话(滚动到底部)即触发已读
-- ❌ 未读数量统计(对话列表数字提示)- 待实现
-- ❌ 自定义已读触发条件(更精确的滚动检测)- 待优化
-
-**后续计划**:
-- 统计对话未读数量并在列表展示
-- 为客服提供“标记全部已读”操作
-- 进一步区分系统消息、客服消息的回执逻辑
-
----
-
-### 6. 事件管理页面(FAQ/知识库)❌ 待实现
-**需求描述**:
-- 事件的增删查改
-- 记录问题和答案
-- 关键词组合搜索(用 `%` 分隔关键词)
-- 搜索逻辑:包含所有关键词(AND 查询)
-- 示例:`openai%api%调用` → 搜索包含 "openai" AND "api" AND "调用" 的记录
-
-**技术要点**:
-- 数据库:事件表(`id`, `question`, `answer`, `keywords`, `created_at`, `updated_at` 等)
-- 后端:CRUD 接口 + 关键词搜索接口
-- 搜索范围:问题、答案、关键词字段
-- 匹配方式:包含所有关键词(AND)
-- 前端:事件管理页面(列表、添加、编辑、删除、搜索)
-- 搜索框:支持 `%` 分隔关键词
-
-**待确认**:
-- 事件分类:暂时不需要
-- 关联对话:是否关联到具体对话(待定)
-
-**预计工作量**:1-2 周
-
----
-
-### 7. 知识库功能(AI 结合文档)❌ 待实现
-**需求描述**:
-- 客服上传技术文档(PDF、Word、TXT、Markdown 等)
-- AI 结合知识库内容回复
-- 支持文档管理(上传、删除、查看)
-
-**技术要点**:
-- 文档存储:本地文件系统或对象存储
-- 文档处理:解析、向量化(用于检索)
-- AI 回复:检索相关文档 → 结合文档内容生成回复
-- 管理界面:文档上传、列表、删除
-- 向量化方案:本地向量库或云服务
-
-**待确认**:
-- 文档格式支持(PDF、Word、TXT、Markdown)
-- 向量化方案选择
-- 存储方式(本地存储还是云存储)
-
-**预计工作量**:2 周
-
----
-
-### 8. 用户管理功能 ❌ 待实现
-**需求描述**:
-- 管理员对客服的增删查改
-- 权限管理(角色、权限分配)
-
-**技术要点**:
-- 用户列表页面
-- CRUD 操作
-- 权限系统(基于角色的访问控制)
-- 角色:管理员、客服、只读(待定)
-- 权限粒度:查看、编辑、删除、创建用户
-
-**预计工作量**:1 周
-
----
-
-## 实施时间线
-
-### 第一阶段(2-3 周)
-- [ ] UI 布局改进(四栏布局)
-  - [x] 左侧栏:对话列表
-  - [x] 中间栏:聊天内容
-  - [x] 右侧栏:访客详情
-- [ ] 会话搜索功能
-- [ ] 客服个人资料管理(头像上传等)
-- [ ] 访客信息收集和显示
-  - [ ] 自动收集技术信息
-  - [ ] 手动添加联系信息
-  - [ ] 系统消息记录
-- [ ] 对话状态管理(在线/离线)
-
-### 第二阶段(2-3 周)
-- [ ] AI 客服功能
-
-### 第三阶段(3-5 周)
-- [ ] 文件上传和图片发送
-- [ ] 消息已读/未读状态(未读计数待实现)
-- [ ] 事件管理页面
-- [ ] 知识库功能
-- [ ] 用户管理功能
-
----
-
-## 已确认的技术决策
-
-1. **文件存储**:可扩展设计,目前本地存储,后续可切换云存储
-2. **已读判定**:客服打开对话并滑动到底部
-3. **事件搜索**:包含所有关键词(AND 查询)
-4. **事件分类**:暂时不需要
-5. **UI 布局**:四栏布局(最左侧导航、左侧对话列表、中间聊天内容、右侧访客详情)
-6. **会话搜索**:支持搜索对话内容,定位到对应消息
-7. **客服资料**:支持头像上传和个人信息修改
-8. **访客信息**:
-   - 自动收集:网站、来源、浏览器、OS、语言、IP地址、位置
-   - 手动添加:邮箱、电话、备注(客服可编辑)
-9. **系统消息**:记录访客打开页面、来源页面、客服加入等事件
-10. **头像**:访客使用默认头像(颜色区分),客服可上传头像
-

+ 0 - 982
doc/测试指南.md

@@ -1,982 +0,0 @@
-# AI-CS 智能客服系统 - 完整测试指南
-
-> 📋 本文档用于全面测试已实现的功能和需求
-> 
-> 最后更新:2025-11-12
-
-## 📑 目录
-
-- [一、准备工作](#一准备工作)
-- [二、启动后端](#二启动后端)
-- [三、启动前端](#三启动前端)
-- [四、访客端测试流程](#四访客端测试流程)
-  - [测试 1:访问聊天页面](#测试-1访问聊天页面访客端)
-  - [测试 2:发送第一条消息](#测试-2发送第一条消息)
-  - [测试 3:发送多条消息](#测试-3发送多条消息)
-  - [测试 4:接收客服消息(实时推送)](#测试-4接收客服消息实时推送)
-  - [测试 5:已读状态同步](#测试-5已读状态同步)
-  - [测试 6:刷新页面](#测试-6刷新页面)
-  - [测试 7:打开新标签页](#测试-7打开新标签页验证-localstorage-共享)
-  - [测试 8:测试错误处理](#测试-8测试错误处理)
-- [五、客服端测试流程](#五客服端测试流程)
-  - [测试 1:客服登录](#测试-1客服登录)
-  - [测试 2:客服工作台(四栏布局)](#测试-2客服工作台四栏布局)
-  - [测试 3:查看对话列表](#测试-3查看对话列表)
-  - [测试 4:选择对话](#测试-4选择对话)
-  - [测试 5:查看对话消息](#测试-5查看对话消息)
-  - [测试 6:客服发送消息](#测试-6客服发送消息)
-  - [测试 7:接收访客消息(实时推送)](#测试-7接收访客消息实时推送)
-  - [测试 8:已读状态同步](#测试-8已读状态同步)
-  - [测试 9:对话搜索功能](#测试-9对话搜索功能)
-  - [测试 10:查看访客详情](#测试-10查看访客详情)
-  - [测试 11:编辑联系信息](#测试-11编辑联系信息)
-  - [测试 12:在线/离线状态显示](#测试-12在线离线状态显示)
-  - [测试 13:刷新访客详情](#测试-13刷新访客详情)
-  - [测试 14:返回对话列表](#测试-14返回对话列表)
-  - [测试 15:退出登录](#测试-15退出登录)
-  - [测试 16:登录状态检查](#测试-16登录状态检查)
-- [六、调试技巧](#六调试技巧)
-- [七、常见问题](#七常见问题)
-- [八、完整测试检查清单](#八完整测试检查清单)
-- [九、快速测试流程(核心功能验证)](#九快速测试流程核心功能验证)
-- [十、高级测试场景](#十高级测试场景)
-  - [场景 1:多个访客同时聊天](#场景-1多个访客同时聊天)
-  - [场景 2:并发消息测试](#场景-2并发消息测试)
-  - [场景 3:长时间连接测试](#场景-3长时间连接测试)
-  - [场景 4:网络中断恢复测试](#场景-4网络中断恢复测试)
-  - [场景 5:大量消息历史测试](#场景-5大量消息历史测试)
-  - [场景 6:搜索性能测试](#场景-6搜索性能测试)
-  - [场景 7:联系信息编辑并发测试](#场景-7联系信息编辑并发测试)
-  - [场景 8:在线状态多访客测试](#场景-8在线状态多访客测试)
-- [十一、测试结果记录](#十一测试结果记录)
-
----
-
-## 一、准备工作
-
-### ⚡ 快速开始(5分钟快速验证)
-
-如果你想快速验证系统是否正常工作,可以按照以下步骤:
-
-1. **启动后端**(1分钟)
-   ```bash
-   cd backend
-   go run main.go
-   ```
-   看到 `🚀 服务器启动成功,监听 :8080` 表示启动成功
-
-2. **启动前端**(1分钟)
-   ```bash
-   cd frontend
-   npm run dev
-   ```
-   看到 `Ready` 表示启动成功
-
-3. **测试访客端**(1分钟)
-   - 访问 `http://localhost:3000/chat`
-   - 发送消息:"你好,这是测试消息"
-   - 确认消息显示在右侧(蓝色气泡)
-
-4. **测试客服端**(2分钟)
-   - 访问 `http://localhost:3000/`,使用 admin/admin123 登录
-   - 确认进入客服工作台(四栏布局)
-   - 确认对话列表显示刚才的对话
-   - 点击对话,确认消息显示在中间栏
-
-**如果以上步骤都正常,说明系统基本功能正常!** ✅
-
-详细测试请参考 [九、快速测试流程(核心功能验证)](#九快速测试流程核心功能验证)
-
----
-
-### 1. 检查数据库配置
-确保 `backend/.env` 文件存在并配置正确:
-
-```env
-DB_HOST=localhost
-DB_PORT=3306
-DB_USER=root
-DB_PASSWORD=your_password
-DB_NAME=CS
-```
-
-**注意**:
-- `DB_PASSWORD` 改成你的 MySQL 密码
-- `DB_NAME=CS` 是数据库名,如果不存在会自动创建(需要先手动创建数据库)
-
-### 2. 创建数据库(如果还没有)
-```sql
-CREATE DATABASE CS CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-```
-
-## 二、启动后端
-
-### 步骤 1:进入后端目录
-```bash
-cd backend
-```
-
-### 步骤 2:安装依赖(如果还没安装)
-```bash
-go mod tidy
-```
-
-### 步骤 3:启动后端服务
-```bash
-go run main.go
-```
-
-**成功标志**:
-- 看到类似这样的输出:
-```
-✅ 找到 .env 文件: D:\tools\AI-CS\backend\.env
-✅ .env 文件加载成功
-✅ 管理员账号已存在
-[GIN-debug] POST   /login                    --> ...
-[GIN-debug] POST   /logout                   --> ...
-[GIN-debug] POST   /conversation/init        --> ...
-[GIN-debug] GET    /conversations            --> ...
-[GIN-debug] GET    /conversations/:id        --> ...
-[GIN-debug] PUT    /conversations/:id/contact --> ...
-[GIN-debug] GET    /conversations/search     --> ...
-[GIN-debug] POST   /messages                 --> ...
-[GIN-debug] GET    /messages                 --> ...
-[GIN-debug] PUT    /messages/read            --> ...
-[GIN-debug] GET    /ws                       --> ...
-🚀 服务器启动成功,监听 :8080
-📡 WebSocket 服务已启动,路径: /ws?conversation_id=<对话ID>
-```
-
-**如果出错**:
-- 数据库连接失败:检查 `.env` 配置和 MySQL 是否启动
-- 端口被占用:检查 8080 端口是否被其他程序占用
-
-## 三、启动前端
-
-### 步骤 1:打开新的终端窗口
-**重要**:后端和前端要在不同的终端窗口运行!
-
-### 步骤 2:进入前端目录
-```bash
-cd frontend
-```
-
-### 步骤 3:安装依赖(如果还没安装)
-```bash
-npm install
-```
-
-### 步骤 4:启动前端开发服务器
-```bash
-npm run dev
-```
-
-**成功标志**:
-- 看到类似这样的输出:
-```
-  ▲ Next.js 15.5.3
-  - Local:        http://localhost:3000
-  - Ready in 2.3s
-```
-
-### 步骤 5:打开浏览器
-访问:`http://localhost:3000`
-
-## 四、测试流程
-
-### ⚠️ 重要说明
-**系统包含两个端**:
-- **访客端** (`/chat`):不需要登录,直接访问即可使用
-- **客服端** (`/` → `/agent/dashboard`):需要登录,使用管理员/客服账号
-
-**测试时你需要分别测试两个端**!
-
-**已实现的核心功能**:
-- ✅ 客服工作台(四栏布局:导航栏、对话列表、聊天内容、访客详情)
-- ✅ WebSocket 实时通信(消息实时推送、已读状态同步、在线状态更新)
-- ✅ 对话搜索功能(支持搜索消息内容、对话ID、访客ID)
-- ✅ 访客信息收集和显示(网站、来源、浏览器、操作系统、语言、IP地址)
-- ✅ 联系信息编辑(邮箱、电话、备注)
-- ✅ 在线/离线状态显示(实时更新)
-- ✅ 已读状态同步(单对勾/双对勾)
-- ✅ 系统消息显示(访客打开页面、来源信息等)
-- ✅ 关键词高亮(搜索时高亮显示)
-- ✅ 消息预览(对话列表显示最后一条消息)
-
-## 四、访客端测试流程
-
-### 测试 1:访问聊天页面(访客端)
-1. 在浏览器打开 `http://localhost:3000/chat`
-2. **预期结果**:
-   - 看到顶部标题栏(渐变背景)
-   - 显示"访客聊天"和会话ID、访客ID(自动生成)
-   - 消息列表区域显示系统消息(如 "Visitor opened the page [URL]")
-     - 系统消息居中显示(灰色背景,圆角边框)
-     - 显示当前页面URL(如果有)
-   - 如果有来源页面,显示 "Visitor came from [referrer URL]"
-     - 系统消息居中显示(灰色背景,圆角边框)
-     - 显示来源页面URL
-   - 底部有输入框和"发送"按钮
-   - **不需要登录,直接可以使用!**
-   - WebSocket 连接自动建立(查看浏览器控制台,应该看到 "✅ WebSocket 连接成功: 对话ID=X")
-   - 后端日志显示:`✅ WebSocket 连接已建立: 对话ID=X, 是访客=true`
-   - 后端日志显示:`✅ 客户端已连接: 对话ID=X, 总连接数=1, 访客连接数=1`
-
-### 测试 2:发送第一条消息
-1. 在输入框输入:"你好,这是第一条消息"
-2. 点击"发送"按钮
-3. **预期结果**:
-   - 输入框清空
-   - 消息立即出现在右侧(蓝色气泡,显示"我")
-   - 显示时间(如 "14:30")
-   - 显示已读状态(单对勾,表示已发送但未读)
-   - 页面自动滚动到底部
-   - 消息通过 WebSocket 实时推送到客服端
-
-### 测试 3:发送多条消息
-1. 连续发送 3-5 条消息
-2. **预期结果**:
-   - 每条消息都显示在右侧
-   - 消息按时间顺序排列
-   - 每次发送后自动滚动到底部
-   - 每条消息显示已读状态(单对勾)
-
-### 测试 4:接收客服消息(实时推送)
-1. **打开两个浏览器标签页**:
-   - 标签页1:访客端 (`http://localhost:3000/chat`)
-   - 标签页2:客服端 (`http://localhost:3000/agent/dashboard`),需要先登录
-2. **在客服端发送消息**:"您好,我是客服,有什么可以帮您的吗?"
-3. **预期结果**(访客端):
-   - **无需刷新页面**,消息自动出现在左侧(白色气泡)
-   - 消息显示时间
-   - 自动滚动到底部(如果当前在底部)
-   - 显示已读状态(单对勾,表示客服已读)
-   - 这是通过 WebSocket 实时推送的!
-
-### 测试 5:已读状态同步
-1. **在客服端查看访客消息**(客服端会自动标记已读)
-2. **预期结果**(访客端):
-   - **无需刷新页面**,已读状态从单对勾变为双对勾
-   - 对勾颜色变为蓝色(表示已读)
-   - 这是通过 WebSocket 实时推送 `messages_read` 事件实现的!
-
-### 测试 6:刷新页面
-1. 按 `F5` 刷新页面
-2. **预期结果**:
-   - 之前发送的消息还在(因为存到数据库了)
-   - 自动拉取历史消息
-   - 访客ID不变(因为存在 localStorage)
-   - 系统消息显示(如 "Visitor opened the page")
-   - WebSocket 连接重新建立
-
-### 测试 7:打开新标签页(验证 localStorage 共享)
-1. 打开新的浏览器标签页,访问 `http://localhost:3000/chat`
-2. **预期结果**:
-   - **使用的是同一个访客ID**(因为 localStorage 在同一浏览器不同标签页之间共享)
-   - **显示同一个对话**(因为 visitor_id 相同)
-   - **能看到之前发送的消息**(因为是同一个对话)
-   - 系统消息显示(如 "Visitor opened the page")
-
-**注意**:
-- 同一浏览器不同标签页 → 共享 localStorage → 同一个访客ID → 同一个对话
-- 如果要测试新访客,需要:
-  - 使用不同的浏览器(Chrome、Edge、Firefox等)
-  - 或者清除浏览器缓存/localStorage
-  - 或者使用隐私/无痕模式
-
-### 测试 8:测试错误处理
-1. **停止后端**(在运行 `go run main.go` 的终端按 `Ctrl+C`)
-2. 尝试发送消息
-3. **预期结果**:
-   - 弹出错误提示:"发送消息失败,请稍后重试"
-   - 按钮恢复正常(不再是"发送中...")
-   - WebSocket 连接断开(查看浏览器控制台,应该看到 "❌ WebSocket 连接关闭")
-
----
-
-## 五、客服端测试流程
-
-### 测试 1:客服登录
-1. 访问 `http://localhost:3000/`(根路径)
-2. **预期结果**:
-   - 看到客服登录页面
-   - 有用户名和密码输入框
-   - 有"登录"按钮
-   - 显示默认管理员账号提示(用户名: admin,密码: admin123)
-
-3. **输入登录信息**:
-   - 用户名:`admin`
-   - 密码:`admin123`
-4. 点击"登录"按钮
-5. **预期结果**:
-   - 登录成功,自动跳转到 `/agent/dashboard`(客服工作台)
-   - 浏览器 localStorage 中保存了 `agent_user_id`、`agent_username`、`agent_role`
-
-### 测试 2:客服工作台(四栏布局)
-1. 登录成功后,自动进入客服工作台
-2. **预期结果**:
-   - **最左侧导航栏**(固定宽度 64px):显示导航图标
-   - **左侧对话列表栏**(固定宽度 320px):显示所有对话列表
-   - **中间聊天内容栏**(自适应宽度):显示当前选中对话的消息
-   - **右侧访客详情栏**(固定宽度 320px):显示当前选中对话的访客详情
-   - 顶部显示当前登录的用户名和角色(如:"admin (管理员)")
-   - 显示"退出"按钮
-
-### 测试 3:查看对话列表
-1. 在工作台左侧查看对话列表
-2. **预期结果**:
-   - 显示所有未关闭的对话列表(如果有访客发送过消息)
-   - 每条对话显示:
-     - 对话头像(圆形,基于 visitor_id 生成颜色)
-     - 对话ID、访客ID
-     - 在线/离线状态图标(绿色圆点 = 在线)
-     - 状态标签("进行中"或"已关闭")
-     - 最后一条消息预览(显示最后一条消息的前50个字符)
-     - 未读消息数量(蓝色徽标,如果有未读消息)
-     - 已读/未读状态图标(单/双对勾)
-     - 最后更新时间(智能格式化:今天显示时间,更早显示日期+时间)
-   - 对话按更新时间倒序排列(最新的在前)
-   - 选中对话高亮显示(蓝色背景)
-
-3. **如果没有对话**:
-   - 需要先访问 `/chat` 页面(访客端)发送一些消息
-   - 然后刷新客服端,就能看到对话了
-
-### 测试 4:选择对话
-1. 在对话列表中,点击任意一条对话
-2. **预期结果**:
-   - 对话高亮显示(蓝色背景)
-   - **中间栏**自动加载该对话的消息
-   - **右侧栏**自动加载该对话的访客详情
-   - WebSocket 连接自动切换到该对话
-   - 无需跳转页面,所有内容在同一页面更新
-
-### 测试 5:查看对话消息
-1. 选择一条对话后,查看中间栏的消息列表
-2. **预期结果**:
-   - 显示该对话的所有消息(包括系统消息)
-   - **访客消息在左侧**(白色气泡)
-   - **客服消息在右侧**(蓝色气泡)
-   - 系统消息居中显示(灰色背景,如 "Visitor opened the page [URL]")
-   - 每条消息显示时间(智能格式化)
-   - 客服消息显示已读/未读状态(单/双对勾)
-   - 自动滚动到底部(如果是新对话)
-   - 消息按时间顺序排列
-
-### 测试 6:客服发送消息
-1. 在中间栏输入框输入消息:"您好,我是客服,有什么可以帮您的吗?"
-2. 点击"发送"按钮
-3. **预期结果**:
-   - 输入框清空
-   - 消息立即出现在右侧(蓝色气泡,客服消息)
-   - 显示时间
-   - 显示已读状态(单对勾,表示已发送但未读)
-   - 页面自动滚动到底部
-   - 消息通过 WebSocket 实时推送到访客端
-   - 对话列表中的最后一条消息预览自动更新
-
-### 测试 7:接收访客消息(实时推送)
-1. **打开两个浏览器标签页**:
-   - 标签页1:访客端 (`http://localhost:3000/chat`)
-   - 标签页2:客服端 (`http://localhost:3000/agent/dashboard`),需要先登录
-2. **在访客端发送消息**:"你好,我需要帮助"
-3. **预期结果**(客服端):
-   - **无需刷新页面**,消息自动出现在左侧(白色气泡)
-   - 消息显示时间
-   - 自动滚动到底部(如果当前在底部)
-   - 对话列表中的最后一条消息预览自动更新
-   - 未读消息数量自动更新(蓝色徽标)
-   - 这是通过 WebSocket 实时推送的!
-
-### 测试 8:已读状态同步
-1. **在客服端查看访客消息**(客服端会自动标记已读)
-2. **预期结果**(访客端):
-   - **无需刷新页面**,已读状态从单对勾变为双对勾
-   - 对勾颜色变为蓝色(表示已读)
-   - 这是通过 WebSocket 实时推送 `messages_read` 事件实现的!
-3. **预期结果**(客服端):
-   - 未读消息数量自动减少
-   - 已读状态图标更新(双对勾)
-
-### 测试 9:对话搜索功能
-1. 在左侧对话列表顶部,找到搜索框(显示 "Q Search" 或搜索图标)
-2. **输入搜索关键词**(例如:对话ID、访客ID、消息内容)
-   - 例如:搜索 "你好"(如果消息中包含"你好")
-   - 例如:搜索对话ID "10"(如果对话ID为10)
-   - 例如:搜索访客ID "176"(如果访客ID为176)
-3. **预期结果**:
-   - 实时搜索(300ms 防抖,输入后等待 300ms 才开始搜索)
-   - 显示匹配的对话列表(只显示包含关键词的对话)
-   - 搜索结果高亮显示(关键词黄色背景高亮,使用 `<mark>` 标签)
-   - 点击搜索结果,自动定位到匹配消息
-     - 如果匹配消息在历史记录中,自动滚动到匹配消息并居中显示
-     - 如果匹配消息是最后一条,自动滚动到底部
-     - 高亮显示持续 3 秒后自动清除
-   - 清除搜索后,显示所有对话
-   - 搜索框为空时,显示所有对话
-
-### 测试 10:查看访客详情
-1. 选择一条对话后,查看右侧栏的访客详情
-2. **预期结果**:
-   - 显示访客头像(圆形,基于 visitor_id 生成颜色)
-   - 显示访客ID(例如:"访客 #176")
-   - 显示在线/离线状态(绿色圆点 = 在线,灰色 = 离线)
-   - 显示刷新按钮(圆形箭头图标)和更多选项图标(三个点)
-   - **联系信息区域**:
-     - 邮箱(可以编辑,显示"+ Add"或"编辑"按钮)
-     - 电话(可以编辑,显示"+ Add"或"编辑"按钮)
-     - 备注(可以编辑,显示"+ Add"或"编辑"按钮)
-   - **技术信息区域**:
-     - 网站(当前页面URL,如果有,显示为链接)
-     - 来源(referrer,从哪个页面跳转过来,如果有,显示为链接)
-     - 语言(浏览器语言设置,例如:"zh-CN")
-     - 浏览器(浏览器信息,例如:"Edge"、"Chrome")
-     - 操作系统(操作系统信息,例如:"Windows"、"macOS")
-     - IP地址(后端获取,例如:"127.0.0.1")
-     - 位置(暂未实现,显示"暂未收集")
-     - 最后活跃时间(智能格式化,例如:"刚刚"、"5分钟前"、"今天 14:30")
-
-### 测试 11:编辑联系信息
-1. 在右侧栏访客详情中,找到"联系信息"区域
-2. **编辑邮箱**:
-   - 点击"邮箱"右侧的"+ Add"或"编辑"按钮
-   - 弹出编辑弹窗(输入框 + "保存"和"取消"按钮)
-   - 输入邮箱地址(例如:`visitor@example.com`)
-   - 点击"保存"按钮
-   - **预期结果**:
-     - 编辑弹窗关闭
-     - 邮箱立即更新显示
-     - 数据保存到后端(查看后端日志,应该看到 `PUT /conversations/:id/contact` 请求)
-     - 刷新后数据还在
-     - 如果保存失败,显示错误提示
-3. **编辑电话**:
-   - 点击"电话"右侧的"+ Add"或"编辑"按钮
-   - 弹出编辑弹窗
-   - 输入电话号码(例如:`13800138000`)
-   - 点击"保存"按钮
-   - **预期结果**:
-     - 编辑弹窗关闭
-     - 电话立即更新显示
-     - 数据保存到后端
-     - 刷新后数据还在
-4. **编辑备注**:
-   - 点击"备注"右侧的"+ Add"或"编辑"按钮
-   - 弹出编辑弹窗(支持多行文本)
-   - 输入备注内容(例如:`重要客户,需要重点关注`)
-   - 点击"保存"按钮
-   - **预期结果**:
-     - 编辑弹窗关闭
-     - 备注立即更新显示(支持换行显示)
-     - 数据保存到后端
-     - 刷新后数据还在
-5. **取消编辑**:
-   - 点击编辑按钮,弹出编辑弹窗
-   - 修改内容(或不修改)
-   - 点击"取消"按钮
-   - **预期结果**:
-     - 编辑弹窗关闭
-     - 内容不更新(保持原值)
-6. **清空联系信息**:
-   - 点击编辑按钮,弹出编辑弹窗
-   - 清空输入框内容
-   - 点击"保存"按钮
-   - **预期结果**:
-     - 编辑弹窗关闭
-     - 联系信息清空,显示"暂未填写"
-     - 数据保存到后端(空值)
-     - 刷新后数据还在(显示"暂未填写")
-
-### 测试 12:在线/离线状态显示
-1. **访客在线时**:
-   - 在访客端打开 `/chat` 页面
-   - **预期结果**(客服端):
-     - 对话列表中显示绿色圆点(在线)
-     - 右侧栏显示"● 在线"
-     - 这是通过 WebSocket 实时更新的!
-2. **访客离线时**:
-   - 关闭访客端页面(或关闭浏览器标签页)
-   - **预期结果**(客服端):
-     - 对话列表中绿色圆点消失(或变为灰色)
-     - 右侧栏显示"● 离线"
-     - 最后活跃时间更新
-     - 这是通过 WebSocket 实时更新的!
-
-### 测试 13:刷新访客详情
-1. 在右侧栏访客详情顶部,找到刷新按钮(圆形箭头图标)
-2. 点击刷新按钮
-3. **预期结果**:
-   - 访客详情重新加载
-   - 显示最新的访客信息(包括联系信息、技术信息、最后活跃时间等)
-
-### 测试 14:返回对话列表
-1. 在工作台左侧对话列表中,点击其他对话
-2. **预期结果**:
-   - 对话切换,中间栏和右侧栏自动更新
-   - WebSocket 连接自动切换
-   - 无需跳转页面,所有内容在同一页面更新
-
-### 测试 15:退出登录
-1. 在工作台顶部,找到"退出"按钮
-2. 点击"退出"按钮
-3. **预期结果**:
-   - localStorage 中的 `agent_user_id`、`agent_username`、`agent_role` 被清除
-   - 自动跳转到登录页面 (`/`)
-
-4. **验证登录状态**:
-   - 尝试直接访问 `/agent/dashboard`
-   - **预期结果**:自动跳转到登录页面(因为未登录)
-
-### 测试 16:登录状态检查
-1. **清除 localStorage**(在浏览器控制台执行):
-   ```javascript
-   localStorage.clear()
-   ```
-2. 尝试直接访问 `/agent/dashboard`
-3. **预期结果**:
-   - 自动跳转到登录页面(因为未登录)
-
----
-
-## 六、调试技巧
-
-### 1. 打开浏览器开发者工具
-按 `F12` 打开开发者工具
-
-### 2. 查看控制台(Console)
-- 如果有红色错误,说明代码有问题
-- 如果有 `console.error` 输出,说明后端请求失败
-- 查看 WebSocket 连接状态:
-  - `✅ WebSocket 连接成功: 对话ID=X` → 连接成功
-  - `❌ WebSocket 连接关闭: 对话ID=X` → 连接断开
-  - `WebSocket 连接错误: {}` → 连接失败(可能是后端未启动)
-
-### 3. 查看网络请求(Network)
-- 点击"发送"后,查看是否有 `POST /messages` 请求
-- 查看 WebSocket 连接:找到 `ws` 或 `wss` 类型的请求
-- 查看请求状态:
-  - `200` = 成功
-  - `400` = 请求参数错误
-  - `500` = 服务器错误
-- 查看 WebSocket 消息:
-  - 点击 WebSocket 连接,查看 "Messages" 标签
-  - 应该看到 `new_message`、`messages_read`、`visitor_status_update` 等事件
-
-### 4. 查看后端日志
-在后端运行的终端窗口,查看日志输出:
-- 如果看到 `✅ 客户端已连接: 对话ID=X, 总连接数=Y, 访客连接数=Z`,说明 WebSocket 连接成功
-- 如果看到 `❌ 客户端已断开: 对话ID=X, 剩余访客连接数=Y`,说明 WebSocket 断开
-- 如果看到 `✅ WebSocket 连接已建立: 对话ID=X, 是访客=true/false`,说明连接建立
-- 如果看到 `更新访客在线状态失败: ...`,说明状态更新失败
-- 如果有错误信息,说明后端处理有问题
-
-## 七、常见问题
-
-### 问题 1:后端启动失败
-**错误**:`数据库连接失败`
-**解决**:
-- 检查 MySQL 是否启动
-- 检查 `.env` 配置是否正确
-- 检查数据库是否存在
-
-### 问题 2:前端启动失败
-**错误**:`npm install` 失败
-**解决**:
-- 检查 Node.js 版本(需要 18+)
-- 删除 `node_modules` 文件夹,重新 `npm install`
-
-### 问题 3:页面空白
-**错误**:浏览器显示空白页
-**解决**:
-- 打开开发者工具(F12)查看错误
-- 检查后端是否启动
-- 检查前端是否启动在 3000 端口
-
-### 问题 4:发送消息失败
-**错误**:点击发送没反应
-**解决**:
-- 打开浏览器控制台(F12)查看错误
-- 检查后端是否启动在 8080 端口
-- 检查 `frontend/lib/config.ts` 中的 API 地址是否正确
-
-### 问题 5:WebSocket 连接失败
-**错误**:`WebSocket 连接错误: {}`
-**解决**:
-- 检查后端是否启动
-- 检查后端是否监听 8080 端口
-- 检查浏览器控制台错误信息
-- 查看后端日志,确认 WebSocket 服务是否启动
-
-### 问题 6:消息不实时推送
-**错误**:发送消息后,对方看不到
-**解决**:
-- 检查 WebSocket 连接状态(查看浏览器控制台)
-- 检查后端日志,确认 WebSocket 是否正常广播
-- 检查后端是否正常处理 WebSocket 消息
-- 确认双方都建立了 WebSocket 连接
-
-### 问题 7:已读状态不同步
-**错误**:已读状态不更新
-**解决**:
-- 检查 WebSocket 连接状态
-- 检查后端是否正常推送 `messages_read` 事件
-- 检查前端是否正常处理 `messages_read` 事件
-- 查看浏览器控制台和后端日志
-
-### 问题 8:在线状态不更新
-**错误**:在线状态不实时更新
-**解决**:
-- 检查 WebSocket 连接状态
-- 检查后端是否正常推送 `visitor_status_update` 事件
-- 检查前端是否正常处理 `visitor_status_update` 事件
-- 查看浏览器控制台和后端日志
-- 确认访客端 WebSocket 连接是否正常(`is_visitor=true`)
-
-### 问题 9:对话列表不显示
-**错误**:对话列表为空
-**解决**:
-- 确认是否有访客发送过消息
-- 检查后端是否正常返回对话列表
-- 查看浏览器控制台错误信息
-- 检查后端日志,确认数据库查询是否正常
-
-### 问题 10:搜索功能不工作
-**错误**:搜索无结果
-**解决**:
-- 检查搜索关键词是否正确
-- 检查后端搜索接口是否正常
-- 查看浏览器控制台错误信息
-- 检查后端日志,确认搜索查询是否正常
-
-## 八、完整测试检查清单
-
-完成以下测试后,打勾:
-
-### 一、基础功能测试
-
-#### 访客端基础功能:
-- [ ] 后端启动成功(显示监听 :8080 和 WebSocket 服务)
-- [ ] 前端启动成功(显示 Ready)
-- [ ] 访问 `/chat` 页面正常显示
-- [ ] 自动生成/获取访客ID
-- [ ] 自动初始化对话(获取对话ID)
-- [ ] 显示系统消息(如 "Visitor opened the page [URL]")
-- [ ] WebSocket 连接自动建立(查看浏览器控制台)
-- [ ] 发送第一条消息成功
-- [ ] 消息显示在右侧(蓝色气泡)
-- [ ] 显示已读状态(单对勾)
-- [ ] 发送后自动滚动到底部
-- [ ] 输入框清空,可以继续输入
-- [ ] 时间显示正确(今天的只显示时间,更早的显示日期+时间)
-- [ ] 可以连续发送多条消息
-- [ ] 输入框为空时,发送按钮禁用
-- [ ] 刷新页面后消息还在
-- [ ] 访客信息自动收集(网站、来源、浏览器、操作系统、语言、IP地址)
-
-#### 客服端基础功能:
-- [ ] 访问 `/` 显示登录页面
-- [ ] 使用 admin/admin123 可以成功登录
-- [ ] 登录后跳转到 `/agent/dashboard`(客服工作台)
-- [ ] 四栏布局正常显示(导航栏、对话列表、聊天内容、访客详情)
-- [ ] 对话列表显示所有未关闭的对话
-- [ ] 对话列表显示在线/离线状态图标(绿色圆点 = 在线)
-- [ ] 对话列表显示最后一条消息预览
-- [ ] 对话列表显示未读消息数量(蓝色徽标)
-- [ ] 对话列表显示已读/未读状态图标(单/双对勾)
-- [ ] 选择对话后,中间栏和右侧栏自动更新
-- [ ] WebSocket 连接自动切换
-- [ ] 客服消息显示在右侧(蓝色气泡)
-- [ ] 访客消息显示在左侧(白色气泡)
-- [ ] 系统消息居中显示(灰色背景)
-- [ ] 客服可以发送消息
-- [ ] 退出登录功能正常
-- [ ] 未登录访问客服页面会跳转到登录页
-
-### 二、实时通信测试
-
-#### WebSocket 实时推送:
-- [ ] 访客发送消息后,客服端实时收到(无需刷新)
-- [ ] 客服发送消息后,访客端实时收到(无需刷新)
-- [ ] 消息通过 WebSocket 实时推送
-- [ ] WebSocket 连接自动重连(断开后自动重连)
-
-#### 已读状态同步:
-- [ ] 客服查看访客消息后,访客端实时更新已读状态(单对勾 → 双对勾)
-- [ ] 访客查看客服消息后,客服端实时更新已读状态(单对勾 → 双对勾)
-- [ ] 已读状态通过 WebSocket 实时推送
-- [ ] 对勾颜色正确(已读 = 蓝色,未读 = 灰色)
-
-#### 在线状态更新:
-- [ ] 访客打开页面后,客服端实时显示在线状态(绿色圆点)
-- [ ] 访客关闭页面后,客服端实时显示离线状态(灰色圆点或消失)
-- [ ] 在线状态通过 WebSocket 实时推送
-- [ ] 最后活跃时间实时更新
-
-### 三、搜索功能测试
-
-#### 对话搜索:
-- [ ] 搜索框UI正常显示
-- [ ] 输入搜索关键词后,实时搜索(300ms 防抖)
-- [ ] 搜索结果正确显示(匹配的对话列表)
-- [ ] 搜索结果高亮显示(关键词黄色背景高亮)
-- [ ] 点击搜索结果,自动定位到匹配消息
-- [ ] 历史消息居中显示,最后一条滚动到底部
-- [ ] 清除搜索后,显示所有对话
-- [ ] 支持搜索消息内容、对话ID、访客ID
-
-### 四、访客信息测试
-
-#### 访客信息收集:
-- [ ] 访客端自动收集网站(当前页面URL)
-- [ ] 访客端自动收集来源(referrer)
-- [ ] 访客端自动收集浏览器信息
-- [ ] 访客端自动收集操作系统信息
-- [ ] 访客端自动收集语言信息
-- [ ] 后端自动获取IP地址
-- [ ] 客服端显示所有访客信息
-
-#### 联系信息编辑:
-- [ ] 编辑邮箱功能正常(新增、修改、清空)
-- [ ] 编辑电话功能正常(新增、修改、清空)
-- [ ] 编辑备注功能正常(新增、修改、清空)
-- [ ] 联系信息保存到后端
-- [ ] 刷新后联系信息还在
-- [ ] 编辑弹窗正常显示和关闭
-
-### 五、界面交互测试
-
-#### 滚动行为:
-- [ ] 消息列表内部滚动正常(不会滚动整个页面)
-- [ ] 对话列表内部滚动正常(不会滚动整个页面)
-- [ ] 访客详情内部滚动正常(不会滚动整个页面)
-- [ ] 发送新消息后自动滚动到底部
-- [ ] 长消息历史正常滚动
-
-#### 响应式布局:
-- [ ] 四栏布局正常显示
-- [ ] 各栏宽度正确(导航栏 64px,对话列表 320px,访客详情 320px,聊天内容自适应)
-- [ ] 各栏高度正确(顶部栏 h-16,内容区域自适应)
-- [ ] 选中对话高亮显示(蓝色背景)
-
-### 六、错误处理测试
-
-#### 网络错误:
-- [ ] 后端停止后,发送消息显示错误提示
-- [ ] WebSocket 连接断开后,显示连接关闭提示
-- [ ] 网络恢复后,WebSocket 自动重连
-- [ ] 错误提示清晰明确
-
-#### 数据验证:
-- [ ] 输入框为空时,发送按钮禁用
-- [ ] 输入框有内容时,发送按钮启用
-- [ ] 编辑联系信息时,输入验证正常
-- [ ] 清空联系信息时,数据正确保存
-
-### 七、性能测试
-
-#### 加载性能:
-- [ ] 页面加载速度正常(< 2秒)
-- [ ] 对话列表加载速度正常(< 1秒)
-- [ ] 消息列表加载速度正常(< 1秒)
-- [ ] 访客详情加载速度正常(< 1秒)
-
-#### 实时性能:
-- [ ] 消息实时推送延迟 < 100ms
-- [ ] 已读状态同步延迟 < 100ms
-- [ ] 在线状态更新延迟 < 100ms
-- [ ] WebSocket 连接稳定(不断开)
-
-### 八、兼容性测试
-
-#### 浏览器兼容:
-- [ ] Chrome 浏览器正常
-- [ ] Edge 浏览器正常
-- [ ] Firefox 浏览器正常
-- [ ] Safari 浏览器正常(如果可用)
-
-#### 数据持久化:
-- [ ] 刷新页面后,消息还在
-- [ ] 刷新页面后,访客ID不变
-- [ ] 刷新页面后,登录状态保持
-- [ ] 联系信息编辑后,刷新后数据还在
-
----
-
-## 九、快速测试流程(核心功能验证)
-
-> 💡 如果你时间有限,可以按照以下流程快速验证核心功能是否正常
-
-### 快速测试步骤:
-
-1. **启动系统**(5分钟)
-   - [ ] 启动后端:`cd backend && go run main.go`
-   - [ ] 启动前端:`cd frontend && npm run dev`
-   - [ ] 访问 `http://localhost:3000`
-
-2. **访客端测试**(5分钟)
-   - [ ] 访问 `http://localhost:3000/chat`
-   - [ ] 发送消息:"你好,这是测试消息"
-   - [ ] 确认消息显示在右侧(蓝色气泡)
-   - [ ] 确认 WebSocket 连接建立(查看浏览器控制台)
-
-3. **客服端测试**(5分钟)
-   - [ ] 访问 `http://localhost:3000/`,使用 admin/admin123 登录
-   - [ ] 确认进入客服工作台(四栏布局)
-   - [ ] 确认对话列表显示刚才的对话
-   - [ ] 点击对话,确认消息显示在中间栏
-   - [ ] 发送消息:"您好,我是客服"
-   - [ ] 确认消息显示在右侧(蓝色气泡)
-
-4. **实时通信测试**(5分钟)
-   - [ ] 打开两个浏览器标签页(访客端和客服端)
-   - [ ] 在访客端发送消息:"测试实时推送"
-   - [ ] 确认客服端实时收到消息(无需刷新)
-   - [ ] 在客服端发送消息:"测试实时推送回复"
-   - [ ] 确认访客端实时收到消息(无需刷新)
-
-5. **已读状态测试**(3分钟)
-   - [ ] 在客服端查看访客消息
-   - [ ] 确认访客端已读状态从单对勾变为双对勾(无需刷新)
-
-6. **在线状态测试**(3分钟)
-   - [ ] 确认客服端对话列表显示绿色圆点(在线)
-   - [ ] 关闭访客端页面
-   - [ ] 确认客服端绿色圆点消失(或变为灰色)
-
-7. **搜索功能测试**(2分钟)
-   - [ ] 在客服端搜索框输入关键词
-   - [ ] 确认搜索结果高亮显示
-   - [ ] 点击搜索结果,确认自动定位到匹配消息
-
-8. **联系信息编辑测试**(3分钟)
-   - [ ] 在客服端右侧栏,点击"邮箱"右侧的"编辑"按钮
-   - [ ] 输入邮箱地址,点击"保存"
-   - [ ] 确认邮箱立即更新显示
-
-**如果以上测试都通过,说明核心功能正常!** ✅
-
----
-
-## 十、高级测试场景
-
-### 场景 1:多个访客同时聊天
-1. **打开多个浏览器**(或使用隐私/无痕模式)
-   - 浏览器1:访客A (`http://localhost:3000/chat`)
-   - 浏览器2:访客B (`http://localhost:3000/chat`)
-   - 浏览器3:客服端 (`http://localhost:3000/agent/dashboard`)
-2. **测试流程**:
-   - 访客A 发送消息:"我是访客A"
-   - 访客B 发送消息:"我是访客B"
-   - 客服端应该看到两个不同的对话
-   - 客服可以选择不同的对话进行回复
-   - 每个访客只能看到自己的对话
-
-### 场景 2:并发消息测试
-1. **打开两个浏览器标签页**(访客端和客服端)
-2. **快速发送多条消息**:
-   - 访客端连续发送 10 条消息
-   - 客服端连续发送 10 条消息
-3. **预期结果**:
-   - 所有消息都实时推送
-   - 消息顺序正确
-   - 已读状态正确同步
-   - 无消息丢失
-   - 无重复消息
-
-### 场景 3:长时间连接测试
-1. **打开访客端和客服端**
-2. **保持连接 30 分钟**
-3. **预期结果**:
-   - WebSocket 连接保持稳定(不断开)
-   - 消息正常推送
-   - 已读状态正常同步
-   - 在线状态正常更新
-   - 无内存泄漏
-
-### 场景 4:网络中断恢复测试
-1. **打开访客端和客服端**
-2. **断开网络**(关闭 WiFi 或拔掉网线)
-3. **等待 10 秒**
-4. **恢复网络**
-5. **预期结果**:
-   - WebSocket 连接自动重连
-   - 消息正常推送
-   - 已读状态正常同步
-   - 在线状态正常更新
-
-### 场景 5:大量消息历史测试
-1. **创建一个对话**
-2. **发送 100+ 条消息**
-3. **预期结果**:
-   - 消息列表正常加载
-   - 滚动性能正常(不卡顿)
-   - 消息显示正确
-   - 搜索功能正常(可以搜索历史消息)
-   - 关键词高亮正常
-
-### 场景 6:搜索性能测试
-1. **创建多个对话**(10+ 个对话)
-2. **每个对话发送多条消息**(10+ 条消息)
-3. **测试搜索功能**:
-   - 搜索消息内容
-   - 搜索对话ID
-   - 搜索访客ID
-4. **预期结果**:
-   - 搜索速度正常(< 500ms)
-   - 搜索结果正确
-   - 关键词高亮正常
-   - 点击搜索结果自动定位正常
-
-### 场景 7:联系信息编辑并发测试
-1. **打开多个客服端**(使用不同的浏览器)
-2. **同时编辑同一个访客的联系信息**:
-   - 客服A 编辑邮箱
-   - 客服B 编辑电话
-3. **预期结果**:
-   - 数据正确保存
-   - 无数据冲突
-   - 刷新后数据正确
-
-### 场景 8:在线状态多访客测试
-1. **打开多个访客端**(3+ 个访客)
-2. **测试在线状态**:
-   - 访客A 在线
-   - 访客B 在线
-   - 访客C 离线
-3. **预期结果**(客服端):
-   - 对话列表正确显示在线/离线状态
-   - 在线状态实时更新
-   - 最后活跃时间正确更新
-
----
-
-## 十一、测试结果记录
-
-### 测试日期:__________
-
-### 测试人员:__________
-
-### 测试环境:
-- 后端版本:__________
-- 前端版本:__________
-- 数据库版本:__________
-- 浏览器版本:__________
-
-### 测试结果:
-- [ ] 所有测试通过
-- [ ] 部分测试通过(请列出未通过的测试)
-- [ ] 测试失败(请列出失败的测试)
-
-### 问题记录:
-1. __________
-2. __________
-3. __________
-
-### 备注:
-__________
-
----
-
-**测试完成后,你就知道整个系统是否正常工作了!** 🎉
-
-**如果所有测试都通过,恭喜你!系统已经可以正常使用了!** ✅
-

+ 0 - 130
doc/系统角色说明.md

@@ -1,130 +0,0 @@
-# 系统角色说明
-
-## 当前系统状态
-
-### ✅ 已实现:访客端(`/chat` 页面)
-
-**访客的特点**:
-- 不需要登录注册
-- 访问 `/chat` 页面时自动生成访客ID
-- 访客ID存储在浏览器的 localStorage 中
-- 发送消息时,`sender_is_agent: false`, `sender_id: 0`
-- **可以直接测试,无需任何配置!**
-
-**访客能做什么**:
-- ✅ 自动创建对话
-- ✅ 发送消息
-- ✅ 查看历史消息
-- ✅ 刷新页面后消息还在
-
-**测试访客功能**:
-```bash
-# 1. 启动后端和前端
-# 2. 浏览器打开 http://localhost:3000/chat
-# 3. 直接发送消息测试,无需登录!
-```
-
----
-
-### ❌ 未实现:客服端(需要单独开发)
-
-**客服的特点**(未来功能):
-- 需要登录(使用 `/register` 和 `/login` 接口)
-- 登录后获取客服ID
-- 发送消息时,`sender_is_agent: true`, `sender_id: 客服ID`
-- 需要创建一个新的页面,比如 `/agent/chat`
-
-**客服需要能做什么**(未来功能):
-- 登录系统
-- 查看所有对话列表
-- 选择某个对话进行回复
-- 发送消息时标识为客服
-
-**后端已支持**:
-- ✅ 后端代码已经支持客服发送消息(`sender_is_agent: true`)
-- ✅ 后端会检查:如果是客服,`sender_id` 必须提供且不能为0
-
----
-
-## 代码逻辑说明
-
-### 后端检查逻辑(`backend/service/service.go`)
-
-```go
-// 第122-125行
-if req.SenderIsAgent && req.SenderID == 0 {
-    c.JSON(http.StatusBadRequest, gin.H{"error": "客服id不能为空"})
-    return
-}
-```
-
-**含义**:
-- 如果 `sender_is_agent == true`(是客服),那么 `sender_id` 不能为0
-- 如果 `sender_is_agent == false`(是访客),`sender_id` 可以为0
-
-**所以访客可以直接发送消息,不需要ID!**
-
-### 前端发送逻辑(`frontend/app/chat/page.tsx`)
-
-```typescript
-// 第187-188行
-sender_is_agent: false,  // false = 访客发的
-sender_id: 0,            // 访客ID(0表示不需要)
-```
-
-**这是访客端页面,所以固定为 `false` 和 `0`**
-
----
-
-## 未来开发计划
-
-### 客服端页面开发(待实现)
-
-需要创建:
-1. **客服登录页面**:`/agent/login`
-   - 使用现有的 `/login` 接口
-   - 登录后保存客服ID和token
-
-2. **客服对话列表页面**:`/agent/conversations`
-   - 显示所有未关闭的对话
-   - 显示每个对话的访客ID、最后消息时间等
-
-3. **客服聊天页面**:`/agent/chat/[conversationId]`
-   - 类似访客聊天页面
-   - 发送消息时:`sender_is_agent: true`, `sender_id: 客服ID`
-   - 显示对话双方的消息(访客在左,客服在右)
-
----
-
-## 测试建议
-
-### 当前可以测试的(访客端)
-1. ✅ 访问 `/chat` 页面
-2. ✅ 发送消息(以访客身份)
-3. ✅ 查看历史消息
-4. ✅ 刷新页面后消息还在
-
-### 暂时无法测试的(客服端)
-1. ❌ 客服登录(页面还没创建)
-2. ❌ 客服查看对话列表(页面还没创建)
-3. ❌ 客服回复消息(页面还没创建)
-
-**但你可以用两个浏览器窗口测试**:
-- 窗口1:访客A,发送消息
-- 窗口2:访客B,查看对话(看到访客A的消息)
-
----
-
-## 总结
-
-**当前状态**:
-- ✅ 访客端完全可用,可以直接测试
-- ❌ 客服端还未开发,需要单独实现
-
-**测试时**:
-- 你就是在模拟访客角色
-- 不需要任何登录或配置
-- 直接访问 `/chat` 就可以发送消息!
-
-**后端已经准备好了**,支持访客和客服两种角色,只是前端客服页面还没做而已。
-