更新记录

2.0.0(2026-07-23) 下载此版本

2.0.0

  • 支持 uni-app 与 uni-app x,并提供 Vue/UVue easycom 聊天组件。
  • 支持用户端与员工端独立实例、会话列表、消息收发、未读统计和 WebSocket 重连。
  • 新增聊天页面路由构造与打开 API,统一从插件根目录导入。
  • 图片和视频上传支持自定义 multipart 字段名、表单参数、请求头和超时时间。
  • 修复 Web 图片上传失败、Android 页面样式异常及切换服务配置后沿用旧会话的问题。
  • 修复 uni-app x App-iOS 实例子模块调用和持续状态订阅失效的问题。
  • 位置选择入口支持按需启用。

平台兼容性

uni-app(3.8.1)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟

uni-app x(5.0)

Chrome Safari Android iOS 鸿蒙 微信小程序

其他

多语言 暗黑模式 宽屏模式
× ×

JSZN IM SDK

为 uni-app 与 uni-app x 提供用户端、员工端即时通讯 API、WebSocket、会话与未读 管理,以及 Vue/UVue easycom 聊天 UI。

安装

通过 DCloud 插件市场导入后,插件目录应为:

uni_modules/jszn-im-sdk

API、常量、错误和 UTS 类型统一从插件根目录导入。不要依赖 /indexcore/utssdk/ 或其他内部路径。

import {
  createUserIMSDK,
  type IMSDKConfig,
} from '@/uni_modules/jszn-im-sdk'

传统 uni-app JavaScript 页面不需要导入 UTS 类型:

import { createUserIMSDK } from '@/uni_modules/jszn-im-sdk'

完整配置

uni-app 可在 config/im.js 中导出配置对象;uni-app x 建议在 config/im.uts 中声明为 IMSDKConfig。以下值均为占位值,请替换为服务提供方分配的配置。

import type { IMSDKConfig } from '@/uni_modules/jszn-im-sdk'

export const imConfig: IMSDKConfig = {
  // 聊天 UI 主色。默认值:#4a90e2。
  themeColor: '#4a90e2',
  // 主色的浅色变体,用于弱强调状态。默认值:#6faef0。
  themeColorLight: '#6faef0',
  // 主色的深色变体,用于按下或强调状态。默认值:#3a7bc8。
  themeColorDark: '#3a7bc8',

  api: {
    // HTTP API 网关。所有角色必填,不包含末尾业务路径。
    baseURL: 'https://api.example.com',
    // 补全后端返回的相对图片、视频和文件地址;返回值均为绝对地址时可留空。
    resourceBaseURL: 'https://resource.example.com',
    // 图片上传接口,multipart 文件字段名默认使用 image。
    uploadImageURL: 'https://resource.example.com/system/uploadImage',
    // 视频和通用文件上传接口,multipart 文件字段名默认使用 file。
    uploadFileURL: 'https://resource.example.com/system/uploadFile',
    // 请求签名密钥。所有角色必填,由服务提供方分配并妥善保管。
    signKey: 'your-sign-key',
    // 员工端站点 ID。createStaffIMSDK 必填;用户端可传空字符串。
    siteId: 'your-site-id',
    // 后端未返回头像时使用的 UI 占位地址;不需要远程占位图时可留空。
    defaultAvatar: 'https://example.com/avatar.png',
    // HTTP、上传和下载超时,单位毫秒。默认值:30000。
    timeout: 30000,
  },

  websocket: {
    // WebSocket 服务地址。留空时根据 api.baseURL 推导 ws:// 或 wss:// 地址。
    url: 'wss://api.example.com',
    // 角色平台标识。工厂会按 user/staff 自动覆盖,通常无需手动切换。
    platform: 'user',
    // 首次重连间隔,单位毫秒;后续异常重连采用退避策略。默认值:3000。
    reconnectInterval: 3000,
    // 心跳发送间隔,单位毫秒。默认值:3000。
    heartbeatInterval: 3000,
    // 单轮连接允许的最大重连次数。默认值:5。
    maxReconnectAttempts: 5,
    // 等待连接成功的最长时间,单位毫秒。默认值:30000。
    connectTimeout: 30000,
    // 是否启用协议跟踪。默认值:false。
    traceEnabled: false,
    // WebSocket 协议映射。
    socketProtocol: {
      // 握手与消息控制器。工厂会按角色自动覆盖为 User 或 Staff。
      controller: 'User',
      action: {
        // 发送消息动作名。
        send: 'send',
        // 上报消息已读动作名。
        msgRead: 'msgRead',
        // 建立连接后的握手动作名。
        onConnect: 'onConnect',
        // 心跳动作名。
        heartbeat: 'heartbeat',
      },
      // 收到这些信号时作为心跳处理,不进入普通消息列表。
      heartbeatSignals: ['ping', 'pong', 'heartbeat'],
    },
  },

  chat: {
    // friend_type=4 且页面未传 phone 时使用的平台客服手机号。
    platformServicePhone: 'your-service-phone',
  },
}

