Skip to content

订单支付与问诊室通知(三) ​

本页对应 订单支付与问诊室通知,共 7 段视频,约 54 分钟。

学习目标 ​

  • 能够说明 socket.io 与 WebSocket 的关系,并完成 socket.io-client 的安装与模块导入。
  • 能够使用 io() 建立客户端连接,通过 on 监听服务端事件、通过 emit 向服务端发送消息并验证通信结果。
  • 能够根据前后端约定自定义事件名称,完成客户端与服务端之间带参数的消息收发与联调。
  • 能够创建问诊室页面模板并配置分包路由,在支付成功后携带 orderId、token 等参数建立问诊室 WebSocket 连接。
  • 能够监听历史消息事件,解析并保存消息列表数据,按 messageType 区分文字、图片、患者信息、温馨提示、处方、评价等消息并选择对应模板渲染。

知识点 ​

一、先明确:socket.io 客户端是干什么的 ​

为什么需要:原生 WebSocket 能通信,但事件命名、断线兼容、参数传递等都要自己处理。socket.io 基于 WebSocket 做了二次封装,客户端用 on 收消息、用 emit 发消息,语义清楚。

怎么想:把“客户端”和“服务端”想成两个能互相触发事件的对象。客户端触发服务端监听的事件,服务端也能触发客户端监听的事件。谁发消息谁用 emit,谁收消息谁用 on。

怎么做:先记住两个核心方法:

js
socket.on('事件名', (data) => {})   // 接收数据
socket.emit('事件名', data)         // 发送数据

易错点:转写稿里的“昂”就是 on,“一米特/emit”就是 emit;不要把 on 和 emit 的职责记反。事件名不是固定死的,但前后端必须约定一致。

二、安装与导入:客户端包别装错 ​

为什么需要:项目里要用模块方式引入 socket.io,不能一直靠 CDN。装错包会导致导入时报“找不到 socket.io-client”。

怎么想:socket.io 同时有服务端包和客户端包。浏览器端要装的是带 client 的包,导入时也要带 client。

怎么做:

bash
npm install socket.io-client
js
import { io } from 'socket.io-client'

转写稿中曾先装错,后来重新安装带 client 的包才解决。调试时看报错信息:如果提示找不到 socket.io-client,先检查安装名和导入名。

易错点:socket.io 更偏服务端;socket.io-client 才是客户端。安装和导入都要带 client,不要只凭编辑器提示随手选。

三、建立连接:io(地址, 配置) ​

为什么需要:真正开发时不能写死本地地址,要使用后端提供的接口地址,并携带登录和订单参数。

怎么想:io 是一个函数,第一个参数是后端地址,第二个参数是配置。问诊室必须带 token,因为患者登录后才能聊天;还要带 orderId,后端要知道你已支付。

怎么做:按转写稿整理连接代码:

js
const socket = io(接口地址, {
  auth: { token },
  query: { orderId },
  transports: ['websocket', 'polling'],
  timeout: 5000
})

其中 token 从 pinia 的 user store 取,orderId 从地址参数取。transports 表示优先 WebSocket,不支持时用长轮询;timeout 是连接超时设置。验证时打开浏览器开发者工具的 WS 面板,看到连接信息即建立成功。

易错点:token、orderId 要先准备好再传;转写稿口述的“长轮巡”应写作“长轮询”。服务端代码修改后要 Ctrl+C 两次停掉,再重新运行,并刷新浏览器。

四、收发消息与自定义事件 ​

为什么需要:连接只是通道,真正业务是互相发消息。客户端要能触发服务端事件,也要监听服务端事件。

怎么想:客户端 emit('send to server', 数据),服务端就 on('send to server', ...) 接收;服务端 emit('send to client', 数据),客户端就 on('send to client', ...) 接收。自定义事件同理,比如前后端约定 abc。

怎么做:

js
socket.emit('send to server', '来自客户端的数据')
socket.on('send to client', (data) => {
  console.log(data)
})

socket.emit('abc', { msg: 'A111' })

服务端对应监听:

js
socket.on('abc', (msg) => {
  console.log(msg)
})

易错点:客户端触发了事件,服务端没监听就是“白触发”;事件名、参数结构要按接口文档来。转写稿里的 C log 即 console.log。

五、接入问诊室:历史消息与消息类型 ​

为什么需要:建立连接后,服务端会返回历史消息;聊天列表里文字、图片、温馨提示、患者信息、处方、评价等布局不同,不能用一个模板硬套。

