提交 ce2ad049 编写于 作者: DCloud_JSON's avatar DCloud_JSON

1.2.1 修复 某些情况下 非uniCloud 开发的项目 接入uni-im 登录后会话列表不更新的问题

上级 6ccbab61
......@@ -10,4 +10,5 @@ uni_modules.config.json
vite.config.js
vue.config.js
.DS_Store
!/uni_modules/uni-im/node_modules/blueimp-md5/
\ No newline at end of file
!/uni_modules/uni-im/node_modules/blueimp-md5/
!/uni_modules/uni-im/node_modules/blueimp-md5
\ No newline at end of file
......@@ -9,4 +9,5 @@ uni_modules.config.json
/cloud_function_publish
vite.config.js
vue.config.js
!/uni_modules/uni-im/node_modules/blueimp-md5/
\ No newline at end of file
!/uni_modules/uni-im/node_modules/blueimp-md5/
!/uni_modules/uni-im/node_modules/blueimp-md5
\ No newline at end of file
<script>
import uniIdPagesinit from '@/uni_modules/uni-id-pages/init.js';
//1. 导入uniIm的Utils工具类
<script>
//1. 导入uni身份信息管理模块
import uniIdPagesInit from '@/uni_modules/uni-id-pages/init.js';
//2. 导入uniIm的Utils工具类
import uniImUtils from '@/uni_modules/uni-im/common/utils.js';
export default {
globalData: {
//2. 初始化uniIm全局变量
uniIm: {
msgManagers: {},
//app是否显示在前台
appIsShow: false
}
},
onLaunch: async function() {
console.log('App Launch');
uniIdPagesinit();
//3.初始化uniIm
//3. 初始化uni身份信息管理模块
uniIdPagesInit();
//4. 初始化uniIm
uniImUtils.init();
},
onShow: function() {
// 4.在onShow生命周期,更改全局变量中app是否显示在前台为true
this.globalData.uniIm.appIsShow = true;
console.log('App Show');
// 5.清理系统通知栏消息和app角标
uniImUtils.clearPushNotify()
},
onHide: function() {
//6.在onHide生命周期,更改全局变量中app是否显示在前台为false
this.globalData.uniIm.appIsShow = false;
console.log('App Hide');
}
};
</script>
<style>
/*每个页面公共css */
/* #ifndef APP-NVUE */
@import "@/uni_modules/uni-im/static/iconfont.css";
/* #endif */
/*每个页面公共css */
</style>
> 本插件需要HBuilderX 3.6.9 及其以上版本支持
> 本插件APP端需要HBuilderX 3.6.9 及其以上版本支持,微信小程序和网页端需要HBuilderX 3.6.4 及其以上版本支持。
# 简介
uni-im是云端一体的、全平台的、免费的、开源即时通讯系统。
......@@ -54,46 +54,30 @@ uni-im是云端一体的、全平台的、免费的、开源即时通讯系统
## 部署到自己的项目
1. 打开`uni-im`插件下载地址:[https://ext.dcloud.net.cn/plugin?name=uni-im](https://ext.dcloud.net.cn/plugin?name=uni-im)
2. 点击`使用HBuilderX导入插件`,选择你的项目,点击确定(同时会自动导入依赖的uni_modules`uni-id-pages`)按提示操作自动配置`pages.json`
3. 打开项目根目录的App.vue文件,进行如下操作:
- 导入uniIm的Utils
- 初始化uniIm
- 在globalData中添加预置数据
- 在onShow和onHide生命周期更改app是否显示在前台的值
3. 打开项目根目录的App.vue文件,初始化uni-id-pages和uniIm模块
示例如下:
```html
<script>
import uniIdPagesinit from '@/uni_modules/uni-id-pages/init.js';
//1. 导入uniIm的Utils工具类
//1. 导入uni身份信息管理模块
import uniIdPagesInit from '@/uni_modules/uni-id-pages/init.js';
//2. 导入uniIm的Utils工具类
import uniImUtils from '@/uni_modules/uni-im/common/utils.js';
export default {
globalData:{
//2. 初始化uniIm全局变量
uniIm:{
msgManagers:{},
//app是否显示在前台
appIsShow:false
}
},
onLaunch: function() {
onLaunch: async function() {
console.log('App Launch');
uniIdPagesinit();
//3.初始化uniIm
//3. 初始化uni身份信息管理模块
uniIdPagesInit();
//4. 初始化uniIm
uniImUtils.init();
},
onShow: function() {
//4.在onShow生命周期,更改全局变量中app是否显示在前台为true
this.globalData.uniIm.appIsShow = true
//5.清理系统通知栏消息和app角标
uniImUtils.clearPushNotify()
console.log('App Show')
console.log('App Show');
},
onHide: function() {
//6.在onHide生命周期,更改全局变量中app是否显示在前台为false
this.globalData.uniIm.appIsShow = false
console.log('App Hide')
console.log('App Hide');
}
}
};
</script>
```
......@@ -152,41 +136,54 @@ uni-im是云端一体的、全平台的、免费的、开源即时通讯系统
export default store
```
5. 账号登录;用户体系基于不同技术实现的项目,登录要求不同:
- 基于`uni-id-pages`开发的项目,直接登录即可。
- 基于老版uni-id(版本号:3.x) 开发的项目,需要如下改造:
1. 在登录成功和token续期后,绑定当前账号与设备推送标识的关联关系。示例代码:
```js
const uniIdCo = uniCloud.importObject("uni-id-co", {customUI: true})
uni.getPushClientId({
success: async function(e) {
console.log(e)
let pushClientId = e.cid
let res = await uniIdCo.setPushCid({
pushClientId
})
console.log('getPushClientId', res);
},
fail(e) {
console.error(e)
}
5. 账号打通
uni-im经常用于嵌入其他App中,成为其中的一个模块。比如客服模块。这就涉及现有应用和uni-im的账户打通问题。
uni-im的账户体系是uni-id的。如果开发者的现有应用要接入uni-im,但账户体系并不在uni-id里,或使用的是老版uni-id,就需要参考本章节打通账户。
- 基于`uni-id-pages`的项目,直接登录即可,无需额外打通工作。
- 基于`uni-id-co`的项目,需要在登录成功和用户信息更新时,同步更新uniId store内的当前用户信息(uni-im显示当前用户头像、昵称时会用到)示例代码:
```js
//导入uniCloud客户端账户体系,用户信息状态管理模块
import {mutations as uniIdMutations} from '@/uni_modules/uni-id-pages/common/store.js';
await uniIdMutations.updateUserInfo()
```
- 基于老版uni-id(版本号:3.x) 开发的项目,需要如下改造:
1. 在登录成功和token续期后,绑定当前账号与设备推送标识的关联关系。示例代码:
```js
const uniIdCo = uniCloud.importObject("uni-id-co", {customUI: true})
uni.getPushClientId({
success: async function(e) {
console.log(e)
let pushClientId = e.cid
let res = await uniIdCo.setPushCid({
pushClientId
})
```
2. 在登录成功和用户信息更新时,同步更新uniId store内的当前用户信息(uni-im显示当前用户头像、昵称时会用到)示例代码:
```js
//导入uniCloud客户端账户体系,用户信息状态管理模块
import {mutations as uniIdMutations} from '@/uni_modules/uni-id-pages/common/store.js';
await uniIdMutations.updateUserInfo()
```
- 基于非uniCloud开发的项目(比如:应用服务端的开发语言是php、java、go、c#等,用户信息并没未存储在uniCloud云数据库中)需要通过uni-im-co的loginWithJWT方法实现登录[详情查看](#uniImCoLoginWithJWT)
6. 接下来打开,“用户列表页”(路径:`/uni_modules/uni-im/pages/userList/userList`)可以看到所有的注册用户。
console.log('getPushClientId', res);
},
fail(e) {
console.error(e)
}
})
```
2. 在登录成功和用户信息更新时,同步更新uniId store内的当前用户信息(uni-im显示当前用户头像、昵称时会用到)示例代码:
```js
//导入uniCloud客户端账户体系,用户信息状态管理模块
import {mutations as uniIdMutations} from '@/uni_modules/uni-id-pages/common/store.js';
await uniIdMutations.updateUserInfo()
```
- 客户端是uni-app的,但服务器不是uniCloud的情况。需开通uniCloud,然后在客户端通过uni-im-co的loginWithJWT方法实现联登,因内容较多,需另见[文档](#uniImCoLoginWithJWT)
- 客户端如果不是uni-app的,如果是网页,可iframe内嵌。如果是app,可嵌入[uni小程序sdk](https://nativesupport.dcloud.net.cn/README)
6. 确保账户对接后,打开“用户列表页”(路径:`/uni_modules/uni-im/pages/userList/userList`)可以看到所有的注册用户。
7. 点击某个用户,会自动创建与该用户的会话,并打开“聊天对话页”(路径:`/uni_modules/uni-im/pages/chat/chat`),然后就可以开始聊天了。
8. 还可以导入uni-im的示例项目作为管理员端与用户聊天。
9. 如果你是2个不同appId的应用相互通讯(比如:淘宝的买家端和卖家端通讯)的场景,请打开聊天对话文件(路径:`/uni_modules/uni-im/pages/chat/chat`)搜索`data.appId = this.systemInfo.appId`修改`this.systemInfo.appId`为相对的appId
**补充:**(基于uni-id-pages开发的项目可忽略)
**补充:**(基于uni-id-pages开发的项目可忽略)
为了实现用户退出登录后,不再收到im消息,需要在执行退出登录时同步状态给uni-id-pages。示例代码如下:
```js
import {mutations as uniIdMutations} from '@/uni_modules/uni-id-pages/common/store.js'
......@@ -201,13 +198,20 @@ uniIdMutations.logout()
逻辑代码如下:
```js
uni.navigateTo({
url:'/uni_modules/uni-im/pages/chat/chat?user_id' + 对应的用户id
url:'/uni_modules/uni-im/pages/chat/chat?user_id=' + 对应的用户id
})
```
- 服务端限制
打开`uni-im-co` 路径:`/uni_modules/uni-im/uniCloud/cloudfunctions/uni-im-co/index.obj.js`
配置第一行 `admin_user_id`的值为管理员客服id。如果会话双方均不属于此域则无法通讯
1. 添加`uni-im`配置文件,打开:`/uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/`;新建`uni-im`文件夹和`config.json`文件,示例如下:
```json
{
"admin_uid":false
}
```
2. 配置`admin_uid`的值为管理员客服的user_id(支持多个以数组的形式指定),如果会话双方均不属于此域则无法通讯。不配置或为false则表示不限制。
# 开发文档
## 目录结构
......@@ -248,9 +252,9 @@ uni-im v1.0.0 暂时比较简单,云端有1个云对象`uni-im-co`,2个opend
名词解释
- 聊天会话ID
根据通讯双方用户id,生成唯一索引;通过该索引值,查询双方的聊天记录等信息。
根据通讯双方用户id,生成的唯一索引值;
- 聊天会话
以会话ID为索引的一组数据,记录:未读消息数量、会话类型、所属用户id、对话的用户id、对话的群id、未读消息数量、最后一条消息概述(文本消息的前15个字,消息为多媒体时只描述类型)、最后一条消息时间
以会话ID为索引的一组数据,记录:未读消息数量、会话更新时间、会话类型、会话所属用户的id、对话的用户id、对话的群id、最后一条消息概述(文本消息的前15个字,消息为多媒体时只描述类型)
## uni-im-co 云函数(云对象)
### API列表
......@@ -262,8 +266,15 @@ uni-im v1.0.0 暂时比较简单,云端有1个云对象`uni-im-co`,2个opend
#### 账号登录loginWithJWT@uniImCoLoginWithJWT
如果你的项目是基于非uniCloud开发的项目(比如:应用服务端的开发语言是php、java、go、c#等,用户信息并没未存储在uniCloud云数据库中)需要通过
基于跨平台签名认证的方式,向uniCloud账户体系新增用户(创建过则更新用户信息)并获取token实现登录的方法。
如果你的项目是基于非uniCloud开发的项目(比如:应用服务端的开发语言是php、java等,用户信息并没未存储在uniCloud云数据库中)需要通过跨平台签名认证的方式,向uniCloud账户体系新增用户(创建过则更新用户信息)并获取token实现登录。
前置要求:添加`uni-im`配置文件,打开:`/uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/`;新建`uni-im`文件夹和`config.json`文件,示例如下:
```json
{
"jwtSecret":"jwtSecretDemo",
}
```
这里的值`jwtSecretDemo`为示例,注意修改为自己的,使用一个较长的字符串即可(越长安全性越高,建议大于32位)。
**接口形式**
......@@ -341,6 +352,8 @@ try{
**示例代码:**
以下为客户端的示例,演示了在客户端中登录之前的账户体系成功后,把signedData传给uni-im,调用uni-im的联登接口。
```js
// 1. 导入uniCloud客户端账户体系,用户信息状态管理模块
import {
......@@ -633,4 +646,4 @@ d) 如您违反本许可协议,需承担因此给DCloud造成的损失。
根据发展,DCloud可能会对本协议进行修改。修改时,DCloud会在产品或者网页中显著的位置发布相关信息以便及时通知到用户。如果您选择继续使用本框架,即表示您同意接受这些修改。
条款结束
\ No newline at end of file
条款结束
......@@ -8,9 +8,9 @@
// 数据库查询有最大返回条数限制,详见:https://uniapp.dcloud.net.cn/uniCloud/cf-database?id=limit
// 详细JQL语法,请参考 https://uniapp.dcloud.net.cn/uniCloud/clientdb?id=jsquery
db.collection('uni-im-msg').where({
conversation_id:"single_5ecba2763bd858474c014870965d93c5"
}).get()
db.collection('uni-im-conversation').where({
id: "single_1ba6a8117d7706a8f26204cf87245871"
}).remove()
// 下面示例查询uni-id-users表的所有数据
......
{
"passwordSecret": "passwordSecret-demo",
"tokenSecret": "passwordSecret-demo",
"passwordStrength": "medium",
"passwordStrength": "",
"tokenExpiresIn": 7200,
"tokenExpiresThreshold": 600,
"passwordErrorLimit": 6,
......
......@@ -52,7 +52,7 @@
watch: {
src:{
handler(src) {
if(src.includes('://')){
if(src.substr(0,8) == 'cloud://'){
uniCloud.getTempFileURL({
fileList: [src]
}).then(res=>{
......
......@@ -52,5 +52,5 @@ export default {
* weak(弱:密码必须包含字母和数字,长度范围:6-16位之间)
* 为空或false则不验证密码强度
*/
"passwordStrength":"medium"
"passwordStrength":""
}
## 1.1.0(2022-11-17)
新增 提供 非uniCloud(比如:应用服务端的开发语言是php、java、go、c#、python等)或 基于老版uni-id 开发的项目 接入uni-im的支持
## 1.2.1(2022-11-25)
- 修复 某些情况下 非uniCloud 开发的项目 接入uni-im 登录后会话列表不更新的问题
## 1.2.0(2022-11-23)
- 【重要】全端支持Vue3
- 修复 当历史消息超长时,APP端键盘弹起,不能滚动到最后一条消息
- 修复 键盘收起时,会自动滚动到最后一条消息的问题
- 修复 部分情况下,切换登录的账号,会话列表没有更新的问题
## 1.1.2(2022-11-21)
修复 某些情况下 iOS端 输入框内容发生变化时 页面重新排版,导致输入框被键盘挡住的问题
## 1.1.1(2022-11-18)
修复 向长时间未登录的用户(push_clientid已过期)发送消息,引起的报错问题。将数据写入云数据库,当用户再次登录时从服务端拉取
## 1.1.0(2022-11-18)
- 新增 支持 非uniCloud(比如:应用服务端的开发语言是php、java、go、c#、python等)或 不基于uni-id-pages 开发的项目 接入uni-im
- 简化部署流程 app.vue 页面仅需init uni-im即可(更加模块化,内部:监听应用生命周期onShow、onHidden实现相关功能、初始化依赖的globalData等)
## 1.0.3(2022-11-15)
降低uni-im使用的HBuilderX版本为`3.6.4`。 注意**APP端**:仅支持Vue2,且HBuilderX的版本为3.6.9+,否则chat页面存在滚动锚定问题(后续会修复此问题)
## 1.0.2(2022-11-14)
......
......@@ -58,11 +58,6 @@ export default {
if(item.chatText && state.currentConversationId == key){
item.update_time = Date.now()
}
if(!item.update_time){
// 插件市场项目补丁 时间字段错误
item.update_time = item.last_time
}
conversationList.push(item)
}
conversationList = conversationList.sort(function(a, b) {
......@@ -87,6 +82,9 @@ export default {
},
mergeConversationDatas(state, val) {
state.conversationDatas = Object.assign({},state.conversationDatas, val)
},
clearConversationDatas(state){
state.conversationDatas = {}
},
updateConversation(state, [id, data, cover = false]) {
let conversationDatas = state.conversationDatas
......@@ -168,15 +166,21 @@ export default {
}).then(e => {
console.log('设置为已读', e.result.updated);
})
},
async initConversationData(context){
getConversationDatasPage = 0
getApp().globalData.uniIm = {"msgManagers":{}}
await context.commit('clearConversationDatas')
await context.dispatch('loadMoreConversation')
},
async loadMoreConversation(context,conversation_id) {
const uniImCo = uniCloud.importObject("uni-im-co")
const uniImCo = uniCloud.importObject("uni-im-co",{customUI:true})
let res = await uniImCo.getConversationList({
page: getConversationDatasPage,
limit: this.limit,
limit: 30,
conversation_id
})
// console.log('loadMoreConversation',conversation_id,res.data);
// console.log('loadMoreConversation',{conversation_id,res});
if(res.data.length){
getConversationDatasPage++
//查到会话列表数据后转化格式
......
// #ifdef APP
let {uniCompileVersion} = uni.getSystemInfoSync()
let versionArr = uniCompileVersion.split('.').join('')
if(
versionArr[0] < 3 ||
(versionArr[0] == 3 && versionArr[1] < 6) ||
(versionArr[0] == 3 && versionArr[1] == 6 && versionArr[2] < 9)
){
uni.showModal({
content: 'APP端uni-im需要HBuilderX 3.6.9以上版本,否则chat页面会存在滚动锚定问题',
showCancel: false
});
}
// #endif
// #ifdef VUE3 && APP-NVUE
uni.showModal({
content: 'uni-im APP端暂不支持Vue3,否则chat页面会存在滚动锚定问题',
showCancel: false
});
// #ifdef VUE3
import {onShow} from '@dcloudio/uni-app'
import {onHide} from '@dcloudio/uni-app'
// #endif
import md5 from '@/uni_modules/uni-im/node_modules/blueimp-md5'
import $store from '@/store/index.js'
import formatTime from '@/uni_modules/uni-im/common/formatTime';
let appIsShow = true;
export default {
init(){
//目前功能还比较简单,只有启用监听聊天数据和时间戳心跳(定时器)
setTimeout(()=>{
// 初始化uniIm依赖的全局变量
getApp().globalData.uniIm = {"msgManagers":{}}
},0)
//监听im消息
this.listenImMsg()
//时间戳心跳(定时器)用于刷新:消息或会话与当前的时间差
setInterval(()=>{
$store.commit('uniIm/updateHeartbeat')
},1000)
// #ifdef APP
//提示兼容性问题
showTip()
const appOnShow = ()=>{
appIsShow = true
//清理系统通知栏消息和app角标
this.clearPushNotify()
}
function appOnHide(){
appIsShow = false
}
// #ifdef VUE2
getApp().$vm.$on('hook:onShow',appOnShow)
getApp().$vm.$on('hook:onHide',appOnHide)
// #endif
// #ifdef VUE3
onShow(appOnShow,getApp().$vm.$)
onHide(appOnHide,getApp().$vm.$)
// #endif
// #endif
},
getConversationId(param, type = 'single') { //single,group
if (type == 'single') {
......@@ -61,8 +72,7 @@ export default {
let currentPages = getCurrentPages()
let topViewRoute = currentPages[currentPages.length-1].route
console.log('topViewRoute',topViewRoute);
let isShow = getApp().globalData.uniIm.appIsShow
if( !isShow || !['uni_modules/uni-im/pages/index/index','uni_modules/uni-im/pages/chat/chat'].includes(topViewRoute) ){
if( !appIsShow || !['uni_modules/uni-im/pages/index/index','uni_modules/uni-im/pages/chat/chat'].includes(topViewRoute) ){
console.log('payload',payload);
let {content,data,title,avatar_file} = payload
let url = avatar_file?avatar_file.url: ''
......@@ -115,3 +125,19 @@ export default {
// #endif
}
}
function showTip(){
// #ifdef APP
let {uniCompileVersion} = uni.getSystemInfoSync()
let version = uniCompileVersion.split('.').reduce((prev,cur,index,arr)=>prev+cur*Math.pow(1000,arr.length-index),0)
// console.log('version',version);
if(
version < 3006009000
){
uni.showModal({
content: 'APP端uni-im需要HBuilderX 3.6.9以上版本,否则chat页面会存在滚动锚定问题',
showCancel: false
});
}
// #endif
}
\ No newline at end of file
......@@ -22,7 +22,7 @@
<!-- #ifndef APP-NVUE -->
<view class="text-box" :class="msgClass">
<template v-if="msg.body == htmlString">
<text class="msg-text" v-text="msg.body"></text>
<text class="msg-text">{{msg.body}}</text>
</template>
<template v-else>
<template v-for="(item,index) in nodes">
......@@ -371,6 +371,7 @@
.link{
// font-size: 16px;
color: #007fff;
cursor: pointer;
}
.self-text-box {
......
{
"id": "uni-im",
"displayName": "uni-im",
"version": "1.0.3",
"version": "1.2.1",
"description": "uni-im是云端一体的、全平台的、免费的、开源即时通讯系统",
"keywords": [
"uni-im,即时通讯,客服,聊天"
......@@ -32,7 +32,8 @@
},
"uni_modules": {
"dependencies": [
"uni-id-pages"
"uni-id-pages",
"uni-config-center"
],
"encrypt": [],
"platforms": {
......
<template>
<view class="page">
<!-- #ifndef APP-NVUE -->
<!-- 非nvue端 左上角显示未读消息数 ,nvue端setTitleNViewButton-->
<!-- #ifdef H5 -->
<!-- H5端 左上角显示未读消息数 ,nvue端setTitleNViewButton-->
<view @click="tapUnreadCount" class="unread_count" v-if="unread_count != 0">
{{ unread_count > 99 ? '99+' : unread_count }}
</view>
......@@ -9,9 +9,8 @@
<view class="msg-list-box">
<!-- 消息列表 -->
<uni-list ref="msg-list" class="msg-list" @touchstart="touchList" :show-scrollbar="false" :border="false"
render-reverse>
<uni-list-item class="item" v-for="(msg, index) in msgList" :key="msg.id" :border="false"
<uni-list ref="msg-list" class="msg-list" @touchstart="touchList" :show-scrollbar="false" :border="false" :render-reverse="true">
<uni-list-item class="item" v-for="(msg, index) in msgList" :key="msg.id" :border="false" :keep-scroll-position="true"
:customStyle='{"padding":0,"backgroundColor":"transparent"}'>
<template v-slot:body>
<view>
......@@ -30,16 +29,14 @@
</uni-list-item>
<!-- #ifdef APP-NVUE -->
<cell v-if="systemInfo.platform == 'android' || msgList.length" keep-scroll-position
render-reverse-position>
<cell v-if="systemInfo.platform == 'android' || msgList.length" :keep-scroll-position="true" :render-reverse-position="true">
<!-- <text ref="msg-item" class="cell">这是最底部的一条</text> -->
</cell>
<!-- #endif -->
<!-- #ifndef APP-NVUE -->
<!-- 非nvue端(是上下颠倒的),分页加载历史聊天数据 -->
<uni-list-item v-if="msgList.length" :border="false"
:customStyle='{"padding":0,"backgroundColor":"transparent"}'>>
<uni-list-item v-if="msgList.length" :border="false" :customStyle='{"padding":0,"backgroundColor":"transparent"}'>>
<template v-slot:body>
<text v-if="isSafariPc" @click="loadMore"
class="loadMore">{{ hasMore ? '点击加载更多' : '没有更多历史消息' }}</text>
......@@ -62,7 +59,7 @@
<view class="tool-bar">
<uni-icons @click="sendImg" size="26px" type="image"></uni-icons>
</view>
<textarea maxlength="400" v-model="chatText"></textarea>
<textarea maxlength="250" v-model="chatText"></textarea>
</view>
<template v-else>
<!-- 切换为语音模式 -->
......@@ -70,7 +67,7 @@
<view class="textarea-box">
<textarea v-model="chatText" @input="input" @confirm="beforeSendMsg" @linechange="linechange"
:style="{ height: textareaHeight + 'px' }" disable-default-padding hold-keyboard confirm-hold
auto-blur confirm-type="send" :show-confirm-bar="false" :cursor-spacing="40" maxlength="400"
auto-blur confirm-type="send" :show-confirm-bar="false" :cursor-spacing="20" maxlength="250"
class="textarea" ref="input-box" />
</view>
<!-- prevent实现,移动端 点击发送按钮,软键盘不自动收起 -->
......@@ -216,17 +213,8 @@
text: unread_count == 0 ? '' : unread_count,
background: '#E5E5E5'
});
},
// #endif
currentConversation(data) {
// console.log(11231133123213,data);
if (data && data.title) {
//设置标题栏文字
uni.setNavigationBarTitle({
title: data.title
});
}
}
// #endif
},
beforeCreate() {
// #ifdef H5
......@@ -267,8 +255,10 @@
// 监听键盘高度变化显示最后一条消息
uni.onKeyboardHeightChange(({
height
}) => {
this.showLast(0);
}) => {
if(height){
this.showLast(0);
}
});
// #endif
......@@ -358,9 +348,9 @@
let text = clipboardData.getData('text');
if (text) {
console.log(text);
if (text.length > 400) {
if (text.length > 250) {
uni.showModal({
content: '你粘贴的文本长度超过400,将被截断。',
content: '你粘贴的文本长度超过250,将被截断。',
complete: e => {
if (!e.confirm) {
setTimeout(() => {
......@@ -447,7 +437,7 @@
oldScrollTop = e.scrollTop;
},
async onLoad(e) {
// console.log('onLoad', e, e.conversation_id,this.msgList.length);
//console.log('onLoad', e, e.conversation_id,this.msgList.length);
if (!e.conversation_id && e.user_id) {
e.conversation_id = uniImUtils.getConversationId(e.user_id)
if(!this.conversation(e.conversation_id)){
......@@ -466,7 +456,7 @@
//设置当前会话id
//设置全局的app当前正在聊天的会话id
this.setCurrentConversationId(conversation_id);
this.setCurrentConversationId(conversation_id);
this.conversation_id = conversation_id;
if (!this.currentConversation.id) {
......@@ -477,7 +467,10 @@
icon: 'none'
});
}
}
}
uni.setNavigationBarTitle({
title: this.currentConversation.title
});
//清除未读数
if (this.currentConversation.unread_count) {
this.clearUnreadCount(this.conversation_id);
......@@ -809,7 +802,7 @@
// console.log('滚动到', this.msgList[this.msgList.length - 1].body);
dom.scrollToElement(target, {
animated: duration != 0,
offset: 500
offset: 999999
});
// #endif
......@@ -963,7 +956,13 @@
/* #ifdef H5 */
margin-top: 45px;
/* #endif */
}
}
/* #ifdef MP */
.msg-list-box ::v-deep .uni-list{
background-color: transparent;
}
/* #endif */
/* #ifndef APP */
@media screen and (max-width: 960px) {
......
<template>
<view id="page">
<view id="page">
<!-- <uni-list>
<uni-list-item title="新的朋友" thumb="/static/im-conversation/add.jpg" to="./add/add"></uni-list-item>
</uni-list> -->
......@@ -30,22 +30,22 @@
<!-- #endif -->
<!-- #ifdef H5 -->
<scroll-view id="user-list-box" scroll-y="true" @scrolltolower="loadMore">
<!-- #endif -->
<!-- 会话用户列表 -->
<scroll-view id="user-list-box" scroll-y="true" @scrolltolower="loadMore()">
<!-- #endif -->
<!-- 会话用户列表 -->
<uni-list class="user-list" :style="{'height':wHeight,'width':'750rpx'}">
<uni-list-chat v-for="(item,index) in conversationList" :key="item.id"
:showBadge="item.unread_count>0" :badgeText="item.unread_count" link :title="item.title"
class="item" :class="{active:currentConversationId==item.id}" :note="item.last_msg_note"
:avatar="item.avatar_file ? item.avatar_file.url : '/uni_modules/uni-im/static/avatarUrl.png'"
:avatar="item.avatar_file&&item.avatar_file.url ? item.avatar_file.url : '/uni_modules/uni-im/static/avatarUrl.png'"
@click="toChat(item.id)" direction="column" :time="formatTime(item.update_time)">
</uni-list-chat>
<!-- #ifdef APP-NVUE -->
<!-- nvue端appear(元素一旦显示在可视窗口中)就触发加载更多。-->
<cell v-if="conversationList.length" @appear="loadMore"></cell>
<cell v-if="conversationList.length" @appear="loadMore()"></cell>
<!-- #endif -->
</uni-list>
<!-- #ifdef H5 -->
<!-- #ifdef H5 -->
</scroll-view>
<!-- #endif -->
</view>
......@@ -58,10 +58,9 @@
{{conversationDatas[currentConversationId].title}}
</block>
</view>
<scroll-view id="chat-view" :scroll-into-view="scrollIntoView" :scroll-top="scrollTop"
<scroll-view id="chat-view" :scroll-top="scrollTop"
@scroll="onScroll" scroll-y :lower-threshold="100">
<chat-view ref="chat-view" @setScroll="scrollTop = $event"></chat-view>
<text id="last-chat-view"></text>
</scroll-view>
</view>
</match-media>
......@@ -93,14 +92,14 @@
// #endif
watch: {
isWidescreen(isWidescreen) {
this.setStyle()
if(isWidescreen){
uni.hideTabBar()
}else{
uni.showTabBar()
this.setStyle()
if (isWidescreen) {
uni.hideTabBar()
} else {
uni.showTabBar()
}
}
},
},
computed: {
...mapState({
//当前正在对话端用户ID,实现pc端高亮效果
......@@ -111,9 +110,9 @@
...mapGetters({
conversationList: 'uniIm/conversationList',
unread_count: 'uniIm/unread_count'
}),
currentUserInfo(){
return uniIdStore.userInfo
}),
currentUserInfo() {
return uniIdStore.userInfo
},
avatarUrl() {
if (this.currentUserInfo.avatar_file && this.currentUserInfo.avatar_file.url) {
......@@ -127,8 +126,7 @@
wHeight: 'auto',
isWidescreen: false,
userInfo: {},
scrollTop: 0,
scrollIntoView: ""
scrollTop: 0
};
},
async onShow() {
......@@ -136,8 +134,7 @@
this.setStyle()
// #endif
},
beforeCreate() {
},
beforeCreate() {},
created() {
// #ifdef H5
const mediaQueryOb = uni.createMediaQueryObserver(this)
......@@ -151,126 +148,134 @@
// #ifdef APP-NVUE
this.wHeight = uni.getSystemInfoSync().windowHeight + 'px'
// #endif
// #ifdef H5
uni.$on('uni-im-showLast', e => {
// console.log('uni-im-showLast');
// this.scrollTop = 100
this.scrollIntoView = ''
this.$nextTick(e => {
this.scrollIntoView = 'last-chat-view'
})
this.scrollTop = document.querySelector('#chat-view .uni-scroll-view-content').scrollHeight
})
// #endif
},
async onLoad({
token,
user_id,
goods,
conversation_id
}) {
// 发布新版本后,清理旧版本下的storage避免脏数据引发问题
let version = '2022.11.11.01'
if(uni.getStorageSync('uni-im-storage-version') != version){
let data = uni.getStorageInfoSync();
console.log('data.keys', JSON.stringify(data.keys));
data.keys.forEach(item => {
if (item.includes('uni-im-msg:') || item.includes('uni-im-conversation')) {
// console.log(item);
uni.removeStorageSync(item);
console.log(uni.getStorageSync(item));
}
});
uni.setStorageSync('uni-im-storage-version',version)
}
}) {
// 发布新版本后,清理旧版本下的storage避免脏数据引发问题
let version = '2022.11.23.01'
if (uni.getStorageSync('uni-im-storage-version') != version) {
let data = uni.getStorageInfoSync();
// console.log('data.keys', JSON.stringify(data.keys));
data.keys.forEach(item => {
if (item.includes('uni-im-msg:') || item.includes('uni-im-conversation')) {
// console.log(item);
uni.removeStorageSync(item);
console.log(uni.getStorageSync(item));
}
});
uni.setStorageSync('uni-im-storage-version',version)
}
if (token) {
// #ifdef H5
//删除地址栏的token,且不刷新页面
history.pushState({},'','/#/');
if (token) {
// #ifdef H5
//删除地址栏的token,且不刷新页面
history.pushState({},'','/#/');
// #endif
//来源三方系统登录的token
//来源三方系统登录的token
const uniImCo = uniCloud.importObject("uni-im-co",{
loadingOptions: {
title: '登录中'
}
})
let res = await uniImCo.login(token)
await uniIdMutations.updateUserInfo()
// return
/*const uniImOpenApi = uniCloud.importObject("uni-im-openapi", {
loadingOptions: {
title: '登录中'
}
})
const loginRes = await uniImOpenApi.login(token)
let res = await uniImCo.login(token)
await uniIdMutations.updateUserInfo()
if(loginRes.chattingUserInfo){
user_id = loginRes.chattingUserInfo._id
}*/
} else {
if (!uniCloud.getCurrentUserInfo().uid) {
if (!uniCloud.getCurrentUserInfo().uid) {
return uni.redirectTo({
url: '/uni_modules/uni-id-pages/pages/login/login-withpwd'
})
}
}
}
this.init({
user_id,
goods,
conversation_id
})
// console.log('user_id',user_id);
// 初始化会话列表
await this.loadMore()
if (conversation_id) {
this.toChat(conversation_id)
} else if (user_id) {
//创建会话
const currentConversation = await this.getConversation({
user_id
})
console.log('currentConversation', currentConversation);
// 当前用户给对方发个消息
this.toChat(currentConversation.id)
} else {
if (this.isWidescreen) {
let [firstConversation] = this.conversationList
if (firstConversation) {
setTimeout(() => {
this.toChat(firstConversation.id)
}, 100);
} else {
uni.showModal({
content: '没有任何会话,请先到用户列表选择用户',
showCancel: false
});
}
}
}
if (this.isWidescreen && goods) {
console.log(goods);
if (typeof goods != 'object') {
goods = JSON.parse(goods)
}
const {
name,
url
} = goods
if (name && url) {
this.$refs['chat-view'].chatText = '【' + name + ':' + url + '】'
}
}
uni.$on('uni-id-pages-login-success', async () => {
this.init({
user_id,
goods,
conversation_id
})
})
},
onUnload() {
uni.$off('uni-id-pages-login-success')
},
onHide() {},
methods: {
...mapMutations({
...mapMutations({
setCurrentConversationId: 'uniIm/setCurrentConversationId',
updateConversation: 'uniIm/updateConversation'
}),
...mapActions({
// 加载更多会话数据
loadMore: 'uniIm/loadMoreConversation',
initConversationData: 'uniIm/initConversationData',
getConversation: 'uniIm/getConversation',
setMsgList: 'uniIm/setMsgList'
}),
async init({
conversation_id,
user_id,
goods
}) {
// 初始化会话列表
await this.initConversationData()
if (conversation_id) {
console.log('conversation_id', conversation_id);
this.toChat(conversation_id)
} else if (user_id) {
//创建会话
const currentConversation = await this.getConversation({
user_id
})
console.log('currentConversation', currentConversation);
// 当前用户给对方发个消息
this.toChat(currentConversation.id)
} else {
if (this.isWidescreen) {
let [firstConversation] = this.conversationList
if (firstConversation) {
setTimeout(() => {
this.toChat(firstConversation.id)
}, 100);
} else {
uni.showModal({
content: '没有任何会话,请先到用户列表选择用户',
showCancel: false
});
}
}
}
if (this.isWidescreen && goods) {
console.log(goods);
if (typeof goods != 'object') {
goods = JSON.parse(goods)
}
const {
name,
url
} = goods
if (name && url) {
this.$refs['chat-view'].chatText = '【' + name + ':' + url + '】'
}
}
},
search(e) {
// console.log("search-e: " + JSON.stringify(e));
uni.showToast({
......@@ -279,8 +284,8 @@
});
},
onScroll(e) {
if (this.isWidescreen) {
// console.log('--------------onPageScroll',e, e.detail.scrollTop);
if (this.isWidescreen) {
// console.log('--------------onPageScroll',e, e.detail.scrollTop);
setTimeout(() => {
if (this.scrollTop == e.detail.scrollTop && this.scrollTop < 50) {
// console.log('滚动结束', e.detail.scrollTop);
......@@ -302,14 +307,16 @@
});
},
toChat(conversation_id) {
// console.log('toChat',{conversation_id,'isWidescreen':this.isWidescreen});
// console.log('toChat',{conversation_id,'isWidescreen':this.isWidescreen});
this.setCurrentConversationId(conversation_id);
if (this.isWidescreen) { //若为宽屏,则触发右侧详情页的自定义事件,通知右侧窗体刷新详情
this.$refs['chat-view'].load(conversation_id)
if(this.$refs['chat-view']){
this.$refs['chat-view'].load(conversation_id)
}
} else { // 若为窄屏,则打开新窗体,在新窗体打开详情页面
uni.navigateTo({
url: '/uni_modules/uni-im/pages/chat/chat?conversation_id=' + conversation_id,
animationDuration:300
url: '/uni_modules/uni-im/pages/chat/chat?conversation_id=' + conversation_id,
animationDuration: 300
})
}
},
......@@ -324,12 +331,13 @@
formatTime(timestamp) {
return uniImUtils.formatTime(timestamp)
},
setStyle() {
setStyle() {
// #ifdef H5
/* 删除页面的占位元素 */
let conversationChildDom = document.querySelector('uni-page[data-page="uni_modules/uni-im/pages/index/index"]')
if(!conversationChildDom){
return
/* 删除页面的占位元素 */
let conversationChildDom = document.querySelector(
'uni-page[data-page="uni_modules/uni-im/pages/index/index"]')
if (!conversationChildDom) {
return
}
let conversationDom = conversationChildDom.parentElement
if (!conversationDom) {
......@@ -352,31 +360,34 @@
}
// #endif
}
},
onNavigationBarButtonTap(e) {
console.log(e);
if (e.index) {
let data = uni.getStorageInfoSync();
console.log('data.keys', JSON.stringify(data.keys));
data.keys.forEach(item => {
if (item.includes('uni-im-msg:') || item.includes('uni-im-conversation')) {
// console.log(item);
uni.removeStorageSync(item);
console.log(uni.getStorageSync(item));
}
});
uni.showToast({
title: 'clear storage ok',
icon: 'none'
});
} else {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/login/login-withpwd',
complete: e => {
console.log(e);
}
});
}
},
async onReachBottom() {
await this.loadMore()
},
onNavigationBarButtonTap(e) {
console.log(e);
if (e.index) {
let data = uni.getStorageInfoSync();
console.log('data.keys', JSON.stringify(data.keys));
data.keys.forEach(item => {
if (item.includes('uni-im-msg:') || item.includes('uni-im-conversation')) {
// console.log(item);
uni.removeStorageSync(item);
console.log(uni.getStorageSync(item));
}
});
uni.showToast({
title: 'clear storage ok',
icon: 'none'
});
} else {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/login/login-withpwd',
complete: e => {
console.log(e);
}
});
}
},
}
</script>
......@@ -386,8 +397,8 @@
#page,
view {
display: flex;
flex-direction: row;
}
flex-direction: row;
}
view,
scroll-view {
......@@ -404,17 +415,19 @@
border-right: solid 1px #dededd;
}
#user-list-box {
height: calc(100vh - 44px);
#user-list-box {
height: calc(100vh - 94px);
}
@media screen and (min-width: 960px) {
#page,page {
background-color: #2e2e3e;
}
#user-list-box {
width: 299px;
#page,
page {
background-color: #2e2e3e;
}
#user-list-box {
width: 299px;
}
#left-tabbar {
......@@ -445,7 +458,7 @@
}
#user-list-box .user-list {
width: 100% !important;
width: 100% !important;
}
#chat-view-box {
......@@ -474,9 +487,10 @@
/* #ifndef APP-NVUE */
scroll-anchoring: 'auto'
/* #endif */
}
#chat-view ::v-deep .null{
background-color: #f8f8f8;
}
#chat-view ::v-deep .null {
background-color: #f8f8f8;
}
#chat-view ::v-deep #list {
......@@ -488,8 +502,8 @@
}
// pc端不用占位符,因为头部是虚拟的
#chat-view ::v-deep .msg-list-box {
margin-top: 0;
#chat-view ::v-deep .msg-list-box {
margin-top: 0;
}
#chat-view ::v-deep .page * {
......@@ -520,14 +534,20 @@
#conversation ::v-deep .uni-list--border,
#conversation ::v-deep .uni-list--border-top {
display: none;
}
/* #ifdef VUE3 && H5 */
::v-deep{
.formatTime,.msg-list, .msg-box,.loadMore,.tip-view{
width: 660px;
}
}
}
/* #ifdef VUE3 && H5 */
::v-deep {
.formatTime,
.msg-list,
.msg-box,
.loadMore,
.tip-view {
width: 660px;
}
}
/* #endif */
}
......@@ -604,8 +624,8 @@
left: calc(50% - 130px);
width: 260px;
text-align: center;
z-index: 999;
z-index: 999;
}
/* #endif */
</style>
\ No newline at end of file
/* #endif */
</style>
......@@ -12,7 +12,7 @@
{
"path": "uni_modules/uni-im/pages/chat/chat",
"style": {
"navigationBarTitleText": "会话列表",
"navigationBarTitleText": "",
"enablePullDownRefresh": false,
"maxWidth": "960"
}
......
**APP端兼容性说明**:仅支持Vue2,且HBuilderX的版本为3.6.9+,否则chat页面存在滚动锚定问题(后续会修复此问题)
<h2>
文档已移至 <a href="https://uniapp.dcloud.io/uniCloud/uni-im.html" target="_blank">uni-im文档</a>
</h2>
......
// 云对象教程: https://uniapp.dcloud.net.cn/uniCloud/cloud-obj
// jsdoc语法提示教程:https://ask.dcloud.net.cn/docs/#//ask.dcloud.net.cn/article/129
const admin_user_id = false//['63413736d76aaf000137e890']
const db = uniCloud.database();
const conversationTable = db.collection('uni-im-conversation')
const msgTable = db.collection('uni-im-msg')
......@@ -10,7 +9,10 @@ const dbCmd = db.command
const $ = dbCmd.aggregate
const md5 = require("md5");
const jwt = require("jsonwebtoken");
const createConfig = require("uni-config-center");
const createConfig = require("uni-config-center");
const uniImConfig = createConfig({
pluginId: 'uni-im', // 插件id
})
module.exports = {
_before() {
this.clientInfo = this.getClientInfo()
......@@ -29,7 +31,7 @@ module.exports = {
}
res = await conversationTable.aggregate()
.sort({
update_time: 1
update_time: -1
})
.match({
"owner_uid": res.uid,
......@@ -111,19 +113,23 @@ module.exports = {
}
//发送者身份id
const from_uid = res.uid
if(
admin_user_id
&&
!(admin_user_id.includes(from_uid) || admin_user_id.includes(to_uid))
){
return {
errMsg: '非法通讯,会话双方用户id,均不属于uni-im-co中配置的admin_user_id',
errSubject: "uni-im",
errCode: 40001
}
let admin_uid = uniImConfig.config('admin_uid')
if(admin_uid){
if(typeof admin_uid == 'string'){
admin_uid = [admin_uid]
}
if(
!(admin_uid.includes(from_uid) || admin_uid.includes(to_uid))
){
return {
errMsg: '非法通讯,会话双方用户id,均不属于uni-im-co中配置的admin_uid',
errSubject: "uni-im",
errCode: 40001
}
}
}
const conversation_id = getConversationId([from_uid, to_uid])
const msgData = {
body,
......@@ -249,11 +255,14 @@ module.exports = {
let uniPush = uniCloud.getPushManager({appId:params.appId})
res = await uniPush.sendMessage(param)
if (res.errCode) {
// if(res.errCode == "uni-push-user-invalid"){
// //可能因为用户长时间没有登录导致的cid过期而发送失败,但是此时已将离线数据写入数据库,登录后可获取。客户端不需要进入 catch
// }
res = {
cause:res
if(res.errCode == "uni-push-user-invalid"){
//可能因为用户长时间没有登录导致的cid过期而发送失败,但是此时已将离线数据写入数据库,登录后可获取。客户端不需要进入 catch
res = {
cause:res,
errCode:0
}
}else{
return res
}
}
console.log('sendMessage',JSON.stringify(res))
......@@ -269,17 +278,26 @@ module.exports = {
}
}
console.log('state : ============> ' + state);*/
res.data.create_time = msgData.create_time
return res
if(!res.data){
res.data = {}
}
res.data.create_time = msgData.create_time return res
},
async loginWithJWT(sign){
const uniImConfig = createConfig({
pluginId: 'uni-im', // 插件id
})
let jwtSecret = uniImConfig.config('jwtSecret')
if(jwtSecret == 'jwtSecretDemo'){
console.error('[warn]: 不要使用默认的:jwtSecret的值');
let jwtSecret = uniImConfig.config('jwtSecret')
if(!jwtSecret){
return {
errCode: 4000,
errMsg: 'jwtSecret的值不能为空'
}
}
if(jwtSecret == 'jwtSecretDemo'){
console.error('[warn]: 不要使用默认的:jwtSecret的值');
return {
errCode: 4100,
errMsg: '不要使用默认的:jwtSecret的值'
}
}
let userInfo;
try{
let decoded = jwt.verify(sign, jwtSecret);
......@@ -319,7 +337,7 @@ module.exports = {
errCode: 0,
newToken
}
}
}
}
function getConversationId(param) {
......
## 1.2.10(2022-11-23)
修复 uni-list-item 组件 keep-scroll-position 属性 无法设置为false的错误
## 1.2.9(2022-11-22)
- 修复 uni-list-chat 在vue3下跳转报错的bug
## 1.2.8(2022-11-21)
- 修复 uni-list-chat avatar属性 值为本地路径时错误的问题
## 1.2.7(2022-11-21)
- 修复 uni-list-chat avatar属性 在腾讯云版uniCloud下错误的问题
## 1.2.6(2022-11-18)
- 修复 uni-list-chat note属性 支持:“草稿”字样功能 文本少1位的问题
## 1.2.5(2022-11-15)
- 修复 uni-list-item 的 customStyle 属性 padding值在 H5端 无效的bug
## 1.2.4(2022-11-15)
......
......@@ -26,7 +26,7 @@
<text class="uni-list-chat__content-title uni-ellipsis">{{ title }}</text>
<view style="flex-direction: row;">
<text class="draft" v-if="isDraft">[草稿]</text>
<text class="uni-list-chat__content-note uni-ellipsis">{{isDraft?note.slice(14,-1):note}}</text>
<text class="uni-list-chat__content-note uni-ellipsis">{{isDraft?note.slice(14):note}}</text>
</view>
</view>
<view class="uni-list-chat__content-extra">
......@@ -155,7 +155,7 @@
watch: {
avatar:{
handler(avatar) {
if(avatar.includes('://')){
if(avatar.substr(0,8) == 'cloud://'){
uniCloud.getTempFileURL({
fileList: [avatar]
}).then(res=>{
......
<template>
<!-- #ifdef APP-NVUE -->
<cell keep-scroll-position>
<cell :keep-scroll-position="keepScrollPosition">
<!-- #endif -->
<view :class="{ 'uni-list-item--disabled': disabled }" :style="{'background-color':customStyle.backgroundColor}"
:hover-class="(!clickable && !link) || disabled || showSwitch ? '' : 'uni-list-item--hover'"
......@@ -183,6 +183,10 @@
backgroundColor: '#FFFFFF'
}
}
},
keepScrollPosition: {
type: Boolean,
default: false
}
},
watch: {
......
{
"id": "uni-list",
"displayName": "uni-list 列表",
"version": "1.2.5",
"version": "1.2.10",
"description": "List 组件 ,帮助使用者快速构建列表。",
"keywords": [
"",
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册