必填规则:

场景 必填配置
所有角色 api.baseURLapi.signKey
员工端 额外需要 api.siteId
uni-app x 首次创建实例时传入完整 IMSDKConfig
图片发送 api.uploadImageURL
视频发送 api.uploadFileURL
相对资源地址 建议配置 api.resourceBaseURL

用户端与员工端必须创建独立实例,不能通过修改配置把已有实例切换角色。修改 api.baseURLapi.signKeyapi.siteIdwebsocket.url 时,SDK 会关闭旧 连接并清理当前角色会话,防止跨环境复用凭据。

接入示例

示例采用 DCloud 标准项目结构。最小接入用于快速跑通用户端聊天;完整接入适用于 需要同时管理用户端、员工端、未读状态、业务路由和退出登录的正式项目。

最小接入

最小接入直接打开插件通过 pages_init.json 自动注册的兼容页面,因此宿主不需要 创建聊天承载页。请先把上文完整配置保存为项目配置文件。

uni-app 最小接入

项目结构:

├── config/
│   └── im.js
├── pages/
│   └── index/index.vue
├── uni_modules/
│   └── jszn-im-sdk/
└── pages.json

config/im.js

// 将上文“完整配置”原样放入此文件,示例值替换为实际服务配置。
export const imConfig = {
  // themeColor、api、websocket、chat 等全部字段
}

pages/index/index.vue

<template>
  <view class="page">
    <!-- 点击后完成 IM 鉴权并打开插件内置聊天页。 -->
    <button :disabled="loading" @click="openChat">
      {{ loading ? '正在进入...' : '打开在线客服' }}
    </button>
  </view>
</template>

<script>
import {
  createUserIMSDK,
  openIM,
} from '@/uni_modules/jszn-im-sdk'
import { imConfig } from '@/config/im'

// 放在模块作用域,确保页面重新显示时复用同一个用户端实例。
const sdk = createUserIMSDK(imConfig)

export default {
  data() {
    return { loading: false }
  },
  methods: {
    async openChat() {
      if (this.loading) return
      this.loading = true
      try {
        // 登录资料应来自宿主现有账号系统,而不是让 SDK 负责账号登录。
        const authorized = await sdk.ensureRoleAuthorized({
          phone: 'current-user-phone',
          username: '当前用户名称',
          avatar: 'https://example.com/avatar.png',
        })
        if (!authorized) {
          uni.showToast({ title: '用户资料不完整', icon: 'none' })
          return
        }

        // 最小接入使用插件自动注册的兼容页面。
        const opened = await openIM({
          path: '/uni_modules/jszn-im-sdk/page/index',
          role: 'user',
        })
        if (!opened) uni.showToast({ title: '聊天页面打开失败', icon: 'none' })
      } catch (error) {
        uni.showToast({
          title: error?.errMsg || error?.message || '即时通讯服务暂不可用',
          icon: 'none',
        })
      } finally {
        this.loading = false
      }
    },
  },
}
</script>

<style scoped>
.page {
  padding: 32rpx;
}
</style>

pages.json 只需要注册宿主自己的首页;插件页面由导入插件时自动合并:

{
  "pages": [
    {
      "path": "pages/index/index"
    }
  ]
}

uni-app x 最小接入

项目结构:

├── config/
│   └── im.uts
├── pages/
│   └── index/index.uvue
├── uni_modules/
│   └── jszn-im-sdk/
└── pages.json

config/im.uts

import type { IMSDKConfig } from '@/uni_modules/jszn-im-sdk'

// 将上文“完整配置”原样放入此文件,并替换全部占位值。
export const imConfig : IMSDKConfig = {
  // themeColor、api、websocket、chat 等全部字段
}

pages/index/index.uvue

<template>
  <view class="page">
    <button :disabled="loading" @click="openChat">
      {{ loading ? '正在进入...' : '打开在线客服' }}
    </button>
  </view>