怎么想:历史消息用 on 接收,事件名按接口文档约定为 chat message list。拿到 result 后先判断 code,成功再把 data 存入 messageList,供页面渲染。

怎么做:

js
socket.on('chat message list', (result) => {
  const { code, data } = result
  if (code !== 1) return
  messageList.value = data
})

消息类型按转写稿梳理:1 文字消息,4 图片消息,21 患者信息,22 处方消息,23 评价消息,31 普通消息,32 温馨提示。不同 msgType 对应不同模板,建议一个类型拆一个组件。

易错点:chat message list 不要写成转写稿口述的“Tide message list”;msgType 不要写成“MSDt”;31 不要听成“30亿”。先看接口文档和 WS 返回数据全貌,再按类型渲染。

安装与导入:先分清服务端包和客户端包 ​

为什么需要:客户端要发送消息,先得把 socket.io 的客户端库装进项目。转写稿里一开始装错了包,后来重新装 socket.io-client,这是一个很典型的坑:socket.io 更多是服务端用的,浏览器端要引的是带 client 的那个。

怎么想:先判断当前代码跑在哪一端。静态页面可以用 CDN 引入;Vue 项目里按普通模块导入更合适。导入时看包名,必须确认是 socket.io-client,不要只看“socket.io”几个字。

怎么做:

bash
npm install socket.io-client
js
import { io } from 'socket.io-client'

易错点:装错包、导错包都会报“找不到模块”之类的错误。转写稿里就是先装了不带 client 的包,导入 socket.io-client 时找不到,重新安装带 client 的包后才正常。另一个易错点是:服务端代码和客户端代码不是同一个包,不要混用。

建立连接:io 函数拿到 socket 实例 ​

为什么需要:emit 不能凭空发消息,必须先有和服务端的连接。连接建立后,客户端才能触发事件、监听事件。

怎么想:把 socket.io 想成“客户端和服务端之间的通道”。io(地址, 配置) 负责建立通道,返回一个 socket 实例;后续发消息、收消息都围绕这个实例做。地址不是随便写的,开发时可以用本地地址,真正联调时要让后端提供接口地址;参数也要按接口文档传,比如登录 token、订单 orderId。

怎么做:

js
const socket = io('后端提供的地址', {
  auth: {
    token
  },
  query: {
    orderId
  },
  transports: ['websocket', 'polling'],
  timeout: 5000
})

转写稿里解释过:transports 是兼容处理,优先用 WebSocket,不支持时用长轮询;timeout 是连接超时设置。orderId 可以从地址参数里取,token 从 Pinia 的用户信息里取。注意转写稿里提到:解构出来的 token 没有响应式,但这里不改它,所以问题不大;如果要响应式,需要按 ref 的方式加 .value。

易错点:地址写错、参数名写错、token 没取到,都会导致连接失败。验证时打开浏览器开发者工具的“网络/WS”,看连接是否建立、请求里有没有带上参数。

用 emit 发送消息:客户端触发服务端事件 ​

为什么需要:建立连接只是“能说话”,真正聊天要客户端主动把消息发出去。socket.io 里客户端发送数据靠 emit。

怎么想:记住两个词:on 用来监听、接收;emit 用来触发、发送。客户端 emit 的事件,是服务端要监听的事件;事件名不是固定死的,而是前后端约定好的。转写稿里用 send to server 举例:客户端触发服务端的事件,并携带“来自客户端的数据”。

怎么做:

js
socket.emit('send to server', '来自客户端的数据')

自定义事件也一样:

js
socket.emit('ABC', { msg: 'A111' })

服务端要对应监听:

js
socket.on('ABC', (msg) => {
  console.log(msg)
})

转写稿里强调:如果服务端没有监听这个事件,客户端触发就是“白触发”。修改服务端代码后要重启服务:Ctrl + C 两次停掉,再按向上箭头调出刚才的命令重新运行;浏览器端也要刷新。

易错点:事件名前后端不一致;只写了 emit 但服务端没写 on;改了服务端忘记重启;浏览器没刷新。发送后要看服务端控制台有没有打印,才能确认数据真的到了。

用 on 接收消息与调试:服务端发、客户端收 ​

为什么需要:聊天不是单向的。客户端发出去后,服务端可能回消息,也可能主动推送历史消息列表。客户端要接收,就得用 on 监听约定事件。

