Files
AI-CS/doc/前端学习笔记.md
T
2025-11-17 18:05:00 +08:00

15 KiB
Raw Blame History

前端聊天页面 - 学习笔记(通俗版)

一、核心概念理解

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.tsmessageApi.tsauthApi.ts
    • 新增 updateConversationContact 方法,调用 PUT /conversations/:id/contact 更新访客联系信息
    • 后续接入 React Query / SWR 时,只需要在这里改
  • 工具层(utils/

    • format.ts:统一时间和消息预览格式
    • highlight.tsx:关键词高亮组件化
    • storage.tslocalStorage 读写统一封装

小结:页面调用 HookHook 使用 ServiceService 请求后端;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:负责连接、自动重连、消息广播

访客与客服共用同一套消息组件/服务:通过 currentUserIsAgentsenderIsAgent 等参数切换左右气泡与已读对勾。

五、为什么这样写?

为什么用 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. 多看报错:出错时看报错信息,学会查问题

记住:编程就像学开车,一开始慢,多练习就熟了!🚀