</template>

<script setup lang="uts">
import {
  createUserIMSDK,
  openIM
} from '@/uni_modules/jszn-im-sdk'
import type {
  IMSDKLoginInfo,
  IMSDKOpenOptions
} from '@/uni_modules/jszn-im-sdk'
import { imConfig } from '@/config/im.uts'

// 模块级实例不会进入响应式系统,适合保存 SDK 连接与订阅状态。
const sdk = createUserIMSDK(imConfig)
const loading = ref(false)

const openChat = async () : Promise<void> => {
  if (loading.value) return
  loading.value = true
  try {
    // 登录资料由宿主账号系统提供。
    const loginInfo : IMSDKLoginInfo = {
      phone: 'current-user-phone',
      username: '当前用户名称',
      avatar: 'https://example.com/avatar.png',
      site: null
    }
    const authorized = await sdk.ensureRoleAuthorized(loginInfo)
    if (!authorized) {
      uni.showToast({ title: '用户资料不完整', icon: 'none' })
      return
    }

    const options : IMSDKOpenOptions = {
      path: '/uni_modules/jszn-im-sdk/page/index',
      role: 'user'
    }
    const opened = await openIM(options)
    if (!opened) uni.showToast({ title: '聊天页面打开失败', icon: 'none' })
  } catch (error) {
    const message = error instanceof Error ? error.message : '即时通讯服务暂不可用'
    uni.showToast({ title: message, icon: 'none' })
  } finally {
    loading.value = false
  }
}
</script>

<style scoped>
.page {
  display: flex;
  flex-direction: column;
  padding: 32rpx;
}
</style>

pages.json

{
  "pages": [
    {
      "path": "pages/index/index"
    }
  ]
}

完整接入

完整接入使用宿主自己的 /pages/im-chat/index 短路由,并把实例管理、鉴权、状态 订阅和退出清理集中在独立服务模块。业务页面只负责提供当前登录用户资料和聊天目标。

uni-app 完整接入

项目结构:

├── config/
│   └── im.js
├── services/
│   └── im.js
├── pages/
│   ├── index/index.vue
│   └── im-chat/index.vue
├── uni_modules/
│   └── jszn-im-sdk/
└── pages.json

config/im.js 使用上文完整配置。services/im.js

import {
  createStaffIMSDK,
  createUserIMSDK,
  openIM,
} from '@/uni_modules/jszn-im-sdk'
import { imConfig } from '@/config/im'

// 用户端与员工端必须是两个独立实例,分别维护 token、缓存和 WebSocket。
const instances = {
  user: null,
  staff: null,
}

function normalizeRole(role) {
  return role === 'staff' ? 'staff' : 'user'
}

export function getIMInstance(role = 'user') {
  const normalizedRole = normalizeRole(role)
  if (instances[normalizedRole]) return instances[normalizedRole]
  instances[normalizedRole] = normalizedRole === 'staff'
    ? createStaffIMSDK(imConfig)
    : createUserIMSDK(imConfig)
  return instances[normalizedRole]
}

export async function authorizeIM(role, account, forceRefresh = false) {
  const normalizedRole = normalizeRole(role)
  const loginInfo = {
    phone: String(account.phone || '').trim(),
    username: String(account.username || '').trim(),
    avatar: String(account.avatar || '').trim(),
  }
  // 员工端必须传站点;用户端不发送 site。
  if (normalizedRole === 'staff') loginInfo.site = String(account.site || '').trim()
  return getIMInstance(normalizedRole).ensureRoleAuthorized(loginInfo, { forceRefresh })
}

export async function openIMPage(role, account, target = {}) {
  const normalizedRole = normalizeRole(role)
  const authorized = await authorizeIM(normalizedRole, account)
  if (!authorized) return false
  return openIM({
    path: '/pages/im-chat/index',
    role: normalizedRole,
    session_id: target.session_id || '',
    phone: target.phone || '',
    friend_type: Number(target.friend_type || 0),
    customerOnly: target.customerOnly === true,
  })
}

export function subscribeIMState(role, listener) {
  return getIMInstance(role).subscribeState(listener)
}

export function logoutIM(role) {
  getIMInstance(role).logout()
}

pages/index/index.vue 展示业务层如何传入账号和聊天目标:

<template>
  <view class="page">
    <button :disabled="loading" @click="openSessionList">会话列表</button>
    <button :disabled="loading" @click="openCustomerService">在线客服</button>
    <button @click="logout">退出 IM</button>
    <text class="unread">未读消息:{{ unread }}</text>
  </view>