怎么想:把“客户端”和“服务端”想成父子组件通信的双方:一方 emit 触发事件,另一方 on 监听事件。服务端 emit('send to client', 数据),客户端就 on('send to client', 回调) 接收;服务端推送历史消息时,客户端监听 chat message list。

怎么做:

js
socket.on('chat message list', (result) => {
  console.log(result)
})

转写稿里先打印 result 看数据全貌,再按业务取 code、data,判断 code !== 1 就 return,成功则把数据保存到 messageList,供页面渲染。后续列表里会有不同类型,如 msgType 为 21 是患者信息、31 是普通消息、32 是温馨提示等,需要按类型展示不同模板。

易错点:on 的事件名写错;把“发送”和“接收”弄反;拿到数据不判断 code 直接渲染;服务端推送了但客户端没监听。调试时先看控制台打印,再看网络 WS 里的请求和数据,确认连接与事件都正常。

一、先明确:socket.io 的自定义事件是什么 ​

为什么需要:原生 WebSocket 只提供底层连接,业务里“发消息、收消息、开处方、结束问诊”都需要自己约定消息格式。socket.io 在 WebSocket 基础上做了二次封装,提供 on 和 emit,让前后端用“事件名 + 数据”通信。

怎么想:把客户端和服务端想成父子组件:emit 是触发事件,on 是监听事件。事件名不是固定的,由前后端约定;约定后不能随意改。

怎么做:客户端监听服务端事件用 socket.on('事件名', 回调);客户端给服务端发数据用 socket.emit('事件名', 数据)。服务端同理:socket.on 接收,socket.emit 发送。

易错点:emit 第二个参数才是数据;事件名前后端必须完全一致;服务端没监听时,客户端触发就是“白触发”。

js
// 客户端触发服务端事件
socket.emit('ABC', { data: 'A111' })

// 服务端监听客户端事件
socket.on('ABC', (msg) => {
  console.log(msg)
})

二、客户端连接:io() 拿到 socket 实例 ​

为什么需要:只有先建立连接,后面的 on、emit 才有意义。

怎么想:socket.io-client 暴露 io 函数,传入后端地址和配置,返回 socket 实例。实例上有 on、emit。

怎么做:安装客户端包,注意不是服务端包:

bash
npm install socket.io-client
js
import { io } from 'socket.io-client'

const socket = io('后端接口地址', {
  auth: { token },
  query: { orderId },
  transports: ['websocket', 'polling'],
  timeout: 5000
})

易错点:转写里安装时曾装错,报“确定这个存在吗”,原因是装成了服务端包;客户端要带 client。transports 表示优先 WebSocket,不支持时用长轮询;timeout 是连接超时。orderId 从地址参数取,token 从 Pinia 用户 store 取;解构 token 当前只读,若要响应式需用 value。

三、接收与发送:on 接数据,emit 发数据 ​

为什么需要:聊天室既要收历史消息,也要发新消息。

怎么想:记住一句话:on 用来接收数据,emit 用来发送数据。

怎么做:先监听后端约定的事件名,例如历史消息 chat message list:

js
socket.on('chat message list', (result) => {
  console.log(result)
})

先打印全貌,确认结构后再解构核心数据;异常就 return,成功则存入 ref 消息列表。

易错点:不要自己改事件名;转写中口述的“Tide message list”应以后端文档的 chat message list 为准。调试时打开浏览器 WS 面板,看到连接成功和返回数据,再写渲染。

四、自定义事件联调:客户端 emit,服务端 on ​

为什么需要:验证“自定义事件”不是写死 API,而是前后端约定。

怎么想:客户端触发 ABC,服务端必须监听 ABC;服务端改代码后要重启。

怎么做:服务端示意:

js
io.on('connection', (socket) => {
  socket.on('ABC', (msg) => {
    console.log(msg)
  })
})

修改服务端后,终端 Ctrl+C 两次停止,再按上箭头重跑;浏览器刷新后,在控制台执行 socket.emit('ABC', { data: 'A111' })。

易错点:服务端没重启,旧代码仍在跑;浏览器没刷新,旧连接仍存在;事件名大小写、空格不一致。

五、反向自定义事件:服务端 emit,客户端 on ​

为什么需要:医生回复、开处方、结束问诊,都是服务端主动推给客户端。

怎么想:服务端 emit 触发的是客户端事件,客户端必须提前 on 监听。

