UniApp 对接 IM 即时通讯全攻略(下篇)

UniApp 对接 IM 即时通讯全攻略(下篇)

上篇中,我们完成了 WebSocket 模块封装和 Pinia 状态管理的搭建。本篇将基于此,实现 IM 的核心页面:会话列表页和聊天页,并处理 Tab 未读角标,最后介绍一些性能优化和常见问题。


一、会话列表页面

会话列表是 IM 应用的首页,展示所有最近会话,包括最后一条消息预览、未读数和时间等。页面数据来自 Pinia 中的 conversations

1.1 模板示例

vue 复制代码
<template>
  <view>
    <view v-for="conv in imStore.conversations" :key="conv.id" @click="openChat(conv.id)">
      <image :src="conv.avatar" />
      <view>
        <text>{{ conv.name }}</text>
        <text>{{ conv.lastMessage }}</text>
      </view>
      <view>
        <text>{{ conv.lastTime }}</text>
        <text v-if="conv.unreadCount > 0" class="badge">{{ conv.unreadCount }}</text>
      </view>
    </view>
  </view>
</template>

1.2 逻辑实现

vue 复制代码
<script setup>
import { useImStore } from '@/stores/im';
import { onShow, onPullDownRefresh } from '@dcloudio/uni-app';

const imStore = useImStore();

onShow(() => {
  imStore.fetchConversations(); // 每次显示刷新
});

onPullDownRefresh(async () => {
  await imStore.fetchConversations();
  uni.stopPullDownRefresh();
});

function openChat(convId) {
  uni.navigateTo({ url: `/pages/im/chat?id=${convId}` });
}
</script>

要点

  • onShow 中拉取会话列表,保证数据最新。
  • 下拉刷新重新拉取,覆盖本地数据。
  • 新消息到达时,Pinia 的 handleIncomingMessage 会自动更新会话列表,无需额外监听。

二、聊天页面

聊天页面负责展示与某个联系人或群组的所有消息,并支持发送新消息、加载更早历史消息。

2.1 模板结构

vue 复制代码
<template>
  <view>
    <scroll-view scroll-y :scroll-into-view="scrollIntoView" class="message-list">
      <view v-for="msg in imStore.currentMessages" :key="msg.id" :id="'msg-' + msg.id">
        <text>{{ msg.content }}</text>
      </view>
    </scroll-view>
    <view class="input-bar">
      <input v-model="inputText" @confirm="sendMessage" placeholder="输入消息..." />
    </view>
  </view>
</template>

2.2 逻辑实现

vue 复制代码
<script setup>
import { ref, watch, nextTick } from 'vue';
import { useImStore } from '@/stores/im';
import { onLoad, onUnload, onPullDownRefresh } from '@dcloudio/uni-app';

const imStore = useImStore();
const inputText = ref('');
const scrollIntoView = ref('');

onLoad((options) => {
  const convId = options.id;
  imStore.fetchMessages(convId, 1);
  imStore.markConversationRead(convId);
});

onUnload(() => {
  imStore.clearCurrentConversation(); // 可选,释放内存
});

onPullDownRefresh(async () => {
  if (imStore.hasMore) {
    const nextPage = imStore.currentPage + 1;
    await imStore.fetchMessages(imStore.currentConvId, nextPage);
  }
  uni.stopPullDownRefresh();
});

// 自动滚动到底部
watch(
  () => imStore.currentMessages.length,
  async () => {
    await nextTick();
    if (imStore.currentMessages.length > 0) {
      const lastMsg = imStore.currentMessages[imStore.currentMessages.length - 1];
      scrollIntoView.value = 'msg-' + lastMsg.id;
    }
  }
);

async function sendMessage() {
  if (!inputText.value.trim()) return;
  await uni.request({
    url: '/api/sendMessage',
    method: 'POST',
    data: {
      conversationId: imStore.currentConvId,
      content: inputText.value.trim()
    }
  });
  inputText.value = '';
}
</script>

说明

  • 打开页面时加载第一页消息,并标记会话已读。
  • 下拉加载更早的消息(向前翻页)。
  • 发送消息使用 HTTP API,发送成功后由服务器通过 WebSocket 推送给双方,本地无需手动插入消息(也可乐观更新,但要注意去重)。
  • 页面卸载时清空当前会话数据,避免占用内存。