</template>

<script>
import {
  logoutIM,
  openIMPage,
  subscribeIMState,
} from '@/services/im'

// 订阅句柄不放进响应式 data,避免函数或 SDK 状态被 Vue 代理。
let stateOff = null

export default {
  data() {
    return {
      loading: false,
      unread: 0,
      // 实际项目从 Pinia、Vuex、globalData 或业务登录接口读取当前账号。
      account: {
        phone: 'current-user-phone',
        username: '当前用户名称',
        avatar: 'https://example.com/avatar.png',
      },
    }
  },
  onLoad() {
    stateOff = subscribeIMState('user', (state) => {
      this.unread = state.totalUnread
    })
  },
  onUnload() {
    stateOff?.()
    stateOff = null
  },
  methods: {
    async runOpen(target = {}) {
      if (this.loading) return
      this.loading = true
      try {
        const opened = await openIMPage('user', this.account, target)
        if (!opened) uni.showToast({ title: '聊天页面打开失败', icon: 'none' })
      } catch (error) {
        uni.showToast({
          title: error?.errMsg || error?.message || '即时通讯服务暂不可用',
          icon: 'none',
        })
      } finally {
        this.loading = false
      }
    },
    openSessionList() {
      return this.runOpen()
    },
    openCustomerService() {
      return this.runOpen({
        phone: 'service-phone',
        friend_type: 4,
        customerOnly: true,
      })
    },
    logout() {
      logoutIM('user')
      this.unread = 0
    },
  },
}
</script>

<style scoped>
.page {
  padding: 32rpx;
}
.unread {
  margin-top: 24rpx;
}
</style>

pages/im-chat/index.vue 是通用聊天承载页:

<template>
  <view class="page">
    <!-- easycom 会自动选择插件中的 Vue 组件。 -->
    <jszn-im-chat
      :role="route.role"
      :session-id="route.sessionId"
      :phone="route.phone"
      :friend-type="route.friendType"
      :customer-only="route.customerOnly"
      @error="handleError"
      @unread-change="handleUnreadChange"
    />
  </view>
</template>

<script>
import { getIMInstance } from '@/services/im'

export default {
  data() {
    return {
      route: {
        role: 'user',
        sessionId: '',
        phone: '',
        friendType: 0,
        customerOnly: false,
      },
    }
  },
  onLoad(query = {}) {
    const role = query.role === 'staff' ? 'staff' : 'user'
    // 创建或恢复对应角色实例,供 easycom 组件通过 getIMSDK 获取。
    getIMInstance(role)
    this.route = {
      role,
      sessionId: String(query.session_id || ''),
      phone: String(query.phone || ''),
      friendType: Number(query.friend_type || 0),
      customerOnly: String(query.customeronly || '') === '1',
    }
  },
  methods: {
    handleError(payload) {
      console.error('IM 页面错误', payload)
    },
    handleUnreadChange(count) {
      // 可在这里同步 tabBar 角标或应用级未读状态。
      console.log('IM 未读数', count)
    },
  },
}
</script>

<style>
page,
.page {
  width: 100%;
  height: 100%;
}
</style>

pages.json

{
  "pages": [
    {
      "path": "pages/index/index"
    },
    {
      "path": "pages/im-chat/index",
      "style": {
        "navigationStyle": "custom",
        "backgroundColor": "#f7f8fa"
      }
    }
  ]
}

员工端调用方式相同,只需传入 role='staff',并确保账号资料包含 site

await openIMPage('staff', {
  phone: 'current-staff-phone',
  username: '当前员工名称',
  avatar: 'https://example.com/staff-avatar.png',
  site: 'current-site-id',
})

uni-app x 完整接入

项目结构:

├── config/
│   └── im.uts
├── services/
│   └── im.uts
├── pages/
│   ├── index/index.uvue
│   └── im-chat/index.uvue
├── uni_modules/
│   └── jszn-im-sdk/
└── pages.json

config/im.uts 使用上文完整的 IMSDKConfigservices/im.uts

import {
  createStaffIMSDK,
  createUserIMSDK,
  openIM
} from '@/uni_modules/jszn-im-sdk'
import type {
  IMSDKInstance,
  IMSDKLoginInfo,
  IMSDKOpenOptions,
  IMSDKRole,
  IMSDKStateListener
} from '@/uni_modules/jszn-im-sdk'
import { imConfig } from '@/config/im.uts'