怎么做:服务端 socket.emit('send to client', data),客户端 socket.on('send to client', (data) => { ... })。转写中服务端随机返回“你好 / 我在写代码 / 快下课了”,客户端能收到,说明双向事件打通。

易错点:客户端监听写在连接之后;回调参数别漏;服务端推送的数据结构要按接口文档处理。

六、消息列表:一个列表,多种模板 ​

为什么需要:问诊室列表里不只有文字,还有患者信息、温馨提示、图片、处方、评价等。

怎么想:用 msgType 区分模板,例如 21 患者信息、31 普通消息、32 温馨提示、22 处方、23 评价。一个类型一个组件,加载时按类型渲染。

怎么做:先 console.log(result) 看数据全貌,再按 msgType 拆分组件。不要把所有布局硬塞进一个模板。

易错点:不要把“列表布局都一样”的经验套过来;类型值要以后端返回为准,转写中的“MSEtype”应纠正为 msgType。

代码示例 ​

示例一:Node 服务端(socket.io server) ​

js
// server.js —— 终端执行:node server.js(先 npm i socket.io)
const http = require('http')
const { Server } = require('socket.io')

const httpServer = http.createServer()
// 把 http 服务交给 socket.io,得到 io 实例(名字随意起)
const io = new Server(httpServer, { cors: { origin: '*' } })

const msgs = ['你好', '我在写代码', '快下课了']

// on('connection'):当有客户端连接建立好之后触发
io.on('connection', (socket) => {
  console.log('有客户端建立连接了')

  // 服务端 emit:给客户端发数据,客户端用 on('sendToClient') 接收
  socket.emit('sendToClient', msgs[Math.floor(Math.random() * msgs.length)])

  // 客户端 emit('sendToServer', 数据) 时,这里被触发
  socket.on('sendToServer', (data) => {
    console.log('收到客户端的数据:', data)
    // 收到后随机回一条
    socket.emit('sendToClient', msgs[Math.floor(Math.random() * msgs.length)])
  })

  // 自定义事件 ABC,客户端 emit('ABC', { a: 111, b: 222 })
  socket.on('ABC', (msg) => {
    console.log('收到 ABC 事件的数据:', msg)
  })
})

httpServer.listen(3000, () => {
  console.log('socket.io 服务已启动:http://localhost:3000')
})

关键行与易错点

  • new Server(httpServer) 必须把 http 服务传进去,否则只有 http 没有 ws 能力;io.on('connection', ...) 是“连接建立好之后”的入口,所有 socket.on 都写在这个回调里。
  • 服务端发数据用 socket.emit,收数据用 socket.on,事件名(sendToClient / sendToServer / ABC)全是前后端自己约定的,不是固定死的,但两边必须写成同一个字符串。
  • 服务端代码每改一次都要重启:Ctrl + C 停掉,按方向键上翻出上条命令回车重跑;不重启你会发现“客户端触发了但服务端没反应”。

示例二:原生 HTML 客户端(CDN 引入 + 收发消息) ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>socket.io 客户端</title>
  <!-- CDN 引入的是客户端包 socket.io.min.js -->
  <script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
</head>
<body>
  <button id="send">给服务端发消息</button>
  <button id="abc">触发自定义事件 ABC</button>
  <ul id="list"></ul>

  <script>
    // io() 是库提供的全局函数:第一个参数是地址,第二个参数是配置(可省略)
    const socket = io('http://localhost:3000')

    socket.on('connect', () => {
      console.log('建立连接了')
    })

    // on:监听事件,用来接收数据
    socket.on('sendToClient', (data) => {
      console.log('收到来自服务器的数据:', data)
      const li = document.createElement('li')
      li.textContent = data
      document.getElementById('list').appendChild(li)
    })

    // emit:触发事件,用来发送数据(事件名 + 数据)
    document.getElementById('send').onclick = () => {
      socket.emit('sendToServer', '快睡觉吧')
    }

    document.getElementById('abc').onclick = () => {
      socket.emit('ABC', { a: 111, b: 222 })
    }
  </script>
</body>
</html>

关键行与易错点

  • 记死两句话:on 用来接收数据,emit 用来发送数据;emit 第一个参数是事件名,第二个参数才是数据(和 Vue 组件里 $emit('事件名', 数据) 完全一个套路)。
  • 客户端 socket.emit('sendToServer', ...) 触发的是服务端的事件,所以服务端必须有对应的 socket.on('sendToServer'),否则就是“白触发”。
  • 先启动服务端(node server.js,光标停住不动说明服务器正在运行),再打开/刷新页面;打开页面后控制台出现“建立连接”,就说明浏览器和服务器已经架好桥了。