三、Tab 未读角标处理

底部 Tab 通常需要显示消息未读总数。我们可以通过 Pinia 的 totalUnread 动态更新 TabBar 的角标。

3.1 在自定义 TabBar 组件中监听

如果使用自定义 TabBar,可以直接在组件中读取 imStore.totalUnread 并渲染。

vue 复制代码
<template>
  <view class="tab-bar">
    <!-- 其他 tab -->
    <view class="tab-item">
      <text>消息</text>
      <text v-if="imStore.totalUnread > 0" class="badge">{{ imStore.totalUnread > 99 ? '99+' : imStore.totalUnread }}</text>
    </view>
  </view>
</template>

<script setup>
import { useImStore } from '@/stores/im';
const imStore = useImStore();
</script>

3.2 使用原生 TabBar 的 setTabBarBadge

如果使用原生 TabBar,可以在 App.vue 或全局监听 imStore.totalUnread 变化,调用 uni.setTabBarBadge

javascript 复制代码
// 在 App.vue 中 watch totalUnread
watch(() => imStore.totalUnread, (val) => {
  if (val > 0) {
    uni.setTabBarBadge({ index: 1, text: val > 99 ? '99+' : String(val) });
  } else {
    uni.removeTabBarBadge({ index: 1 });
  }
});

四、性能优化与注意事项

4.1 消息去重与排序

网络异常或重连可能导致消息重复。建议在 Store 中维护一个 msgId 集合,插入前检查:

javascript 复制代码
// 在 handleIncomingMessage 中
if (this.currentMessages.some(m => m.id === msg.id)) return;
this.currentMessages.push(msg);
// 可根据服务器返回的时间戳排序
this.currentMessages.sort((a, b) => a.timestamp - b.timestamp);

4.2 心跳与重连参数调优

  • 心跳间隔:移动网络建议 30~60 秒,过短会消耗流量和电量。
  • 重连间隔:建议指数退避(如 1s、3s、5s...),避免服务器压力过大。

4.3 清理监听器

  • 使用 registerMessageHandler 注册的处理器,在页面卸载时应调用其返回的取消函数(如果只在 App.vue 中注册一次则可忽略)。
  • 使用 uni.$emit 时,务必在 onUnloaduni.$off

4.4 安全与鉴权

建议 WebSocket 连接时在 URL 中携带 token,或连接后发送鉴权消息,避免通过 bangUid 明文绑定用户。


五、总结

通过上下两篇,我们完成了一个完整的 UniApp IM 模块搭建:

  • 上篇:WebSocket 连接管理、Pinia 状态设计、消息分发机制。
  • 下篇:会话列表、聊天页面、未读角标及优化。

这套方案已在多个项目中验证,具备良好的稳定性和扩展性。你可以根据实际后端协议调整接口路径、消息格式等。希望对你有所帮助!

有任何疑问或建议,欢迎在评论区交流。

相关推荐
2501_915106322 小时前
Flutter iOS混淆打包详细教程与步骤
android·flutter·ios·小程序·uni-app·iphone·webview
DK1858383225215 小时前
【源码开源部署】电竞代练护航陪玩小程序:全游戏服务平台完整解决方案(附宝塔部署教程)
游戏·微信小程序·uni-app·开源·php
码云数智-大飞15 小时前
2026 年跨端开发决战:小程序原生 vs uni-app vs Taro 深度对比
小程序·uni-app·taro
BS30813_vx21 小时前
基于 Express + uni-app 的养老院管理系统设计与实现
后端·uni-app·express
bug总结1 天前
uniapp 微信小程序请求规范
微信小程序·小程序·uni-app
梦曦i2 天前
unix-router v0.1.0 首发:uni-app x 的 vue-router 风格路由库
前端·uni-app
bug总结3 天前
uniapp 微信小程序分包规范
微信小程序·小程序·uni-app
天府云创3 天前
uni-app X 蒸汽模式发布!性能比肩原生~
前端框架·uni-app·移动开发·原生应用·蒸汽模式·app研发·多端适配
堕落年代3 天前
uni-app x 蒸汽模式:实时流式语音识别(ASR)三大疑难杂症全记录
人工智能·uni-app·语音识别