export type IMAccount = {
  phone : string
  username : string
  avatar : string
  site ?: string | null
}

export type IMTarget = {
  session_id ?: string | null
  phone ?: string | null
  friend_type ?: number | null
  customerOnly ?: boolean | null
}

let userSDK : IMSDKInstance | null = null
let staffSDK : IMSDKInstance | null = null

export function getIMInstance(role : IMSDKRole) : IMSDKInstance {
  if (role == 'staff') {
    if (staffSDK == null) staffSDK = createStaffIMSDK(imConfig)
    return staffSDK as IMSDKInstance
  }
  if (userSDK == null) userSDK = createUserIMSDK(imConfig)
  return userSDK as IMSDKInstance
}

export async function authorizeIM(
  role : IMSDKRole,
  account : IMAccount,
  forceRefresh : boolean = false
) : Promise<boolean> {
  const loginInfo : IMSDKLoginInfo = {
    phone: account.phone.trim(),
    username: account.username.trim(),
    avatar: account.avatar.trim(),
    site: role == 'staff' ? (account.site ?? '') : null
  }
  return await getIMInstance(role).ensureRoleAuthorized(loginInfo, { forceRefresh })
}

export async function openIMPage(
  role : IMSDKRole,
  account : IMAccount,
  target : IMTarget | null = null
) : Promise<boolean> {
  const authorized = await authorizeIM(role, account)
  if (!authorized) return false
  const value = target ?? ({} as IMTarget)
  const options : IMSDKOpenOptions = {
    path: '/pages/im-chat/index',
    role,
    session_id: value.session_id ?? '',
    phone: value.phone ?? '',
    friend_type: value.friend_type ?? 0,
    customerOnly: value.customerOnly == true
  }
  return await openIM(options)
}

export function subscribeIMState(
  role : IMSDKRole,
  listener : IMSDKStateListener
) : () => void {
  return getIMInstance(role).onStateChange(listener)
}

export function logoutIM(role : IMSDKRole) : void {
  getIMInstance(role).logout()
}

pages/index/index.uvue

<template>
  <view class="page">
    <button :disabled="loading" @click="openSessionList">会话列表</button>
    <button :disabled="loading" @click="openCustomerService">在线客服</button>
    <button @click="logout">退出 IM</button>
    <text class="unread">未读消息:{{ unread }}</text>
  </view>
</template>

<script setup lang="uts">
import type { IMSDKState } from '@/uni_modules/jszn-im-sdk'
import {
  logoutIM,
  openIMPage,
  subscribeIMState
} from '@/services/im.uts'
import type { IMAccount, IMTarget } from '@/services/im.uts'

const loading = ref(false)
const unread = ref(0)
let stateOff : (() => void) | null = null

// 实际项目从应用级状态或业务登录接口读取当前账号。
const account : IMAccount = {
  phone: 'current-user-phone',
  username: '当前用户名称',
  avatar: 'https://example.com/avatar.png',
  site: null
}

const runOpen = async (target : IMTarget | null = null) : Promise<void> => {
  if (loading.value) return
  loading.value = true
  try {
    const opened = await openIMPage('user', account, target)
    if (!opened) uni.showToast({ title: '聊天页面打开失败', icon: 'none' })
  } catch (error) {
    const message = error instanceof Error ? error.message : '即时通讯服务暂不可用'
    uni.showToast({ title: message, icon: 'none' })
  } finally {
    loading.value = false
  }
}

const openSessionList = async () : Promise<void> => {
  await runOpen()
}

const openCustomerService = async () : Promise<void> => {
  const target : IMTarget = {
    phone: 'service-phone',
    friend_type: 4,
    customerOnly: true
  }
  await runOpen(target)
}

const logout = () : void => {
  logoutIM('user')
  unread.value = 0
}

onLoad(() => {
  stateOff = subscribeIMState('user', (state : IMSDKState) : void => {
    unread.value = state.totalUnread
  })
})

onUnload(() => {
  stateOff?.()
  stateOff = null
})
</script>

<style scoped>
.page {
  display: flex;
  flex-direction: column;
  padding: 32rpx;
}
.unread {
  margin-top: 24rpx;
}
</style>

pages/im-chat/index.uvue

<template>
  <view class="page">
    <!-- easycom 自动选择插件中的 UVue 组件。 -->
    <jszn-im-chat
      :role="role"
      :session-id="sessionId"
      :phone="phone"
      :friend-type="friendType"
      :customer-only="customerOnly"
      @error="handleError"
      @unread-change="handleUnreadChange"
    />
  </view>