示例三:Vue 项目里建立连接 + 接收历史消息(问诊室) ​

js
// 安装:npm i socket.io-client   ← 包名带 client,别装成服务端的 socket.io
import { io } from 'socket.io-client'
import { ref } from 'vue'
import { useRoute } from 'vue-router'
import { useUserStore } from '@/stores/user'

const route = useRoute()
const userStore = useUserStore()
const { token } = userStore        // 解构后没有响应式,这里只读不改,够用

// 地址栏参数:支付成功跳回来时自动带上 orderId
const orderId = route.query.orderId

const messageList = ref([])

// 建立连接:地址 + 一堆参数(参数以接口文档为准)
const socket = io('后端提供的接口地址', {
  auth: { token },                        // 登录信息,接口文档要求传 token
  query: { orderId },                     // query 传参,类似地址栏 ?orderId=xxx
  transports: ['websocket', 'polling'],   // 优先 websocket,不支持则长轮询
  timeout: 5000                           // 5 秒连不上就放弃
})

// on 接收服务端推过来的数据,事件名由后端接口文档规定,不能随便改
socket.on('chat message list', (result) => {
  const { code, data } = result
  if (code !== 1) return                  // 数据有问题直接返回
  messageList.value = data                // ref 必须用 .value 赋值
})

关键行与易错点

  • 装包容易翻车:导入时写 import { io } from 'socket.io-client',安装时也必须是 socket.io-client,装成 socket.io 会报“找不到模块”。
  • io() 第一个参数是后端给的真实接口地址(不是本地测试的 localhost:3000),第二个参数要照着接口文档传:auth 放 token、query 放 orderId——没交钱没订单,后端不会让你聊。
  • transports: ['websocket', 'polling'] 是 socket.io 的兼容处理:支持 WebSocket 就用 WebSocket,不支持就退回长轮询。
  • 事件名(如 chat message list)是前后端约定好的,改了前端就收不到数据;验证是否连上,可到浏览器 Network 面板的 WS 里看有没有连接信息。
  • 用 ref 存的列表赋值时不要忘了 .value;const { token } = userStore 这种解构不具备响应式,只读场景可以,要跟着变必须用 userStore.token。

常见错误 ​

1. 装错包,导入即报错

  • 现象:import { io } from 'socket.io-client' 报"找不到模块 / 无法解析",控制台提示确定该依赖存在吗。
  • 原因:执行了 npm install socket.io,装的是服务端包;客户端要的是 socket.io-client。转写稿里讲师本人也踩了这个坑。
  • 修法:删掉重装 npm i socket.io-client,导入路径与安装包名必须一致(带 -client)。

2. 服务端改了代码,客户端触发事件毫无反应

  • 现象:前端 emit 出去了,服务端 console.log 不打印;或新加的 on('abc') 一直不触发。
  • 原因:Node 进程不会热更新,改完 .js 没重启,跑的还是旧代码;同时浏览器页面也没刷新,旧连接还挂着。
  • 修法:终端 Ctrl + C(两次)停掉 → 上箭头调回上条命令重跑 → 浏览器刷新页面重连。

3. 事件名前后端对不上,emit 白触发

  • 现象:客户端 emit('sendToServer'),服务端 on('send-to-server'),两边都"正常执行"但谁也收不到谁。
  • 原因:事件名是自定义字符串,不做任何校验;接口文档常写成 chat-message-list 这种带连字符的形式,直接照抄进 on() 就错了。另外服务端压根没写 on 监听时,客户端触发同样无效。
  • 修法:以接口文档为准逐字对齐(文档里的连字符写法对应代码里的事件名要去掉 -,如 chatMessageList);先确认服务端已有 on 监听,再写客户端 emit。

4. on / emit 方向写反

  • 现象:页面收不到消息;或想发消息却写成了 socket.on('sendToServer', data),回调永远不执行。
  • 原因:把"监听"和"触发"混为一谈。on = 接收数据(监听事件),emit = 发送数据(触发事件),且 emit 触发的是对方那边的事件。
  • 修法:按"谁发谁 emit,谁收谁 on"自查一遍:客户端发 → 客户端 emit + 服务端 on;服务端发 → 服务端 emit + 客户端 on。