</template>

<script setup lang="uts">
import type { IMSDKRole } from '@/uni_modules/jszn-im-sdk'
import { getIMInstance } from '@/services/im.uts'

const role = ref<IMSDKRole>('user')
const sessionId = ref('')
const phone = ref('')
const friendType = ref(0)
const customerOnly = ref(false)

const stringValue = (value : any | null) : string => {
  return value == null ? '' : value.toString().trim()
}

const handleError = (payload : UTSJSONObject) : void => {
  console.error('IM 页面错误', payload)
}

const handleUnreadChange = (count : number) : void => {
  // 可在这里同步 tabBar 角标或应用级未读状态。
  console.log('IM 未读数', count)
}

onLoad((options : OnLoadOptions) => {
  role.value = stringValue(options['role']) == 'staff' ? 'staff' : 'user'
  getIMInstance(role.value)
  sessionId.value = stringValue(options['session_id'])
  phone.value = stringValue(options['phone'])
  const parsedFriendType = parseFloat(stringValue(options['friend_type']))
  friendType.value = Number.isNaN(parsedFriendType) ? 0 : parsedFriendType
  customerOnly.value = stringValue(options['customeronly']) == '1'
})
</script>

<style scoped>
.page {
  width: 100%;
  height: 100%;
  flex: 1;
}
</style>

pages.json

{
  "pages": [
    {
      "path": "pages/index/index"
    },
    {
      "path": "pages/im-chat/index",
      "style": {
        "navigationStyle": "custom",
        "backgroundColor": "#f7f8fa"
      }
    }
  ]
}

员工端调用时传入 staff 和包含 siteIMAccount,其余流程不变。

ensureRoleAuthorized 会优先恢复当前角色的有效本地会话;资料不完整时返回 false,网络或服务端鉴权失败时抛出 SDK 错误。login 始终主动鉴权。

聊天组件

完成对应角色鉴权后,页面可直接使用 easycom 主组件,无需手写组件导入路径:

<jszn-im-chat role="user" @unread-change="handleUnreadChange" />

Props

属性 类型 默认值 说明
role string user 绑定由工厂创建的 userstaff 实例
session-id string 直接打开指定服务端会话
phone string 直接打开目标联系人
friend-type number 0 联系人类型
customer-only boolean false 直接进入平台客服模式
title string 自定义页面标题
enable-location boolean false 显示位置选择入口;宿主需先关联 uniCloud 服务空间并安装 uni-map-common
upload-image-options object \| null null 图片上传选项,结构见“请求模块”
upload-file-options object \| null null 视频上传选项,结构见“请求模块”

未传 session-idphonecustomer-only=false 时显示会话列表; customer-only=true 时直接进入平台客服聊天视图。

事件

  • error:发生可展示错误,参数包含已归一化消息和原始错误。
  • session-change:进入会话,参数包含 session_id 和会话记录。
  • unread-change:总未读数变化,参数为未读数量。

公开方法

  • refresh():刷新当前会话列表或消息列表。
  • openSession(sessionRecord):打开指定会话记录。
  • backToSessions():返回并刷新会话列表。

插件还包含 jszn-im-session-listjszn-im-message-listjszn-im-chat-inputjszn-im-emoji 底层 easycom 组件。推荐业务页面使用 jszn-im-chat 统一处理鉴权实例、会话、消息、已读和未读联动。

页面与导航

推荐由宿主注册 /pages/chat/index 短路由并承载 <jszn-im-chat />。插件同时通过 pages_init.json 注册 /uni_modules/jszn-im-sdk/page/index 兼容页面。

import { buildIMRoute, openIM } from '@/uni_modules/jszn-im-sdk'

const url = buildIMRoute({
  role: 'user',
  session_id: 'session-id',
})

const opened = await openIM({
  path: '/pages/chat/index',
  role: 'user',
  phone: 'target-phone',
  friend_type: 1,
})

导航参数包括 pathrolesession_idphonefriend_typecustomerOnly。uni-app 及 uni-app x 非 App-iOS 平台的 openIM 使用 uni.navigateTo,成功返回 true,失败返回 false。uni-app x App-iOS 的 UTS 插件原生层不能调用页面路由 API,因此 openIM 返回 false;宿主应调用 buildIMRoute 后在页面层自行导航。宿主未注册默认短路由时必须传入 path