5. 连接参数缺失,连上了但拿不到数据

  • 现象:WS 面板里能看到连接,但 chatMessageList 不回调;或直接被后端拒绝。
  • 原因:io(url, { auth, query, transports, timeout }) 里的 auth.token、query.orderId 是后端校验身份和订单的必传项,漏传就查不到会话。
  • 修法:对照接口文档补齐 auth 与 query;orderId 从路由参数取(注意地址参数都是字符串),token 从 pinia 取——直接解构 store 会丢响应式,后续要用就用 store.token 或 storeToRefs。

6. 一个列表用一套模板渲染所有消息

  • 现象:患者信息、温馨提示、图片、处方、评价全挤成同一个白底黑字气泡,样式全乱。
  • 原因:问诊室列表的 msgType 有 9 种(1 文字、4 图片、21 患者信息、22 处方、23 评价、31 普通消息、32 温馨提示……),不是"一个列表一种布局"的常规场景。
  • 修法:按 msgType 拆成独立组件,父级用条件渲染/动态组件分发;接收数据时先判 code !== 1 直接 return,再把 data 存进 ref([]) 的 messageList。

练习 ​

  1. 最小可跑通闭环:起一个 Node 服务端(on('connection') 里 socket.emit('welcome', ...)),客户端 io('http://localhost:3000'),用 on 收欢迎语、用 emit 发一条自定义事件 abc 带对象数据,服务端 console.log 打出来。要求:全程在浏览器 Network → WS 面板里找到收发帧。

  2. 造一次"白触发"并修复:故意把服务端 on('abc') 改名为 on('abcd') 而不动客户端,观察现象;然后只改客户端或只改服务端各修一次,说明为什么"两边都跑起来了却谁也收不到"。

  3. 连接参数拼装:在问诊室页面中,从 useRoute() 取 orderId、从 pinia 取 token,组装成 io(url, { auth: { token }, query: { orderId }, transports: ['websocket', 'polling'], timeout: 5000 })。要求说明 transports 数组两项的优先级含义,以及 timeout 超时后会发生什么。

  4. 历史消息接入:监听后端约定的事件(如 chatMessageList),回调里打印完整 result 看清数据结构,再解构出 code 与 data,判 code !== 1 提前 return,成功则存入 messageList(初始为 ref([]))。在控制台验证三条初始数据的 msgType 分别是 21、31、32。

  5. 类型分发设计:不写具体样式,只写出 messageList 的渲染骨架——按 msgType 映射到 5 个以上不同子组件的方案(v-if 链 / 动态组件 :is / 映射表三种任选其一),并说明为什么这个列表不能沿用患者列表"一套模板循环"的写法。

课堂画面 ​

课堂画面 01

课堂画面 02

课堂画面 03

课堂画面 04

课堂画面 05

课堂画面 06

原视频对照 ​

#视频时长
1socket.io客户端介绍06:26
2socket.io客户端发送消息09:13
3socket.io自定义事件07:16
4优医咨询-问诊室页面模板05:36
5优医咨询-问诊室建立websocket连接12:19
6优医咨询-问诊室历史消息06:02
7优医咨询-消息类型说明07:55

小结 ​

  1. 客户端只装 socket.io-client、只导 socket.io-client;socket.io 是服务端包,两个名字差一个后缀,装错就是编译期报错。
  2. 一个 io(url, options) 得到一个实例,实例上只需记牢两件事:on 收数据、emit 发数据;emit 触发的是对方那边注册的事件。
  3. 事件名完全自定义、无编译期校验,前后端必须逐字对齐,接口文档是唯一权威;服务端没写 on,客户端 emit 就是空放。
  4. 服务端 Node 代码不热更新,每次改动都要 Ctrl + C 重启并刷新浏览器,否则你调试的是旧逻辑。
  5. auth(token)与 query(orderId)是后端鉴权和定位会话的必传项,地址参数一律是字符串;store 解构会丢响应式,要用就保留 store.xxx 写法。
  6. transports: ['websocket', 'polling'] 体现 socket.io 的兼容策略:优先 WebSocket,不支持则降级长轮询;timeout 是连接建立的兜底。
  7. 问诊室是"一个列表、多种模板"的典型场景,msgType(1/4/21/22/23/31/32…)决定渲染哪套布局,必须按类型拆组件,而不是复用同一套列表模板。
  8. 收数据的固定套路:on → 打印全貌看清结构 → 解构 code/data → 判 code !== 1 提前 return → 存入 ref 数组 → 交给模板按类型分发。