根导出

导出 说明
createIMSDK(role, config) 创建指定角色实例
createUserIMSDK(config) 创建用户端实例
createStaffIMSDK(config) 创建员工端实例
getIMSDK(role) 获取最近创建的对应角色实例,未创建时返回 null
MESSAGE_TYPE 文本、图片、表情、位置、系统、卡片和视频消息类型
WS_EVENT_TYPE 系统、消息和已读 WebSocket 事件类型
buildIMRoute(options) 构建宿主聊天页地址
openIM(options) 打开聊天页并返回 Promise<boolean>
IMSDK_ERROR_CODE 稳定错误码映射
IMSDK_ERROR_MESSAGE 默认错误消息映射

uni-app 额外导出 IMSDKError;uni-app x 导出 IMSDKFailIMSDKErrorsUniErrorSubjectimError 及全部公开 UTS 类型。

实例 API

实例属性:

  • role:固定角色,值为 userstaff
  • state:包含 totalUnreadauthStatusdefaultAvatarthemeColor
  • requestauthsessionmessagewebsocket:子模块 API。

实例方法:

  • configure(configPatch):合并更新配置并返回当前实例。
  • setRole(options?):重新应用固定角色;reconnect=true 时重连,不改变角色。
  • ensureRoleAuthorized(loginInfo?, options?):恢复会话或重新鉴权。
  • login(loginInfo):主动鉴权并返回公开用户对象。
  • logout():关闭连接并清理当前角色本地会话。
  • connectWebSocket(userId, phone):发起连接。
  • getUserInfo()getToken()getConfig()getCore()
  • onStateChange(listener):立即回调当前状态,持续通知后续变化,并返回取消订阅函数。
  • subscribeState(listener):与 onStateChange 等价;uni-app x 应使用 onStateChange, 以符合原生端持续回调的保活规则。

uni-app x 应优先使用本节列出的实例根方法;根方法与对应子模块共享同一状态和实现, 是 App-iOS 原生代理的稳定调用形式。传统 uni-app 可以继续使用 sdk.authsdk.sessionsdk.messagesdk.websocket

themeColor 会同步进入实例状态,并用于本人消息气泡与气泡尾、发送按钮、附件 抽屉取消文字、重试按钮和加载指示器。已读、未读、角色等业务语义色不跟随主题色 变化。themeColorLightthemeColorDark 保留在配置中,当前组件不自动消费。

请求模块

sdk.request 提供 requestgetpostputdeleteuploaddownload。普通请求与下载会自动添加签名、当前角色鉴权和已配置的 site-id。 上传使用 upload({ url, filePath, name?, formData?, headers?, timeout? })name 是 multipart 文件字段名,formData 是附加表单字段,headers 是上传接口专用请求头, timeout 单位为毫秒。未传选项时不附加自定义请求头。

鉴权模块

sdk.auth 提供:

  • login(loginInfo)
  • getUserInfo(phone, friendType)
  • getUserInfoBySessionId(sessionId)

对应根方法为 logingetRemoteUserInfogetRemoteUserInfoBySession

会话模块

sdk.session 提供:

  • getChatConfig()
  • getSessionList(page?, limit?, options?)
  • clearUnread(sessionRecordId)
  • closeSession({ session_id })
  • getTotalUnreadCount()
  • syncUnreadCount(options?)
  • getCachedTotalUnread()
  • onUnreadChange(listener, options?):注册监听并返回取消订阅函数。

上述方法在实例根级同名提供。

员工端优先使用服务端总未读接口;用户端按 max(0, count - read_count) 聚合会话 未读。第 1 页替换会话缓存,后续页按会话 ID 合并。

消息模块

sdk.message 提供:

  • getMessages(query?)
  • sendTextMessage(sessionId, content, options?)
  • sendImageMessage(sessionId, imageURL, options?)
  • sendVideoMessage(sessionId, videoURL, options?)
  • sendLocationMessage(sessionId, location, options?)
  • uploadImage(filePath, options?)
  • uploadVideo(filePath, options?)
  • readMessage(sessionId, options?)
  • onMessage(listener):注册监听并返回取消订阅函数。

上述方法在实例根级同名提供。

uploadImageuploadVideooptions 使用“请求模块”定义的上传选项,不需要传 urlfilePath;还可传 fileSize(字节),用于跨平台上传前大小校验。

const imageURL = await sdk.uploadImage(filePath, {
  formData: { scene: 'chat' },
})

图片上限为 5MB,视频上限为 50MB。getMessages 优先按 session_id 查询;平台 客服可使用 customerOnly=truephone。尚未建立会话时,发送选项必须提供有效 receive_idfriend_type。消息字段保持后端 snake_case,例如 session_idsend_idreceive_idfriend_typemsg_typeis_readcreate_time

WebSocket 模块

sdk.websocket 提供:

  • connect(force?, userId?, phone?)
  • close()
  • waitForConnection(timeout?)
  • isConnected()
  • onMessage(listener):注册监听并返回取消订阅函数。

需要等待连接结果时,实例根级提供 openWebSocket(force?, userId?, phone?)。鉴权成功后 SDK 仍会自动连接,无需重复调用。

SDK 在鉴权成功后自动连接,异常断线按配置退避重连,并发送握手和心跳。连接层只 使用真实、非零服务端消息 ID 去重;缺少有效 ID 时生成本地 ID。type=1 表示可 展示消息,type=2 表示已读回执;msg_type 表示消息内容类型。当前会话收到消息 后会上报已读,自己的消息回显不会增加未读数。

错误契约

uni-app 抛出 IMSDKError,uni-app x 抛出实现 IMSDKFail 的错误。两端都提供:

  • errSubject:固定为 jszn-im-sdk
  • errCode:稳定错误码。
  • errMsg:适合展示或记录的错误消息。
错误码 常量 含义
9401001 CONFIG_INVALID 配置或调用参数不完整
9401002 LOGIN_INVALID 登录资料或鉴权状态无效
9401003 NETWORK_ERROR 网络、上传或下载失败
9401004 RESPONSE_ERROR HTTP 或业务响应不符合契约
9401005 SOCKET_ERROR WebSocket 未连接或发送失败

平台支持

插件支持 uni-app 与 uni-app x 官方支持的客户端平台:

  • uni-app:Vue 2、Vue 3、Web、Android、iOS、HarmonyOS、小程序和快应用。
  • uni-app x:Web、Android、iOS、HarmonyOS 和微信小程序。

小程序支持微信、支付宝、抖音、百度、快手、京东、QQ、飞书、小红书及鸿蒙元 服务;快应用支持华为与联盟快应用。

安全与隐私

  • 登录资料、鉴权信息、会话和消息只发送到宿主配置的 API 与 WebSocket 服务。
  • 图片、视频和位置仅在用户主动选择对应发送功能时上传到宿主配置的服务。
  • 宿主必须为相机、相册、定位、网络域名和 WebSocket 域名配置目标平台权限、隐私 声明与白名单。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

发送图片或视频时可能使用相机和相册权限;发送位置时使用定位权限。仅在用户主动选择对应功能时调用。

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件将登录资料、鉴权信息、会话、消息及用户主动选择的图片、视频和位置发送到接入方配置的 API、资源和 WebSocket 服务,用于即时通讯。插件自身不内置第三方统计或广告服务。

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

免费商用受限许可证

Copyright © JSZN. All rights reserved.

授权范围

在遵守本许可证的前提下,任何个人或组织均可免费:

  • 在自有或受托开发的应用中使用本插件,包括商业项目。
  • 为适配具体项目修改本插件源码。
  • 将本插件随最终应用的编译产物一并发布和交付。

限制

未经版权所有者书面许可,不得:

  • 单独出售、出租、转授权或以付费服务形式提供本插件及其修改版本。
  • 在插件市场、代码托管平台、网盘或其他渠道公开分发本插件源码及其修改版本。
  • 更换名称、作者或版权信息后,将本插件或其主要代码重新发布为独立插件、SDK 或组件库。
  • 删除或隐瞒本许可证、版权声明及原作者信息。
  • 使用 JSZN 名称、商标或标识暗示版权所有者对衍生产品提供认可或担保。

向项目委托方交付包含本插件源码的完整项目时,委托方仅取得该项目范围内的使用和修改 权,不因此取得单独分发、转售或再授权本插件的权利,并应继续保留本许可证。

免责声明

本插件按“现状”提供,不附带任何明示或默示担保,包括但不限于适销性、特定用途适用性 和不侵权担保。在适用法律允许的最大范围内,版权所有者不对使用或无法使用本插件产生的 任何直接、间接、附带、特殊或后果性损失承担责任。

本许可证未明确授予的权利均由版权所有者保留。超出上述范围的使用需取得版权所有者书面 授权。

暂无用户评论。