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

删除 多余的文件

上级 c8235760
<template>
<!-- 设置导航栏标题,如与谁对话,群人数为几人等 -->
<!-- #ifndef H5 -->
<page-meta>
<navigation-bar :title="navTitle" background-color="#f8f8f8" front-color="#000000" />
</page-meta>
<!-- #endif -->
<!-- #ifdef H5 -->
<view v-if="isWidescreen">
<text style="position: absolute;z-index: 999;top: -45px;left: 15px;" :selectable="true">{{ navTitle }}</text>
</view>
<page-meta v-else>
<navigation-bar :title="navTitle" background-color="#f8f8f8" front-color="#000000" />
</page-meta>
<!-- #endif -->
<view class="page">
<view class="head">
<view v-if="count == 0 && loading" class="hint">正在加载……</view>
<view v-else class="hint">{{ count }} 条与“{{ keyword }}”相关的聊天记录</view>
<view
v-if="conversation_id"
class="enter-chat"
@click="onEnterConversation(conversation_id)"
>
<uni-icons type="chatbubble" size="16"></uni-icons>
进入会话
</view>
</view>
<scroll-view
class="message-list"
scroll-y
:scroll-into-view="autoScrollToEl"
@scrolltoupper="onScrollToUpper"
>
<uni-im-msg
v-for="msg in msgList"
:key="msg._id"
:id="`msg-${msg._id}`"
:msg="msg"
no-time
no-jump
@loadMore="cb => cb()"
>
<view class="float-info">
<view>{{ toFriendlyTime(msg) }}</view>
<view class="enter-fragment" @click="onOpenFragment(msg)">查看上下文</view>
</view>
</uni-im-msg>
<!-- nvue不支持id选择器,所以需要写两个 -->
<view id="bottom-el" class="bottom-el"></view>
</scroll-view>
<chat-fragment
v-if="fragment"
:entry="fragment"
@close="onCloseFragment"
/>
</view>
</template>
<script>
/**
* chat-filtered 组件,渲染一个会话中经过滤选择的消息列表,用于显示某个会话的消息搜索结果。
*
* 点击某条消息的“查看上下文”按钮可以打开 {@link module:chat-fragment} 组件。
*
* @module
*/
const uniImCo = uniCloud.importObject("uni-im-co", {
customUI: true
})
import uniIm from '@/uni_modules/uni-im/sdk/index.js'
import ChatFragment from './cmp/chat-fragment'
export default {
components: {
ChatFragment,
},
emits: ['to-chat'],
data() {
return {
loading: true,
count: 0,
keyword: '',
msgList: [],
hasMore: true,
skip: Number.MAX_SAFE_INTEGER,
conversation_id: '',
autoScrollToEl: '',
// 当前会话对象
conversation: {},
// 聊天记录片段的入口消息
fragment: null,
};
},
computed: {
...uniIm.mapState(['isWidescreen']),
navTitle() {
let title = this.conversation.title
if (this.conversation.group_id) {
title += `(${Object.keys(this.conversation.group_member).length})`;
}
return title
}
},
async onLoad(param) {
// 调用load方法,因为pc宽屏时本页面是以组件形式展示,如 $refs.chatFiltered.load(conversation_id)
await this.load(param);
},
methods: {
async load({ keyword, count, conversation_id }) {
// 根据入口参数进行初始化
this.loading = true
this.count = count
this.keyword = keyword
this.msgList = []
this.hasMore = true
this.skip = Number.MAX_SAFE_INTEGER
this.conversation_id = conversation_id
this.conversation = await uniIm.conversation.get(conversation_id)
this.autoScrollToEl = ''
this.fragment = null
// 加载第一批匹配的聊天记录
this.loadData(() => {
// 自动滚动到底
this.autoScrollToEl = 'bottom-el'
})
},
async loadData(afterLoaded) {
this.loading = true
let result = await uniImCo.getFilteredMessageOfConversation({
keyword: this.keyword,
skip: this.skip,
conversation_id: this.conversation_id,
})
this.msgList.unshift(...result.data.reverse())
if (this.count < this.msgList.length) {
// 计数以传入的 count 为准,除非实际查询到的更多
this.count = this.msgList.length
}
this.hasMore = result.hasMore
this.skip = result.skip
this.loading = false
this.$nextTick(afterLoaded)
},
onScrollToUpper(evt) {
if (this.loading) return
if (!this.hasMore) return
let elId = 'bottom-el'
if (this.msgList.length > 0) {
elId = 'msg-' + this.msgList[0]._id
}
this.autoScrollToEl = ''
this.loadData(() => {
this.autoScrollToEl = elId
})
},
onEnterConversation(conversation_id) {
this.$emit('to-chat', { conversation_id })
},
onOpenFragment(msg) {
this.fragment = msg
},
onCloseFragment() {
this.fragment = null
},
toFriendlyTime(msg) {
return uniIm.utils.toFriendlyTime(msg.create_time || msg.client_create_time)
}
}
}
</script>
<style lang="scss" scoped>
/* #ifndef APP-NVUE */
view {
display: flex;
flex-direction: column;
box-sizing: border-box;
}
page {
background-color: #efefef;
}
/* #endif */
.page {
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
/* #ifndef APP-NVUE */
height: calc(100vh - 45px);
/* #endif */
/* #ifndef APP-NVUE */
background-color: #efefef;
/* #endif */
// border: solid 5px #2faf4c;
}
/* #ifdef H5 */
.pc {
// .pc内的元素只有pc端打开才显示,样式在index页面
display: none;
}
/* #endif */
.head {
flex-direction: row;
justify-content: space-between;
height: 30px;
line-height: 30px;
padding: 0px 15px;
font-size: 12px;
border-bottom: 1px solid #ddd;
}
.hint {
color: #999;
}
.enter-chat {
/* #ifdef H5 */
cursor: pointer;
/* #endif */
flex-direction: row;
padding: 0 5px;
}
/* #ifdef H5 */
.enter-chat:hover {
background-color: #ddd;
}
/* #endif */
.bottom-el {
height: 1px;
}
.message-list {
/* #ifndef APP-NVUE */
height: calc(100% - 30px);
/* #endif */
}
.uni-im-msg ::v-deep .msg-content {
/* #ifndef APP-NVUE */
width: calc(95% - 40px);
/* #endif */
}
.float-info {
align-items: flex-end;
position: absolute;
top: 0;
right: 0px;
font-size: 12px;
color: #999;
padding: 10px;
}
.enter-fragment {
/* #ifdef APP-NVUE */
// 因为nvue不支持 display:none;
width: 0;
height: 0;
/* #endif */
/* #ifdef H5 */
cursor: pointer;
/* #endif */
color: #576b95;
}
/* #ifdef H5 */
.enter-fragment:hover {
color: #7c8cae;
}
.uni-im-msg:hover .enter-fragment {
display: block;
}
/* #endif */
.chat-fragment {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: white;
}
</style>
<template>
<!-- 设置导航栏标题,如与谁对话,群人数为几人等。注意:部分平台page-meta必须为第一个节点 -->
<!-- #ifndef H5 -->
<page-meta>
<navigation-bar :title="navTitle" background-color="#f8f8f8" front-color="#000000" />
</page-meta>
<!-- #endif -->
<!-- #ifdef H5 -->
<view v-if="isWidescreen">
<text style="position: absolute;z-index: 9;top: -45px;left: 15px;" :selectable="true">{{navTitle}}</text>
</view>
<page-meta v-else>
<navigation-bar :title="navTitle" background-color="#f8f8f8" front-color="#000000" />
</page-meta>
<!-- #endif -->
<view class="page" id="uni-im-chat">
<!-- #ifdef H5 -->
<!-- H5端 左上角显示未读消息数 ,nvue端使用setTitleNViewButtonBadge设置-->
<view @click="tapUnreadCount" class="unread_count" v-if="unread_count != 0">
{{ unread_count > 99 ? '99+' : unread_count }}
</view>
<!-- #endif -->
<uni-im-msg-list v-if="conversation.id" :conversationId="conversation.id" ref="msg-list" @showControl="showControl" @chatInputContentAddcallUser="setCallAboutUidBefore"
@retriesSendMsg="retriesSendMsg" :paddingBottom="msgListPaddingBottom + 'px'" class="msg-list"
@clickItem="hideKeyboard" @putChatInputContent="putChatInputContent" :chooseMore="chooseMoreMsg"
:checkedMsgList.sync="checkedMsgList"
></uni-im-msg-list>
<!-- 聊天数据输入框 键盘弹出后要抬高底部内边距 全面屏的安全距离 -->
<text v-if="conversation.leave" class="disable-input">- 你不是此群成员 -</text>
<text v-else-if="conversation.isMuteAllMembers" class="disable-input">- 全群禁言禁止输入 -</text>
<view v-else class="chat-foot">
<!-- pc宽屏(width>960px)的输入框 -->
<!-- #ifdef H5 -->
<view v-if="isWidescreen" class="pc">
<view class="tool-bar">
<view class="icons">
<uni-im-icons v-for="(item,index) in menuList" :key="index" @click.native.stop="clickMenu(index,$event)"
:code="item.iconCode" size="24" color="#666666" :title.native="`选择${item.title},并发送`"></uni-im-icons>
</view>
<template v-for="item in inputMsgToolBar" :key="item.component.name">
<component :is="item.component" v-bind="item.props" @beforeSendMsg="beforeSendMsg" @sendMsg="sendMsg" @updateMsg="updateMsg" />
</template>
</view>
<view class="answer-msg" v-if="answerMsg !== false">
<text class="answer-msg-text">{{getNicknameByUid(answerMsg.from_uid)}}:{{trAnswerMsg(answerMsg)}}</text>
<uni-icons @click="answerMsg = false" type="clear" color="#aaa" size="16px"></uni-icons>
</view>
<uni-im-chat-input ref="chat-input" class="textarea" @input="onInput" :maxlength="50000"
v-model="chatInputContent" @focus="onChatInputFocus" @blur="onChatInputBlur" :focus="isFocus"
placeholder="请输入消息" placeholder-style="color:#bbb;" @confirm="chatInputConfirm"
></uni-im-chat-input>
<view class="send-btn-box">
<text class="send-btn-tip">↵ 发送 / shift + ↵ 换行</text>
<button @click="chatInputConfirm" :disabled="!canSend" class="send" type="primary">发送</button>
</view>
</view>
<!-- #endif -->
<!-- 非pc宽屏(width<960px)的输入框 -->
<template v-if="!isWidescreen">
<view v-if="showInputBox" class="input-box">
<!-- 切换为语音模式 -->
<!-- #ifdef H5 -->
<view class="camera-filled-box" @click="chooseFileSendMsg('image')">
<uni-icons type="camera-filled" size="20px" color="#FFF"></uni-icons>
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<uni-im-icons @click="changeSoundIsShow" :code="soundIsShow?'e69f':'e684'" size="30"
class="icon"></uni-im-icons>
<!-- #endif -->
<view class="input-box-conetnt">
<view class="textarea-box">
<textarea :cursor="inputCursor" v-model="chatInputContent" @input="onInput" @confirm="beforeSendMsg()"
@linechange="linechange" :flex="true" :style="{ height: textareaHeight + 'px' }"
:disable-default-padding="false" :hold-keyboard="true" :confirm-hold="true" :auto-blur="false"
confirm-type="send" :show-confirm-bar="false" :cursor-spacing="20" maxlength="50000" :focus="systemInfo.uniPlatform === 'app'?'':isFocus"
@focus="onChatInputFocus" @blur="onChatInputBlur" :fixed="true" :adjust-position="false"
class="textarea" ref="chat-input" />
</view>
<view class="answer-msg" v-if="answerMsg !== false">
<text class="answer-msg-text">{{getNicknameByUid(answerMsg.from_uid)}}:{{trAnswerMsg(answerMsg)}}</text>
<uni-icons @click="answerMsg = false" type="clear" color="#aaa" size="16"></uni-icons>
</view>
<uni-im-sound class="uni-im-sound" v-if="soundIsShow" @success="sendSound"></uni-im-sound>
</view>
<uni-im-icons @click.native.stop="changeEmojiIsShow" :code="emojiIsShow?'e69f':'e646'" size="30"
class="icon"></uni-im-icons>
<text v-if="!soundIsShow&&chatInputContent" @click.prevent="beforeSendMsg()" class="icon beforeSendMsg">发送</text>
<uni-im-icons v-else @click.native.stop="changeMenuIsShow" code="e75a" size="30" class="icon"></uni-im-icons>
</view>
<view v-if="menuIsShow||emojiIsShow" class="media-box" :style="{height:keyboardMaxHeight+'px'}">
<view v-if="menuIsShow" class="menu" :style="{height:keyboardMaxHeight+'px'}">
<view class="menu-item" v-for="(item,index) in menuList" :key="index" @click.stop="clickMenu(index,$event)">
<view class="menu-item-icon">
<uni-im-icons :code="item.iconCode" size="26"></uni-im-icons>
</view>
<text class="menu-item-text">{{item.title}}</text>
</view>
</view>
<scroll-view :scroll-y="true" v-if="emojiIsShow" class="emojiListBox"
:style="{height:keyboardMaxHeight+'px'}">
<text v-for="(uniCodeEmoji,index) in emojiCodes" :key="index"
@click.stop="clickEmojiItem(uniCodeEmoji,$event)" class="emoji-item">{{uniCodeEmoji}}</text>
</scroll-view>
</view>
<!-- #ifndef H5 -->
<!-- 调整输入框的位置,占位专用:键盘高度 或 全面屏底部不可用区域高度 -->
<view v-else :style="{height: (keyboardHeight || phoneBH) +'px'}"></view>
<!-- #endif -->
</template>
</view>
<msg-popup-control ref="msg-popup-control" @answer="setAnswerMsg" @share="shareMsg" @chooseMore="chooseMoreMsg = true;checkedMsgList = $event"></msg-popup-control>
<template v-if="showAboutUidBox && memberList.length != 0">
<view class="member-list-mark" @click.stop="showAboutUidBox = false"></view>
<scroll-view scroll-y="true" class="member-list" :scroll-top="memberListScrollTop">
<view v-for="(item,index) in memberList" :key="item._id" class="member-list-item"
:class="{'member-list-item-active':callAboutUid == item.users._id}" @mouseover="callAboutUid = item.users._id"
@click="setCallAboutUid(item.users._id)" :id="'a'+item.users._id">
{{item.users.nickname}}
</view>
</scroll-view>
</template>
<!-- #ifdef H5 -->
<uni-im-share-msg id="uni-im-share-msg" ref="share-msg"></uni-im-share-msg>
<!-- #endif -->
<view style="position: fixed;top: 200px;left: 0;background-color: #FFFFFF;">
<!-- showAboutUidBox:{{showAboutUidBox}}-
aboutUserKeyword:{{aboutUserKeyword}}-
conversation.leave:{{conversation.leave}}
msgListPaddingBottom:{{msgListPaddingBottom}}
keyboardHeight:{{keyboardHeight}}
keyboardMaxHeight:{{keyboardMaxHeight}}
systemInfo.osName:{{systemInfo.osName}}
chooseMoreMsg:{{chooseMoreMsg}}
checkedMsgList:{{checkedMsgList}} -->
</view>
<view v-if="chooseMoreMsg" class="toolbar" @click="chooseMoreMsg = false">
<view class="item" @click="shareMsg(checkedMsgList)">
<view class="icons-box">
<uni-icons size="35" type="redo"></uni-icons>
</view>
<text class="title">逐条转发</text>
</view>
<view class="item" @click="shareMsg(checkedMsgList,true)">
<view class="icons-box">
<uni-icons size="35" type="paperplane"></uni-icons>
</view>
<text class="title">合并转发</text>
</view>
<view class="item" @click="toolBarNext">
<view class="icons-box">
<uni-icons size="35" type="folder-add"></uni-icons>
</view>
<text class="title">收藏</text>
</view>
<view class="item" @click="toolBarNext">
<view class="icons-box">
<uni-icons size="35" type="download"></uni-icons>
</view>
<text class="title">保存至电脑</text>
</view>
<view class="item" @click="toolBarNext">
<view class="icons-box">
<uni-icons size="35" type="trash"></uni-icons>
</view>
<text class="title">删除</text>
</view>
<uni-icons @click="chooseMoreMsg = false" color="#999" size="35" type="closeempty"></uni-icons>
</view>
</view>
</template>
<script>
import uniIm from '@/uni_modules/uni-im/sdk/index.js';
import msgPopupControl from '@/uni_modules/uni-im/components/uni-im-msg/popup-control.vue';
import {markRaw} from "vue";
// #ifdef APP-NVUE
// 定义weex的dom模块(https://doc.weex.io/zh/docs/modules/dom.html#scrolltoelement)
const dom = weex.requireModule('dom') || {};
// #endif
import {
store as uniIdStore
} from '@/uni_modules/uni-id-pages/common/store';
import emojiCodes from './emojiCodes.js';
let shiftKeyPressed = false;
let lastKeyDown = ""
let currentCursor = ''
/**
* chat 组件,渲染一个完整的会话,包括头部、消息列表、输入区。
*
* @module
*
* @see 用于渲染消息列表的组件 {@link module:uni-im-msg-list}
*/
export default {
components: {
msgPopupControl
},
data() {
return {
inputMsgToolBar:[],
// 当前会话对象
conversation: {
id: false,
leave:false
},
//聊天输入框高度
textareaHeight: 26,
menuIsShow: false,
emojiIsShow: false,
soundIsShow: false,
menuList: [{
"title": "图片",
"iconCode": "e7be"
},
{
"title": "视频",
"iconCode": "e690"
},
{
"title": "文件",
"iconCode": "e69e"
}
],
navTitle:"",//导航栏标题
keyboardHeight: 0,
keyboardMaxHeight: 260,
emojiCodes: emojiCodes,
isFocus: false,
answerMsg: false,
callAboutUid: false,
showAboutUidBox: false,
inputCursor: '',
// @用户时的搜索词
aboutUserKeyword: '',
memberListScrollTop: 0,
chooseMoreMsg: false,
checkedMsgList: [],
// 聊天输入框内容
chatInputContent: '',
};
},
props: {
// #ifdef VUE3
conversation_id: {
default: ''
}
// #endif
},
computed: {
...uniIm.mapState(['currentConversationId', 'conversationDatas', 'isWidescreen', 'systemInfo']),
unread_count() {
// 所有会话的未读消息数
const unreadCount = uniIm.conversation.unreadCount()
// #ifdef APP-PLUS
// 给标题栏返回按钮添加数字角标,表示有几条其他会话的未读消息数
plus.webview.currentWebview().setTitleNViewButtonBadge({
index: 0,
text: unreadCount
})
// #endif
return unreadCount
},
isSafariPc() {
return this.systemInfo.browserName == 'safari' && this.isWidescreen
},
group_member() {
let group_member = this.conversation.group_member
if (!this.callAboutUid && typeof group_member == 'object') {
this.callAboutUid = Object.keys(this.conversation.group_member)[0]
}
return group_member
},
memberList() {
// 当前输入框已经@了的用户id 要过滤掉
let callUidList = this.getCallUid(this.chatInputContent)
let group_member = this.group_member || {}
let memberList = []
for (let key in group_member) {
let member = group_member[key]
if(member.users.nickname){
let nickname = member.users.nickname.toLowerCase()
if (nickname.indexOf(this.aboutUserKeyword.toLowerCase()) != -1 && !callUidList.includes(member.users._id)) {
memberList.push(member)
}
}
}
if (memberList.length) {
let has = memberList.find(member => member.users._id == this.callAboutUid)
if (!has) {
this.callAboutUid = memberList[0].users._id
}
} else {
// 没有数据时也不隐藏,因为用户可能回删@用户的关键词
// this.showAboutUidBox = (memberList.length != 0)
}
// 按昵称排序
return memberList.sort((a, b) => {
return a.users.nickname.localeCompare(b.users.nickname)
})
},
//聊天数据
canSend() {
if(typeof this.chatInputContent === 'string'){
return this.chatInputContent.trim() != ''
}else{
return this.chatInputContent.html.length != 0
}
},
//当前用户自己的uid
current_uid() {
return uniIdStore.userInfo._id;
},
phoneBH() {
// #ifdef H5
return 0
// #endif
// #ifndef H5
return this.systemInfo.safeAreaInsets.bottom
// #endif
},
msgListPaddingBottom() {
// #ifdef H5
// 是否为pc宽屏(width>960px)
if (this.isWidescreen) {
return 0
}else{
return 56
}
// #endif
let msgListPaddingBottom = this.textareaHeight + 30
if (this.keyboardHeight || this.menuIsShow || this.emojiIsShow) {
msgListPaddingBottom += this.keyboardMaxHeight
}
if (!this.keyboardHeight) {
msgListPaddingBottom += this.phoneBH
}
return msgListPaddingBottom
},
// 临时方案 修复(兼容)微信小程序框架的bug:iOS端textarea组件 在 iOS 真机下 无法动态切换绑定 onInput 事件
// 大家可以一起顶帖 链接地址:https://developers.weixin.qq.com/community/develop/doc/0002a02800cd90b9632efeab55b000
showInputBox() {
// #ifndef MP-WEIXIN
return true
// #endif
// #ifdef MP-WEIXIN
return this.conversation.id
// #endif
}
},
created() {
// 监听推送消息
this.onImMsg = (res) => {
if(uniIm.isDisabled){
return console.log('uniIm isDisabled')
}
//获取透传内容
const {
type,
data
} = res.data.payload;
//判断消息类型是否为im,且为当前页面对应会话消息
if (
type == "uni-im"
&& data.conversation_id == this.currentConversationId
&& data.from_uid != this.current_uid
&& uniIm.utils.isReadableMsg(data)
) {
// 已经打开相应会话时,收到消息,则设置此会话为已读。注意无需判断,本地会话未读数是否为0,因为云端可能不为0
this.conversation.clearUnreadCount();
console.log('聊天页面-收到消息: ', JSON.stringify(res));
// 需要重新设置滚动条的高,以至于用户可以立即看到(即:滚动到最后一条消息)
// console.log(66666);
// 注:为兼容web-PC端这里必须使用setTimeout 0
setTimeout(() => {
this.$refs['msg-list']?.notifyNewMsg()
}, 0);
}
}
uniIm.onMsg(this.onImMsg);
// #ifdef H5
//通过监听窗口变化 获取键盘弹出或收起事件
window.addEventListener('resize', () => {
if (this.currentConversationId) {
this.showLast(0);
this.soundIsShow = false
}
})
// #endif
this.onKeyboardHeightChange = ({
height
}) => {
this.keyboardHeight = height
// console.log('height',height)
if (height) {
this.keyboardMaxHeight = height
}
this.$nextTick(() => {
this.showLast();
// 临时方案:解决部分情况下 web-pc nvue 平台下不能滚动到最后一条消息的问题
setTimeout(() => {
this.showLast()
}, 800)
});
}
// #ifndef H5
// 监听键盘高度变化显示最后一条消息
uni.onKeyboardHeightChange(this.onKeyboardHeightChange);
// #endif
// #ifdef H5
const oldWindowHeight = window.innerHeight;
window.onresize = ()=>{
this.onKeyboardHeightChange({"height": oldWindowHeight - window.innerHeight})
}
// #endif
},
mounted() {
// #ifdef H5
// 以下为实现拖拽或粘贴图片至聊天页面,直接发送的逻辑
const chatBodyDom = document.getElementById("uni-im-chat")
// 阻止默认事件
chatBodyDom.addEventListener(
'dragover',
function(event) {
event.preventDefault();
},
false
);
// 拖拽结束时触发
chatBodyDom.addEventListener(
'drop',
e => {
//取消默认浏览器拖拽效果
e.preventDefault();
//获取文件对象
let fileList = e.dataTransfer.files;
if (fileList.length == 0) {
return false;
}
const [file] = fileList
let fileType = file.type.split('/')[0] || 'file'
if (fileList === 'image') {
let blobUrl = window.URL.createObjectURL(file);
if(this.chatInputContent === ''){
this.$refs["chat-input"]?.setHtml(`<img src="${blobUrl}" />`)
}else{
this.$refs["chat-input"]?.addHtmlToCursor(`<img src="${blobUrl}" />`)
}
return false; // 拖拽图片内容进入输入框先不发送
}
let {
name,
size
} = file
// console.log(78979798,fileList);
const blobUrl = window.URL.createObjectURL(file);
this.uploadFileAndSendMsg({
tempFiles:[{
size,
name,
path:blobUrl,
fileType
}]
})
},
false
);
const chatInput = document.querySelector('.pc .textarea');
if (chatInput) {
//键盘按下时
let oldValue = ''
chatInput.addEventListener('keydown',async e => {
if (this.showAboutUidBox) {
if(e.key == 'Enter'){
if(this.memberList.length){
console.log('选中要@的人')
this.setCallAboutUid(this.callAboutUid)
}
}else if(["ArrowUp", "ArrowDown"].includes(e.key)){
console.log('上下箭头选择@谁')
let index = this.memberList.findIndex(i => i.users._id == this.callAboutUid)
// console.log('index',index);
if (e.key == "ArrowUp") {
index--
} else {
index++
}
if (index < 0 || index > this.memberList.length - 1) {
index = 0
}
this.callAboutUid = this.memberList[index].users._id
// 防止选中的成员看不到,触发滚动
this.memberListScrollTop = (index - 3) * 50
// console.log('this.memberListScrollTop',this.memberListScrollTop);
e.preventDefault();
}else if(["ArrowLeft", "ArrowRight"].includes(e.key)){
this.showAboutUidBox = false
}else if(e.key == 'Backspace'){
setTimeout(() => {
// 获取e.target 元素内不包含在标签内的文字内容
let newValue = e.target.innerText
console.log('删除键',newValue,oldValue);
// 拿到newValue 和 oldValue 中 包含的@字符的个数
let newAtN = newValue.replace(/[^@]/g, "").length
let oldAtN = oldValue.replace(/[^@]/g, "").length
if(newAtN === 0 || newAtN < oldAtN){
console.log('删除了@成员的昵称');
this.showAboutUidBox = false
}
oldValue = newValue
}, 0);
}
}else{
// console.log('键盘按下时',e.key);
if(e.key == '@'){
this.showAboutUidBox = true
}
}
})
}
// #endif
},
onShow() {
if (this.conversation.id) {
// 用于打开会话窗口后,切换到后台,再切回时设置当前会话id。
uniIm.currentConversationId = this.conversation.id
// 用于从后台切到前台时,设置当前会话为已读
this.clearConversationUnreadCount()
}
},
onUnload() {
// console.log('onUnload');
// 关闭监听消息推送事件
uniIm.offMsg(this.onImMsg);
// #ifndef H5
uni.offKeyboardHeightChange(this.onKeyboardHeightChange)
// #endif
//页面销毁之前销毁 全局变量 正在聊天的用户的id
uniIm.currentConversationId = false
// console.log('beforeDestroy');
// 关闭sound播放
uniIm.audioContext.stop()
},
beforeDestroy() {
//页面销毁之前销毁 全局变量 正在聊天的用户的id
uniIm.currentConversationId = false
// console.log('beforeDestroy');
// 关闭sound播放
uniIm.audioContext.stop()
},
onHide() {
uniIm.currentConversationId = false
// 关闭sound播放
uniIm.audioContext.stop()
},
async onLoad(param) {
for (const key in param) {
try{
param[key] = JSON.parse(param[key])
}catch(_){}
}
//调用load方法,因为pc宽屏时本页面是以组件形式展示。如$refs.chatView.loadconversation_id()执行
await this.load(param);
},
watch: {
async chatInputContent(newValue, oldValue) {
// #ifndef APP-NVUE
if(this.isWidescreen || this.watchChatInputContentDisabled === true){
return
}
// console.log('chatInputContent',newValue,oldValue);
// 截流调用 chatInputContentAfter 方法
if(this.doChatInputContentAfter){
clearTimeout(this.doChatInputContentAfter)
}
this.doChatInputContentAfter = setTimeout(() => {
// console.error('chatInputContent#6');
this.lockDoChatInputContentAfter = false
this.chatInputContentAfter(newValue, oldValue)
}, 300);
// #endif
},
// 监听群昵称变化
'conversation.title':{
handler(){
this.updateNavTitle()
}
},
// 监听群成员数变化
'conversation.group_member':{
handler(){
this.updateNavTitle()
},
deep:true,
immediate:true
},
// 兼容nvue下不能通过isFocus控制聚焦
// #ifdef APP-NVUE
isFocus(newValue){
console.log("this.$refs['chat-input'].focus",this.$refs["chat-input"].attr.focus)
if(newValue){
this.$refs["chat-input"].focus()
}else{
this.$refs["chat-input"].blur()
}
},
// #endif
},
methods: {
async load(param) {
this.answerMsg = false
// conversation_id = "single_eff0518ad35e16a8a025cc8af03e0388"
if(this.conversation.id){
// 设置(含清空)上一个会话的chatInputContent 实现多个会话之间的草稿功能
this.conversation.chatInputContent = this.chatInputContent
}
// console.log('conversation_id',conversation_id);
this.conversation = await uniIm.conversation.get(param)
// 初始化会话的chatInputContent
this.chatInputContent = this.conversation.chatInputContent
// this.conversation.call_list = []
// console.log('this.conversation',this.conversation)
// 调用扩展点,扩展程序可以在消息输入框新增一个工具类的项
this.inputMsgToolBar = uniIm.extensions
.invokeExts("input-msg-tool-bar",this.conversation)
.filter((result) => result && result.component)
.map((result) => {
return {
component: markRaw(result.component),
props: result.props||{}
};
});
//设置全局的app当前正在聊天的会话id(注:本页面可能是直达页)
uniIm.currentConversationId = this.conversation.id
this.$nextTick(() => {
this.$refs['msg-list'].init()
})
// console.log('this.conversation',this.conversation);
//debug用模拟一次性自动发送100条数据
// for (var i = 0; i < 20; i++) {
// this.chatInputContent = '这是第'+i+'条消息'
// this.beforeSendMsg()
// }*/
// 清除当前会话未读数
this.clearConversationUnreadCount()
// #ifdef H5
if(this.isWidescreen){
// 切换到新的会话后把输入焦点设置到输入框(考虑到可能有草稿文字,需延迟设置)
setTimeout(() => {
this.$refs["chat-input"].focusToLast?.()
}, 100)
}
// #endif
},
chatInputContentAfter(newValue, oldValue){
// console.log('chatInputContentAfter',newValue,oldValue);
currentCursor = -1
// 先找到字符不同的位置,即为光标操作时的位置
for (let i = 0; i < newValue.length; i++) {
if (newValue[i] !== oldValue[i]) {
currentCursor = i
break
}
}
if(currentCursor == -1){
currentCursor = newValue.length
}
// console.log('cursor光标的位置',currentCursor,newValue,'newValue');
let isAdd = newValue.length > oldValue.length
let changeValue = isAdd ? newValue[currentCursor] : oldValue[currentCursor]
// console.log('changeValue变化的内容',changeValue);
if(changeValue == '@'){
this.showAboutUidBox = isAdd
if(isAdd){
// 增加了@符号,显示@成员列表
this.memberListScrollTop = 0
if (this.memberList.length) {
this.callAboutUid = this.memberList[0].users._id
}
}
}
// 删除了 \u200b@\u200b${nickname}\u2009
if(!isAdd){
if(changeValue === '\u2009'){
// console.log('删除了 u2009 符号');
// 找到从光标位置到\u200b@\u200b符号的最左边的位置
let leftIndex = newValue.slice(0,currentCursor).lastIndexOf('\u200b@\u200b')
// console.log('leftIndex',leftIndex);
// 删除从leftIndex到光标之间的字符
const newchatInputContent = newValue.slice(0,leftIndex) + newValue.slice(currentCursor)
this.setChatInputContent(newchatInputContent,leftIndex)
}else if(changeValue === '\u200b'){
// console.log('删除了 u200b 符号');
// 找到从光标位置到\u2009符号的最左边的位置
let leftIndex = newValue.slice(currentCursor).indexOf('\u2009') + currentCursor + 1
// console.log('leftIndex~~',leftIndex,newValue.slice(leftIndex));
// 删除从光标到leftIndex之间的字符
const newchatInputContent = newValue.slice(0,currentCursor) + newValue.slice(leftIndex)
this.setChatInputContent(newchatInputContent,currentCursor)
}else{
// 判断被删的字符是否在\u200b@\u200b${nickname}\u2009中
let leftIndex = newValue.slice(0,currentCursor).lastIndexOf('\u200b@\u200b')
let rightIndex = newValue.slice(currentCursor).indexOf('\u2009') + currentCursor + 1
if(leftIndex != -1 && rightIndex != currentCursor){
// console.log('删除了@成员的昵称');
// 删除从leftIndex到rightIndex之间的字符
const newchatInputContent = newValue.slice(0,leftIndex) + newValue.slice(rightIndex)
this.setChatInputContent(newchatInputContent,leftIndex)
}
}
}
},
clearConversationUnreadCount(){
if(this.conversation.unread_count){
this.conversation.clearUnreadCount();
}
},
putChatInputContent(value){
if(this.isWidescreen){
this.$refs["chat-input"]?.setContent(value)
}else{
if(typeof value === 'string'){
this.setChatInputContent(value)
}else{
uni.showToast({
title: '移动端暂不支持编辑富文本消息',
icon: 'none'
});
}
}
},
// 通过setChatInputContent设置chatInputContent时,不再触发watchchatInputContent,并可以设置光标位置
setChatInputContent(value,cursor){
this.watchChatInputContentDisabled = true
this.chatInputContent = value
this.$nextTick(() => {
this.watchChatInputContentDisabled = false
// 光标被重新定位到当前位置
setTimeout(() => {
this.inputCursor = cursor
}, 300);
})
},
getNicknameByUid(uid) {
let users = uniIm.users[uid]
if (users) {
return users.nickname
} else {
return ''
}
},
trAnswerMsg(answerMsg) {
return uniIm.utils.getMsgNote(answerMsg)
},
onChatInputFocus() {
// console.log('onChatInputFocus');
this.isFocus = true;
this.$nextTick(() => {
this.menuIsShow = false
this.emojiIsShow = false
})
},
onChatInputBlur() {
// console.log('onChatInputBlur');
this.isFocus = false;
},
changeSoundIsShow() {
this.soundIsShow = !this.soundIsShow
if (this.soundIsShow) {
// 将输入框高度记录,并设置为26
this.oldTextareaHeight = this.textareaHeight
this.textareaHeight = 26
uni.hideKeyboard();
} else {
// 恢复切换之前的输入框高度
this.textareaHeight = this.oldTextareaHeight
this.isFocus = true
}
uni.$emit('changeSoundIsShow');
this.$nextTick(() => {
this.menuIsShow = false
this.emojiIsShow = false
})
},
changeMenuIsShow(e) {
if (this.keyboardHeight) {
this.menuIsShow = true
uni.hideKeyboard()
} else {
this.menuIsShow = !this.menuIsShow
}
this.emojiIsShow = false;
this.showLast(0)
e.stopPropagation()
},
changeEmojiIsShow(e) {
this.soundIsShow = false
if (this.keyboardHeight) {
this.emojiIsShow = true
uni.hideKeyboard()
} else {
this.emojiIsShow = !this.emojiIsShow
}
this.menuIsShow = false
this.showLast(0)
e.stopPropagation()
},
uploadFileAndSendMsg({tempFiles}){
// console.log(res, 'image');
// console.log('this.uploadFileAndSendMsg res',res);
tempFiles.forEach(async tempFile => {
// console.log('tempFile~',tempFile);
const {
path:url,
name,
size
} = tempFile;
let {fileType} = tempFile
if (!['image', 'video'].includes(fileType)) {
fileType = 'file'
}
// console.log('fileType===>', fileType);
// console.error('tempFile~~~~~~~~~', tempFile,size/1000/1024+'mb');
const sizeMB = size/1000/1024
if(fileType == 'image' && sizeMB > 2){
return uni.showToast({
title: '图片大小不能超过2mb',
icon: 'none'
});
} else if(sizeMB > 100){
return uni.showToast({
title: '文件大小不能超过100mb',
icon: 'none'
});
}
const data = {};
const fileInfo = {
url,
size,
name
};
if(fileType == 'image'){
const {width,height} = await uni.getImageInfo({src:url});
fileInfo.width = width
fileInfo.height = height
}
data[fileType] = fileInfo
let msg = await this.beforeSendMsg(data,false)
// console.log('~~~beforeSendMsg',msg);
try{
const result = await uniCloud.uploadFile({
filePath: tempFile.path,
cloudPath: Date.now() + uniCloud.getCurrentUserInfo().uid + '.' + name.split('.').pop(),
});
// console.log('result.fileID',result.fileID);
msg.body.url = result.fileID
await this.updateMsg(msg)
this.sendMsg(msg)
}catch(e){
console.error('uploadFile error:',e)
}
});
},
async chooseFileSendMsg(type,_config={}) {
// console.log('type',type);
//先创建发送消息的
let objFn = {
'image':()=>{
uni.chooseImage({
// count:9,
crop:{
"quality":100,
"width":800,
"height":800
},
// sourceType,
// extension,
success:res=> beforeUploadFileAndSendMsg(res,'image'),
"fail":alertFail
});
},
'video':()=>{
uni.chooseVideo({
sourceType: ['camera', 'album'],
success:res=> beforeUploadFileAndSendMsg(res,'video'),
"fail":alertFail
});
},
'all':()=>{
let chooseFile = uni.chooseFile;
// #ifdef MP-WEIXIN
chooseFile = wx.chooseMedia;
// #endif
chooseFile({
type: 'all',
// count:10,
sourceType:['album','camera'],
"success":this.uploadFileAndSendMsg,
"fail":alertFail
})
}
};
objFn[type]();
const _this = this;
function beforeUploadFileAndSendMsg(res,fileType){
// console.log(111,res)
// 视频只能选择单文件,为了参数统一,这里转成数组
if(fileType == 'video'){
// #ifndef H5
res.tempFile = {
size: res.size,
width: res.width,
height: res.height
}
// #endif
res.tempFile.path = res.tempFilePath
res.tempFiles = [res.tempFile]
}
res.tempFiles.forEach(item=>{
//如果没有type,默认为:用户选择的类型
if(!item.fileType){
item.fileType = fileType
}
// 如果没有name,默认为:用户id+随机数+时间戳生成一个
if(!item.name){
item.name = _this.current_uid + Math.random().toString(36).substr(2) + Date.now()
}
})
// console.log(222,res)
_this.uploadFileAndSendMsg(res)
}
function alertFail(res){
console.error('res',res);
// uni.showModal({
// content: JSON.stringify(res),
// showCancel: false
// });
}
},
hideKeyboard() {
// console.log('hideKeyboard');
uni.hideKeyboard();
this.$nextTick(() => {
this.menuIsShow = false
this.emojiIsShow = false
this.isFocus = false
})
},
onInput(e) {
// console.log('onInput',e);
// 记录按下@之后的内容
const enterText = this.isWidescreen ? e.data : e.detail.value[e.detail.cursor - 1]
// console.log('enterText',enterText);
if(this.showAboutUidBox && enterText && enterText != '@'){
setTimeout(()=>{
// 输入法正在输入中
let isComposing = false
// #ifdef H5
isComposing = this.isWidescreen ? document.querySelector('#uni-im-chat-input').isComposing : e.isComposing
// #endif
if (isComposing) {
console.log('输入法正在输入中')
}else{
this.aboutUserKeyword += enterText
}
},0)
}else{
this.aboutUserKeyword = ""
}
// 非宽屏设备 && 非Android端 一旦输入换行符直接发送消息
if (!this.isWidescreen && this.systemInfo.platform != 'android' && this.chatInputContent && this.chatInputContent.indexOf('\n') >= 0) {
e.preventDefault() // 阻止换行,TODO:视乎并没有阻止住
// console.log("有\n",this.chatInputContent, this.chatInputContent.length);
this.beforeSendMsg()
}
},
sendSound(e) {
// console.log('sendSound',e);
this.beforeSendMsg({
sound: e
})
},
async setAnswerMsg(msgId) {
this.answerMsg = this.conversation.msgList.find(msg => msg._id == msgId)
this.isFocus = true
},
async chatInputConfirm() {
if(this.showAboutUidBox && this.memberList.length){
console.log('正在执行选中要@的人,不发送数据')
return
}
if (typeof this.chatInputContent == 'object'){
// 富文本(图文混排、@某人)消息
// 把字符串中带url的链接转为html的a标签的形式。1.将字符串,按是否为标签,切割为数组
const htmlStr = this.chatInputContent.html.split(/(<[^>]+>)/)
// 2.找到不以<开头的字符串内的url并替换为 html的a
.map(str=>str.startsWith('<') ? str : uniIm.utils.replaceUrlToLink(str))
.join('')
// 先插到消息列表
let msg = await this.beforeSendMsg({
"rich-text":uniIm.utils.parseHtml(htmlStr)
},false)
// 上传消息中的图片
let promiseArr = []
msg.body.forEach(async item=>{
if(item.type === 'text'){
item.text = item.text.trim();
}else if(item.name === 'img'){
promiseArr.push(new Promise((resolve,reject)=>{
uni.getImageInfo({
src:item.attrs.src,
success:res=>{
item.attrs.width = res.width
item.attrs.height = res.height
resolve()
},
fail:reject
});
}))
if(item.attrs.src.indexOf('data:image/png;base64,') === 0){
promiseArr.push(new Promise((resolve,reject)=>{
uniCloud.uploadFile({
filePath: item.attrs.src,
cloudPath: Date.now() + uniCloud.getCurrentUserInfo().uid + '.' + name.split('.').pop(),
}).then(res=>{
item.attrs.src = res.fileID
// console.log('上传成功',res);
resolve()
}).catch(e=>{
reject()
})
}))
}
}
})
await Promise.all(promiseArr)
// 传完更新
await this.updateMsg(msg)
// 执行发送
this.sendMsg(msg)
}else{
// 把this.chatInputContent中的&nbsp;变成空格,再把头尾的空格去掉
this.chatInputContent = this.chatInputContent.replace(/&nbsp;/g, ' ').trim()
// 普通消息
await this.beforeSendMsg()
}
},
async beforeSendMsg(param = {},_continue = true) {
// console.log('beforeSendMsg',{param});
let msg = {
type: 'text',
to_uid: this.conversation.friend_uid,
conversation_id: this.conversation.id,
group_id: this.conversation.group_id,
client_create_time: Date.now(),
from_uid: uniCloud.getCurrentUserInfo().uid,
state: 0,
body: this.chatInputContent
}
// 根据传入的参数设置消息类型和内容
for (let key in param) {
if (param[key]) {
msg.type = key
msg.body = JSON.parse(JSON.stringify(param[key]))
}
}
// 如果是文本类型需要做一些处理
if (msg.type === 'text') {
//清除空格
msg.body = msg.body.trim();
// 阻止发送空消息
if (!msg.body.length) {
this.resetChatInput()
return uni.showToast({
title: '不能发送空消息',
icon: 'none'
});
}
// 如果包含\u200b@\u200b 则转为富文本消息
if (msg.body.includes('\u200b@\u200b')) {
msg.type = 'rich-text'
// 把msg.body转成富文本消息;tmpBody按照\u200b@\u200b分割成数组,再按照\u2009分割成数组
let tmpBody = msg.body
msg.body = []
tmpBody.split('\u200b@').forEach(str => {
str.split('\u2009').forEach(item=>{
if(item.includes('\u200b')){
const nickname = item.replace('\u200b','')
let user_id = false
for (let uid in this.group_member) {
if(this.group_member[uid].users.nickname == nickname){
user_id = uid
break
}
}
msg.body.push({
"name": "span",
"attrs": {
"class": "nickname",
user_id,
}
})
}else{
msg.body.push({
type: 'text',
text: item
})
}
})
})
}
}
//如果是回复某一条消息,需要带上相关id
if (this.answerMsg !== false) {
msg.about_msg_id = this.answerMsg._id
}
// 消息列表追加此消息。此时消息状态值为0,表示发送中
let resMsg = this.conversation.msgList.push(msg)
this.resetChatInput()
this.$nextTick(() => {
this.showLast()
// #ifdef APP-NVUE
// 临时方案:解决偶发nvue下不能滚动到最后一条消息的问题
setTimeout(() => {
this.showLast()
}, 800)
// #endif
})
msg.state = 0
// console.error('sendMsg-sendMsg-sendMsg', msg);
// 存到本地数据库
await this.conversation.msgManager.localMsg.add(msg)
// console.log('msg被localMsg.add引用 会新增一个unique_id',msg)
if(_continue){
this.sendMsg(msg);
}else{
return msg;
}
},
resetChatInput() {
this.chatInputContent = ''
this.textareaHeight = 26
// 关闭引用的消息
this.answerMsg = false
},
getCallUid(param){
let aboutNicknameList = []
if( this.isWidescreen){
// #ifdef H5
if(typeof param === 'object'){
let tmpDom = document.createElement('div')
tmpDom.innerHTML = param.html
let nicknameDomList = tmpDom.querySelectorAll('.nickname');
for (let i = 0; i < nicknameDomList.length; i++) {
aboutNicknameList.push(nicknameDomList[i].innerText.substring(1))
}
}
// #endif
}else{
// 获取所有@的用户昵称
aboutNicknameList = param.split('\u200b@\u200b').filter(str => str.includes('\u2009')).map(
str => {
let index = str.indexOf('\u2009')
return str.slice(0, index)
})
}
// console.log('aboutNicknameList',aboutNicknameList);
let ids = []
aboutNicknameList.forEach(nickname => {
for (let uid in this.group_member) {
if (this.group_member[uid].users.nickname == nickname) {
ids.push(uid)
}
}
})
// console.log('ids',ids);
return ids
},
sendMsg(msg, callback) {
if(this.conversation.source){
msg.chat_source = this.conversation.source
}
// console.log('sendMsg-sendMsg-sendMsg', msg);
const uniImCo = uniCloud.importObject('uni-im-co', {
customUI: true
});
// 接收消息的appId,默认为当前应用的appId。如果你是2个不同appId的应用相互发,请修改此值为相对的appId
msg.appId = this.systemInfo.appId
// 拿到当前消息的索引值
let index = this.conversation.msgList.findIndex(i => i.unique_id == msg.unique_id)
// 生成新对象,否则不触发更新
msg = Object.assign({}, msg)
// 检查内容不是包含 个推 两个字,有则 改成 个 + 零宽字符 + 推
let tmpBody = JSON.stringify(msg.body)
if(tmpBody.includes('个推')){
msg.body = JSON.parse(tmpBody.replace(/个推/g,'个\u200b推'))
}
uniImCo.sendMsg(msg)
.then(async e => {
// console.log('uniImCo.sendMsg',{e,msg});
msg.state = e.errCode === 0 ? 100 : -100;
msg.create_time = e.data.create_time;
msg._id = e.data._id;
await this.updateMsg(msg)
})
.catch(async e => {
uni.showModal({
content: e.message,
showCancel: false,
confirmText: '关闭',
});
console.error('uniImCo.sendMsg error:', e.errCode, e.message);
// 必须要有create_time的值,否则indexDB通过创建时间索引找不到数据
msg.create_time = Date.now();
msg.state = -200;
await this.updateMsg(msg)
})
.finally(e => {
if (callback) {
callback(e);
}
});
},
// 更新消息
async updateMsg(msg){
if(!msg.conversation_id){
throw 'msg.conversation_id不能为空'
}
if(!msg.unique_id){
throw 'msg.unique_id不能为空'
}
let conversation = await uniIm.conversation.get(msg.conversation_id)
let index = conversation.msgList.findIndex(_msg => _msg.unique_id == msg.unique_id)
if(index === -1){
throw 'updateMsg msg 不存在'
}
/* TODO: splice 更新方式会把原本的 msg 对象从数组中踢出,导致已经渲染的消息组件无法响应后续的变更(比如 read_msg),
所以这里使用 merge 更新方式,虽然此方式在 vue2 中有问题。*/
let oldMsg = conversation.msgList[index]
Object.assign(oldMsg, msg)
// conversation.msgList.splice(index, 1, msg)
conversation.msgManager.localMsg.update(msg.unique_id, msg)
// console.log('change after',conversation.msgList[index]);
},
retriesSendMsg(msg) {
uni.showLoading({
mask: true
});
// console.log('retriesSendMsg', msg);
msg.isRetries = true
this.sendMsg(msg, e => {
uni.hideLoading();
});
},
showLast(duration = 300) {
let msgListRef = this.$refs['msg-list']
if (msgListRef) {
msgListRef.showLast(duration)
}
},
setCallAboutUidBefore(uid){
// 当前输入框已经@了的用户id 要过滤掉
let callUidList = this.getCallUid(this.chatInputContent)
if(callUidList.includes(uid)){
console.log('此用户id已经@过');
uni.showToast({
title: '此用户已经@过',
icon: 'none'
});
}else{
this.setCallAboutUid(uid)
}
},
setCallAboutUid(uid) {
// 替换光标位置到左边最后一个@符号数据 为: \u200b@\u200b + nickname
// 光标位置到左边最后一个@符号的位置
const nickname = this.group_member[uid].users.nickname
if(this.isWidescreen){
// #ifdef H5
// web 宽屏设备
const callString = `<span class="nickname" user_id="${uid}" contenteditable="false">@${nickname}</span>\u2009`
if(this.showAboutUidBox){
this.$refs["chat-input"]?.deleteLeftChar(1 + this.aboutUserKeyword.length)
}
// 在光标位置插入@昵称
this.$refs["chat-input"]?.addHtmlToCursor(callString)
// TODO:解决在消息列表中的@昵称点击事件,确弹出了选择@成员列表的问题
this.$nextTick(()=>this.showAboutUidBox = false)
// #endif
}else{
// 窄屏设备
const callString = `\u200b@\u200b${nickname}\u2009`
// 找到从光标位置到左边的@符号的位置
const atCharIndex = this.chatInputContent.slice(0,currentCursor+1).lastIndexOf('@')
// console.log('this.chatInputContent.slice(0,currentCursor)',this.chatInputContent.slice(0,currentCursor+1))
// console.log('atCharIndex',atCharIndex);
// console.log('this.chatInputContent',this.chatInputContent);
if(atCharIndex == -1){
console.error('没有找到@符号');
}
// 获取焦点
this.isFocus = true
// 删除在选用户时敲的字符和@符号
const newchatInputContent = this.chatInputContent.slice(0, atCharIndex) + callString + this.chatInputContent.slice(currentCursor+1)
// 光标位置移到@昵称的后面
const newCursor = atCharIndex + callString.length
// console.log('newchatInputContent',newchatInputContent);
// console.log('newCursor',newCursor);
this.setChatInputContent(newchatInputContent,newCursor)
}
// 关闭@成员列表
this.showAboutUidBox = false
},
linechange(e) {
//console.log(e.detail);
let {
height,
lineCount
} = e.detail;
// console.log(height,lineCount);
if (lineCount === 1) {
this.textareaHeight = 26;
} else if (height <= 100) {
this.textareaHeight = height - 2;
}
},
async showControl({
msgId,
msgContentDomInfo
}) {
const msg = this.conversation.msgList.find(msg => msg._id === msgId)
let isSelf = msg.from_uid == uniCloud.getCurrentUserInfo().uid
this.$refs['msg-popup-control'].show({isSelf,msg,msgContentDomInfo})
/*
let controlData = {
msg,
isInTop: false
};
let metrics = uniIm.utils.getScreenMetrics()
if (isSelf) {
controlData.left = 'unset'
controlData.right = metrics.pageWidth - msgContentDomInfo.right
} else {
controlData.left = msgContentDomInfo.left + msgContentDomInfo.width / 2
controlData.right = 'unset'
}
controlData.isInTop = msgContentDomInfo.top > 60
if (controlData.isInTop) {
// #ifdef H5
let n = -20
// #endif
// #ifndef H5
let n = -65
// #endif
controlData.top = msgContentDomInfo.top + n
} else {
// #ifdef APP
let n = 8
// #endif
// #ifdef H5
let n = 55
// #endif
// #ifdef MP
let n = 10
// #endif
controlData.top = msgContentDomInfo.bottom + n
}
console.log('msgContentDomInfo',msgContentDomInfo)
controlData.msgWidth = msgContentDomInfo.width
this.$refs['msg-popup-control'].show(controlData)
*/
},
shareMsg(msgList,merge = false) {
this.$refs['share-msg'].open(msgList,merge)
},
toolBarNext(){
uni.showToast({
title: '暂不支持',
icon: 'none',
duration: 2000
});
this.chooseMoreMsg = false
},
clickMenu(index, e) {
// console.log('clickMenu-',index);
if (index < 2) {
this.chooseFileSendMsg(index === 0 ? 'image' : 'video')
}
if (index === 2) {
// #ifdef APP-NVUE
return uni.showToast({
title: '暂不支持,发送文件',
icon: 'none'
});
// #endif
this.chooseFileSendMsg('all')
}
e.stopPropagation()
},
clickEmojiItem(emojiUniCode, e) {
this.chatInputContent += emojiUniCode
e.stopPropagation()
},
tapUnreadCount() {
//点击未读消息文字按钮事件
if (this.isWidescreen) {
// this.$emit('tapUnreadCount') //点击后会话列表自动滚动 置顶 待读项
// console.log('tapUnreadCount');
} else {
uni.navigateBack();
}
},
updateNavTitle(){
this.navTitle = this.conversation.title
if (this.conversation.group_id) {
this.navTitle += '(' + Object.keys(this.conversation.group_member).length + ")";
}
}
},
onNavigationBarButtonTap(e) {
if (e.index === 0) {
if (this.conversation.group_id) {
uni.navigateTo({
url: "/uni_modules/uni-im/pages/group/info?conversation_id=" + this.conversation.id
})
} else {
// console.log(this.conversation,6565);
uni.navigateTo({
url: `/uni_modules/uni-im/pages/chat/info?user_id=${this.conversation.friend_uid}&conversation_id=${this.conversation.id}`
})
// uni.showToast({
// title: '仅群里可见,详细信息',
// icon: 'none'
// });
}
}
// uni.navigateBack();
}
};
</script>
<style lang="scss" scoped>
/* #ifndef APP-NVUE */
view {
display: flex;
flex-direction: column;
box-sizing: border-box;
}
page {
background-color: #efefef;
}
/* #endif */
.page {
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
/* #ifndef APP-NVUE */
height: calc(100vh - 45px);
/* #endif */
/* #ifndef APP-NVUE */
background-color: #efefef;
/* #endif */
// border: solid 5px #2faf4c;
}
.chat-foot,.disable-input{
flex-direction: column;
border-top: 1rpx solid #BBBBBB;
background-color: #F7F7F7;
width: 750rpx;
position: fixed;
bottom: 0;
/* #ifndef APP-NVUE */
z-index: 9;
// overflow: hidden;
/* #endif */
}
.disable-input{
padding: 20px;
text-align: center;
justify-content: center;
color: #777777;
}
/* #ifdef H5 */
.pc {
// .pc内的元素只有pc端打开才显示,样式在index页面
display: none;
}
.vue-codemirror {
position: fixed;
top: 100px;
left: 50%;
width: 500px;
}
/* #endif */
/* #ifdef H5 */
.chat-foot {
border: none;
}
/* #endif */
/* #ifndef APP-NVUE */
.chat-foot * {
// border: solid 1px red;
}
/* #endif */
.textarea-box {
background-color: #ffffff;
padding: 10px;
width: 450rpx;
border-radius: 10px;
}
.textarea {
width: 400rpx;
padding: 0;
background-color: #ffffff;
color: #666666;
//padding: 20rpx;
font-size: 32rpx;
}
.tip-view {
position: fixed;
top: 100px;
width: 750rpx;
align-items: center;
color: #999999;
}
.tip-null-msg {
color: #999999;
font-size: 14px;
}
.beforeSendMsg {
color: #ffffff;
font-size: 24rpx;
border-radius: 6px;
background-color: #2faf4c;
// width: 80rpx;
height: 28px;
line-height: 28px;
text-align: center;
}
.icon {
width: 70rpx;
justify-content: center;
align-items: center;
}
.loadMore {
line-height: 80rpx;
height: 80rpx;
text-align: center;
width: 750rpx;
color: #adb3b7;
font-size: 12px;
}
/* #ifndef APP-NVUE */
.unread_count {
position: absolute;
top: -35px;
left: 70rpx;
z-index: 999;
background-color: #dfe2e9;
padding: 0 14rpx;
height: 14px;
line-height: 14px;
border-radius: 9px;
color: #0c1013;
font-size: 12px;
margin-top: 3px;
}
/* #endif */
.input-box {
flex-direction: row;
padding: 10rpx 18rpx;
justify-content: space-around;
align-items: center;
}
.menu {
padding: 36rpx;
width: 750rpx;
border-top: solid 1px #ededed;
flex-direction: row;
flex-wrap: wrap;
}
.menu-item,
.menu-item-icon {
width: 160rpx;
height: 160rpx;
justify-content: space-around;
align-items: center;
}
.menu-item-icon {
width: 80rpx;
height: 80rpx;
background-color: #ffffff;
color: #6F6F6F;
border-radius: 10px;
}
.menu-item-text {
font-size: 12px;
}
.emojiListBox {
width: 750rpx;
padding: 27rpx;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
}
.emoji-item {
text-align: center;
font-size: 65rpx;
width: 87rpx;
height: 87rpx;
justify-content: center;
align-items: center;
/* #ifndef APP-NVUE */
display: inline-block;
/* #endif */
}
.uni-im-sound {
position: absolute;
top: 0;
left: 0;
z-index: 999;
}
.answer-msg {
width: 450rpx;
padding: 2px 5px;
margin: 5px 0 3px 0;
background-color: #efefef;
color: #6a6a6a;
border-radius: 5px;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
/* #ifdef H5 */
.answer-msg ::v-deep .uni-icons {
cursor: pointer;
}
/* #endif */
.answer-msg-text {
/* #ifndef APP-NVUE */
white-space: nowrap;
overflow: hidden;
/* #endif */
lines: 2;
text-overflow: ellipsis;
font-size: 12px;
}
/* #ifdef H5 */
.send-btn-box {
flex-direction: row;
justify-content: end;
padding: 0 10px 10px 0;
margin-top: 5px;
}
.send-btn-box .send {
font-size: 12px;
padding: 0 20px;
}
.send-btn-box .send-btn-tip {
color: #919396;
margin-right: 8px;
font-size: 12px;
line-height: 28px;
}
/* #endif */
.member-list {
position: fixed;
width: 500rpx;
height: 200px;
bottom: 100px;
left: 125rpx;
z-index: 9999;
background-color: #ffffff;
overflow: hidden;
border-radius: 15px;
box-shadow: 0 0 100px rgba(0, 0, 0, 0.2);
}
.member-list-item {
overflow: hidden;
height: 40px;
font-size: 14px;
line-height: 40px;
padding-left: 15px;
border-radius: 10px;
width: 500rpx;
margin: 5px;
/* 解决css特殊情况下 margin 重叠的问题 */
margin-bottom: 10px;
text-align: left;
}
.member-list-item-active {
background-color: #efefef;
}
.member-list-mark {
position: fixed;
top: 0;
left: 0;
width: 750rpx;
flex: 1;
height: 9000px;
/* #ifndef APP-NVUE */
width: 100vw !important;
height: 100vh !important;
z-index: 9998;
/* #endif */
background-color: rgba(0, 0, 0, 0.1);
}
/* #ifdef H5 */
@media screen and (min-device-width:960px){
.member-list {
width: 260px;
left: auto;
right: calc(50vw - 300px);
bottom: 300px;
}
.member-list-item {
width: 250px;
cursor: pointer;
}
}
/* #endif */
.toolbar{
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 260px;
background-color: #ededed;
border-top: solid 1px #ededed;
flex-direction: row;
justify-content: space-around;
align-items: center;
z-index: 999;
}
.toolbar .item {
align-items: center;
}
/* #ifdef H5 */
.toolbar ::v-deep .uni-icons {
cursor: pointer;
}
/* #endif */
.toolbar .icons-box {
background-color: #fff;
justify-content: center;
width: 60px;
height: 60px;
border-radius: 100px;
margin-bottom: 10px;
}
.camera-filled-box {
background-color: #3b81fd;
justify-content: center;
width: 26px;
height: 26px;
border-radius: 100px;
}
</style>
\ No newline at end of file
<template>
<view class="chat-fragment">
<view class="head">
<uni-icons class="btn-back" @click="onBack" type="arrow-left" size="20"></uni-icons>
</view>
<scroll-view
class="message-list"
scroll-y
:scroll-into-view="autoScrollToEl"
@scrolltoupper="onScrollToUpper"
@scrolltolower="onScrollToLower"
>
<uni-im-msg
v-for="(msg, index) in msgList"
:key="msg._id"
:id="`msg-${msg._id}`"
:msg="msg"
:equalPrevTime="equalPrevTime(index)"
no-jump
:class="{highlight:msg.highlight}"
@loadMore="cb => cb()"
/>
</scroll-view>
</view>
</template>
<script>
/**
* chat-fragment 组件,渲染会话中一个片段的消息列表,用于显示某条消息搜索结果的上下文列表。
*
* @module
*/
const uniImCo = uniCloud.importObject("uni-im-co", {
customUI: true
})
import uniIm from '@/uni_modules/uni-im/sdk/index.js';
export default {
emits: ['close'],
props: {
entry: {
type: Object
}
},
data() {
// 因为要修改 msg 对象的属性值,所以 clone 一下,以切断响应性,避免干扰原数据
let { ...cloneEntry } = this.entry
cloneEntry.highlight = true
return {
msgList: [cloneEntry],
hasMoreBackward: true,
skipBackward: this.entry.create_time,
hasMoreForward: true,
skipForward: this.entry.create_time,
autoScrollToEl: '',
};
},
mounted() {
this.loadDataForward(() => {
this.loadDataBackward(() => {
this.autoScrollToEl = 'msg-' + this.entry._id
}, 15)
}, 15)
},
methods: {
async loadDataForward(afterLoaded, limit = 30) {
this.loading = true
let result = await uniImCo.getFragmentMessageOfConversation({
conversation_id: this.entry.conversation_id,
skip: this.skipForward,
limit,
forward: true,
})
this.msgList.push(...result.data)
this.hasMoreForward = result.hasMore
this.skipForward = result.skip
this.loading = false
this.$nextTick(afterLoaded)
},
async loadDataBackward(afterLoaded, limit = 30) {
this.loading = true
let result = await uniImCo.getFragmentMessageOfConversation({
conversation_id: this.entry.conversation_id,
skip: this.skipBackward,
limit,
forward: false,
})
this.msgList.unshift(...result.data.reverse())
this.hasMoreBackward = result.hasMore
this.skipBackward = result.skip
this.loading = false
this.$nextTick(afterLoaded)
},
onScrollToUpper(evt) {
if (this.loading) return
if (!this.hasMoreBackward) return
let elId = 'msg-' + this.msgList[0]._id
this.autoScrollToEl = ''
this.loadDataBackward(() => {
this.autoScrollToEl = elId
})
},
onScrollToLower(evt) {
if (this.loading) return
if (!this.hasMoreForward) return
this.loadDataForward(() => {})
},
onBack() {
this.$emit('close')
},
equalPrevTime(index) {
if (index === 0) {
return false
} else if (index == this.msgList.length - 1) {
return false
} else {
const getFriendlyTime = (msg) => {
return uniIm.utils.toFriendlyTime(msg.create_time || msg.client_create_time)
}
return getFriendlyTime(this.msgList[index]) == getFriendlyTime(this.msgList[index - 1])
}
},
}
}
</script>
<style>
.chat-fragment {
padding: 5px;
background-color: #efefef;
}
.message-list {
/* #ifndef APP-NVUE */
height: calc(100% - 30px);
/* #endif */
}
.head {
flex-direction: row;
border-bottom: 1px solid #ddd;
}
.btn-back {
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
.highlight {
background-color: #f9f3de;
}
</style>
<template>
<view class="container">
<uni-list :border="false" class="list">
<uni-list-chat
:avatar="friend_info.avatar_file ? friend_info.avatar_file.url:'/uni_modules/uni-im/static/avatarUrl.png'"
:title="friend_info.nickname"
:note="friend_info.nickname != friend_info.email ? friend_info.email : ''"
/>
<uni-list-item
title="消息免打扰"
:switch-checked="conversation.mute"
:show-switch="true"
@switchChange="changeConversationMute"
/>
<!-- #ifdef H5 -->
<!-- 发送名片消息(仅内部人员可用) -->
<uni-list-item
v-if="uniIDHasRole('staff')"
:link="true"
title="发送他(她)的名片"
@click="sendNameCard"
/>
<!-- #endif -->
<!-- 选择某个用户创建群聊(仅dcloud员工可用) -->
<uni-list-item
v-if="uniIDHasRole('staff') && friend_uid != currentUid"
:link="true"
title="选此用户创建群聊"
@click="createGroup"
/>
<template v-for="item in UserinfoMenu" :key="item.component.name">
<component :is="item.component" :conversation="conversation" cementing="UserinfoMenu" />
</template>
</uni-list>
<button
v-if="isFriend"
class="btn"
plain
type="warn"
@click="deteleFriend"
>
删除好友
</button>
<!-- #ifdef H5 -->
<uni-im-share-msg
ref="share-msg"
no-msg-list
no-comment
/>
<!-- #endif -->
</view>
</template>
<script>
const db = uniCloud.database();
import uniIm from '@/uni_modules/uni-im/sdk/index.js';
import { ref,markRaw } from "vue"
export default {
data() {
// 调用扩展点,扩展程序可以在消息输入框新增一个工具类的项
let UserinfoMenu = uniIm.extensions
.invokeExts("userinfo-menu-extra",)
.filter((result) => result && result.component)
.map((result) => {
return {
component: markRaw(result.component),
props: result.props
};
});
return {
UserinfoMenu,
conversation: {},
friend_uid: '',
friend_info: {
username: '',
nickname: '',
avatar_file: {
url: ''
}
}
}
},
props: {
conversation_id: {
default () {
return false
}
},
},
computed: {
...uniIm.mapState(['isWidescreen']),
isFriend() {
let friendList = uniIm.friend.get()
return friendList.find(i => i._id == this.friend_uid)
},
currentUid() {
return uniCloud.getCurrentUserInfo().uid
}
},
async onLoad(options) {
this.load(options)
},
async mounted() {
if (uniIm.isWidescreen) {
this.load({ conversation_id: this.conversation_id })
}
},
methods: {
async load(options) {
console.log('options',options);
// 如果只传了user_id,需要先获取conversation_id
if(options.user_id && !options.conversation_id){
options.conversation_id = await uniIm.utils.getConversationId(options.user_id)
console.log('options.conversation_id',options.conversation_id);
}
let conversation = await uniIm.conversation.get(options.conversation_id)
this.conversation = conversation
options.user_id = conversation.friend_uid
this.friend_uid = options.user_id
let field = '_id,nickname,avatar_file'
if (this.uniIDHasRole('staff')) {
field += ',email'
}
let res = await db.collection('uni-id-users')
.doc(this.friend_uid)
.field(field)
.get()
// console.log("res: ",res);
this.friend_info = res.result.data[0]
},
changeConversationMute() {
console.log('changeConversationMute',this.conversation)
this.conversation.changeMute()
},
async deteleFriend() {
uni.showModal({
title: '确认要删除好友吗',
content: '此操作不可撤销',
showCancel: true,
cancelText: '取消',
confirmText: '确认',
complete: async (e) => {
if (e.confirm) {
uni.showLoading({
mask: true
});
try {
await db.collection('uni-im-friend').where({
friend_uid: this.friend_uid,
user_id: uniCloud.getCurrentUserInfo().uid
}).remove()
// 收到push消息后store会自动,将此用户从列表中移除
uni.navigateBack({ delta: 2 })
} catch (e) {
uni.showModal({
content: JSON.stringify(e.message),
showCancel: false
});
}
uni.hideLoading()
}
}
});
},
async createGroup(){
console.log('createGroup')
const user_ids = [this.friend_uid]
const uniImCo = uniCloud.importObject("uni-im-co")
let res = await uniImCo.chooseUserIntoGroup({
user_ids
})
uni.$emit('uni-im-toChat','group_'+res.data.group_id)
},
// #ifdef H5
async sendNameCard() {
let msg = {
type: 'userinfo-card',
body: {
user_id: this.conversation.friend_uid,
name: this.conversation.title,
},
from_uid: uniCloud.getCurrentUserInfo().uid,
create_time: Date.now(),
}
this.$refs['share-msg'].open([msg], false)
}
// #endif
}
}
</script>
<style lang="scss" scoped>
.container {
width: 750rpx;
align-items: center;
/* #ifndef APP-NVUE */
height: 100vh;
/* #endif */
flex: 1;
background-color: #fff;
}
.list {
width: 750rpx;
border-bottom: 1px solid #ececec;
}
/* #ifdef H5 */
.list ::v-deep .uni-list-chat__content-note,
.list ::v-deep .big{
cursor: text;
user-select: text;
}
.list ::v-deep .uni-list-item + .uni-list-item{
cursor: pointer;
}
/* #endif */
.btn {
width: 600rpx;
/* height: 45px; */
text-align: center;
line-height: 45px;
border-radius: 20rpx;
}
</style>
\ No newline at end of file
<template>
<view v-if="url" class="video-box" :class="{'is-float-mode':mode == 'float'}">
<view class="mask" v-if="mode == 'float'"></view>
<video @click="showCloseBtnFn" :src="url" :autoplay="true" :page-gesture="true" :show-mute-btn="true" :show-fullscreen-btn="mode == 'float'" class="video"></video>
<uni-icons v-if="showCloseBtn" @click="close" size="30px" color="#FFFFFF" type="closeempty" class="close-icon"></uni-icons>
</view>
</template>
<script>
import uniIm from '@/uni_modules/uni-im/sdk/index.js';
export default {
data() {
return {
url:"",
showCloseBtn:true,
// 全屏模式和小窗模式,fullscreen为全屏模式,float为小窗模式
mode: 'fullscreen',
};
},
onLoad({url}) {
// console.log({url});
this.url = url
setTimeout(()=> {
this.showCloseBtn = false
}, 1000);
},
mounted(){
// console.log('mounted');
uni.$on('uni-im-playVideo',(url)=>{
this.mode = 'float'
this.url = url
this.showCloseBtn = true
})
// 监听esc按键,关闭视频
// #ifdef H5
uniIm.utils.appEvent.onKeyDown(evt => this.onDownEscapeKey, {
order: 1000,
match: {
key: 'Escape',
altKey: false,
ctrlKey: false,
shiftKey: false,
}
})
// #endif
},
beforeDestroy(){
// #ifdef H5
uniIm.utils.appEvent.offKeyDown(this.onDownEscapeKey)
// #endif
},
methods:{
onDownEscapeKey() {
if (this.url.length) {
this.close()
}
return true
},
close(){
if(this.mode == 'fullscreen'){
uni.navigateBack()
}else{
this.url = ''
}
},
showCloseBtnFn(){
// console.log('showCloseBtnFn');
this.showCloseBtn = true
if(this.mode == 'fullscreen'){
setTimeout(()=> {
this.showCloseBtn = false
}, 5000);
}
}
}
}
</script>
<style lang="scss">
.video-box,.video{
width: 750rpx;
/* #ifndef APP-NVUE */
width: 100vw;
height: 100vh;
/* #endif */
flex: 1;
}
.close-icon{
position: absolute;
top: 80rpx;
left: 30rpx;
z-index: 9999;
/* #ifndef APP-NVUE */
text-shadow: 0 0 15px black;
cursor: pointer;
/* #endif */
}
.is-float-mode,
.is-float-mode .video{
position: fixed;
top: 10vh;
/* #ifndef APP-NVUE */
left: calc(10vw + 250px);
width: calc(80vw - 220px) !important;
height: 80vh !important;
z-index: 9999;
/* #endif */
}
.mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9999;
}
</style>
<template>
<view class="code-view">
<!-- 默认不启用代码浏览模块,如有需要请关闭注释 -->
<uni-im-code-view :msg="msg" :showFullBtn="false"></uni-im-code-view>
</view>
</template>
<script>
import uniImCodeView from '@/uni_modules/uni-im/components/uni-im-msg/types/code.vue'
export default {
components: {
uniImCodeView
},
data() {
return {
msg: {
type: "code",
body: ""
}
}
},
onLoad({
code
}) {
console.log(code)
this.msg.body = JSON.parse(decodeURIComponent(code));
},
methods: {}
}
</script>
<style lang="scss">
.text-box,
.code-view {
flex: 1;
width: 750rpx;
/* #ifndef APP-NVUE */
width: 100vw;
height: 100vh;
/* #endif */
}
</style>
\ No newline at end of file
<template>
<view>
<uni-nav-bar color="#999" :fixed="true" background-color="#ffffff" status-bar left-icon="left" @clickLeft="back">
<view class="segmented-box">
<uni-segmented-control :current="current" :values="items" @clickItem="setActiveIndex" styleType="button" activeColor="#5fc08e" style="width:120px;"></uni-segmented-control>
</view>
</uni-nav-bar>
<view class="content">
<uni-search-bar :placeholder="activeIndex?'搜索群名称/群号':'搜索手机号/用户名/用户昵称'" :radius="100"
class="search-bar"
bgColor="#eeeeee"
v-model="keyword"
@confirm="doSearch"
@focus="searchFocus = true"
@blur="searchFocus = false"
@cancel="doClear"
@clear="doClear"
></uni-search-bar>
<view v-if="activeIndex === 0">
<!-- 搜索 -->
<uni-list v-if="usersList.length">
<uni-list-chat v-for="(item,index) in usersList" :key="index"
:title="item.nickname" :avatarCircle="true"
:avatar="item.avatar_file&&item.avatar_file.url ? item.avatar_file.url : '/uni_modules/uni-im/static/avatarUrl.png'"
>
<text v-if="item.isFriend" class="chat-custom-right grey">已添加</text>
<text v-else @click="addUser(index)" class="chat-custom-right">加为好友</text>
</uni-list-chat>
<!--
v-if="keyword.length"
<template v-else>
<uni-list-item v-for="(tab,index) in tabs" :key="index"
class="tab-item" :title="tab.title"
:to="tab.url" showArrow :border="false"
></uni-list-item>
</template> -->
</uni-list>
<uni-im-load-state v-else :status="loading?'loading':(hasMore?'hasMore':'noMore')"></uni-im-load-state>
</view>
<view v-if="activeIndex === 1">
<uni-list v-if="groupList.length">
<uni-list-chat v-for="(item,index) in groupList" :key="index"
:title="item.name"
:avatar="item.avatar_file && item.avatar_file.url ? item.avatar_file.url : '/uni_modules/uni-im/static/avatarUrl.png'"
>
<text v-if="item.isExist" class="chat-custom-right grey">已加入</text>
<text v-else @click="addUser(index)" class="chat-custom-right">申请加入</text>
</uni-list-chat>
</uni-list>
<uni-im-load-state v-else :status="loading?'loading':(hasMore?'hasMore':'noMore')"></uni-im-load-state>
</view>
</view>
<uni-popup ref="popup" type="dialog">
<uni-popup-dialog mode="input" :title="activeIndex?'申请加群':'申请添加好友'"
placeholder="请输入验证信息" confirmText="发送" message="成功消息"
:duration="2000" :before-close="true" :value="value"
@close="close" @confirm="confirm"
></uni-popup-dialog>
</uni-popup>
</view>
</template>
<script>
import uniIm from '@/uni_modules/uni-im/sdk/index.js';
const db = uniCloud.database();
export default {
data() {
return {
current:0,
loading:true,
hasMore: false,
activeIndex:0,
value:'',
items: ['找人', '找群'],
searchFocus:false,//是否展示搜索列表
keyword:'',
tabs:[
{
'title':'添加手机联系人',
'url':''
},
{
'title':'扫一扫加好友',
'url':''
},
{
'title':'查找陌生人',
'url':''
}
],
usersData: [],
checkIndex:'',//申请加的群index
groupData:[]
}
},
computed: {
usersList() {
let current_uid = uniCloud.getCurrentUserInfo().uid
let friendList = uniIm.friend.dataList
return this.usersData.map(item => {
const isFriend = friendList.find(i=>i._id == item._id)
return {
...item,
isFriend
}
})
},
groupList() {
let groupList = uniIm.group.dataList
console.log('已经加入的groupList',groupList);
console.log('查到的groupList',this.groupData);
// return this.groupData.filter(item=> groupList.find(i=>i._id == item._id))
return this.groupData.map(item => {
const isExist = groupList.find(i=>i._id == item._id)
return {
...item,
isExist
}
})
}
},
onLoad(param) {
this.setParam(param)
},
methods: {
setParam(param){
console.log("param: ",param);
if(param.group_id){
this.current = 1
this.setActiveIndex({currentIndex: 1})
this.keyword = param.group_id
return this.doSearch()
}
this.getUserList()
this.getGroupsList()
},
async getGroupsList(){
const limit = 1000
const skip = this.groupData.length/limit + 1
const res = await db.collection('uni-im-group')
.where(`"user_id" != "${uniCloud.getCurrentUserInfo().uid}"`)
.field('_id,name,avatar_file')
.orderBy('create_date', 'desc')
.limit(limit)
.skip(skip)
.get()
// console.log("uni-im-group: ",res);
if(res.result.data.length){
this.loading = false
this.hasMore = true
this.groupData = res.result.data
}
},
async getUserList(){
try{
let res = await db.collection('uni-id-users')
.field('_id,nickname,avatar_file')
.get()
let data = res.result.data
// console.log("data: ",data);
if(data.length){
this.loading = false
this.hasMore = true
this.usersData = data
}
}catch(e){
console.log(e);
}
},
back() {
uni.navigateBack()
},
async doSearch(e){
console.log("doSearch: ",e,this.keyword);
uni.showLoading({
title: '搜索中'
})
if(this.activeIndex){
let res = await db.collection('uni-im-group')
.where(`
/${this.keyword}/.test(name) ||
"_id" == "${this.keyword}"
`)
.get()
console.log(res);
this.groupData = res.result.data
}else{
const whereString = [
"_id",
"username",
"nickname",
"email",
"mobile"
].map(item => `"${item}" == "${this.keyword}"`).join(' || ')
// console.log('whereString',whereString);
let res = await db.collection('uni-id-users')
.where(whereString)
.field('_id,nickname,avatar_file')
.get()
// tip:用户表数据少,或者已做好优化,可以使用:/${this.keyword}/.test(nickname) 模糊匹配用户昵称
console.log(res);
this.usersData = res.result.data
}
uni.hideLoading()
},
doClear() {
if(this.keyword){
this.keyword = ''
this.usersData = []
this.groupData = []
this.getUserList()
this.getGroupsList()
}
},
setActiveIndex(e) {
// console.log("activeIndex: ",e);
if (this.activeIndex != e.currentIndex) {
this.activeIndex = e.currentIndex;
}
},
addUser(index){
this.checkIndex = index
this.$refs.popup.open()
},
async confirm(value){
// if(!value){
// uni.showToast({
// title: '验证信息不能为空!',
// icon:'none'
// });
// return
// }
// console.log('提供的验证信息',value);
this.value = value
this.$refs.popup.close()
if(this.activeIndex === 0){
//添加好友
const uniImCo = uniCloud.importObject("uni-im-co")
await uniImCo.addFriendInvite({
"to_uid": this.usersList[this.checkIndex]._id,
"message": this.value
}).then((res)=>{
console.log("res: ",res);
uni.showToast({
title: '已申请',
icon: 'none'
});
}).catch((err) => {
uni.showModal({
content: err.message || '请求服务失败',
showCancel: false
})
})
}else{
// console.log('1233123132123132',this.groupData,this.checkIndex);
//申请加群
db.collection('uni-im-group-join').add({
"group_id":this.groupList[this.checkIndex]._id,
"message":this.value
}).then((res) => {
console.log("res: ",res);
uni.showToast({
icon: 'none',
title: '已申请'
})
}).catch((err) => {
uni.showModal({
content: err.message || '请求服务失败',
showCancel: false
})
})
}
setTimeout(()=> {
this.value = ''
}, 100);
},
close(){
console.log('取消了');
this.$refs.popup.close()
}
}
}
</script>
<style lang="scss" scoped>
.segmented-box{
flex: 1;
justify-content: center;
align-items: center;
}
/* #ifdef H5 */
@media screen and (min-device-width:960px){
.content {
height: calc(100vh - 175px);
overflow: auto;
}
::v-deep .uni-navbar__header-btns-left,
::v-deep .uni-navbar__placeholder,
{
display: none;
}
::v-deep .uni-navbar--fixed{
position: relative;
left: 0
}
}
/* #endif */
.content{
/* #ifdef APP-NVUE */
margin-top: 75px;
/* #endif */
}
.tab-item{
border-bottom: #f5f5f5 solid 1px;
height:60px;
justify-content: center;
padding: 0 15rpx;
}
.uni-list-item{
width:720rpx;
}
.background{
background-color: #f5f5f5;
}
.grey{
color: #ddd;
}
.chat-custom-right {
width:70px;
height:30px;
line-height: 30px;
color: #666;
font-size: 12px;
text-align: center;
background-color: #efefef;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
border-radius: 100px;
}
.border{
border: #ddd solid 1px;
}
.state-text{
text-align: center;
font-size: 28rpx;
}
</style>
<template>
<view @click="hiddenDeleteBtn" class="contacts-pages">
<uni-list :border="false" class="menu-list-box" v-if="showMenu">
<uni-list-item v-for="(menu,menuIndex) in menuList" :key="menuIndex" :title="menu.title" link
@click="openPages(menu)" :showBadge="true" :class="{activeMenu:isOpenItemTitle === menu.title}">
<template v-slot:header>
<view class="slot-icon-box green">
<image class="slot-icon" :src="'/uni_modules/uni-im/static/noticeIcon/' + menu.srcName + '.png'"
mode="widthFix"></image>
</view>
</template>
</uni-list-item>
<uni-list-item v-for="(item,index) in noticeList" :key="item.id" :title="item.title" :showBadge="true"
:badgeText="item.badge" :badgeStyle="item.badgeStyle" link @click="openPages(item)" :border="false"
:class="{activeMenu:isOpenItemTitle === item.title}">
<template v-slot:header>
<view class="slot-icon-box blue">
<image class="slot-icon" :src="item.icon" mode="widthFix"></image>
</view>
</template>
</uni-list-item>
</uni-list>
<text class="title">好友列表</text>
<uni-list v-if="showUser" :border="false" class="user-list-box" :scroll-y="true">
<uni-list-item v-for="(item, index) in friendList" :key="item._id" :customStyle="{padding:0}"
class="user-list-item">
<template v-slot:body>
<scroll-view scroll-x="true" @scroll="scroll" :scroll-left="activeIndex === index ?'':scrollLeft[index]"
:show-scrollbar="false" :scroll-with-animation="true" class="user-list-item-scroll-view">
<view class="user-list-item-scroll-view-item" @click="toChat(item)"
@touchstart.passive="activeIndex = index">
<image class="avatar"
:src="item.avatar_file&&item.avatar_file.url ? item.avatar_file.url : '/uni_modules/uni-im/static/avatarUrl.png'"
mode="widthFix"></image>
<text class="username">{{item.nickname}}</text>
<button @click.stop="deleteItem(item,index,$event)" class="delete-btn" size="mini"
type="warn">删除</button>
</view>
</scroll-view>
</template>
</uni-list-item>
<uni-list-item :customStyle="{padding:0,backgroundColor:'#FFFFFF'}">
<template v-slot:body>
<uni-im-load-state :status="friendHasMore?'loading':'noMore'"></uni-im-load-state>
</template>
</uni-list-item>
</uni-list>
</view>
</template>
<script>
import uniIm from '@/uni_modules/uni-im/sdk/index.js';
const db = uniCloud.database()
export default {
emits: ['clickMenu'],
props: {
// pc端时会控制隐藏
showMenu: {
type: Boolean,
default: true
},
// pc端时会控制隐藏
showUser: {
type: Boolean,
default: true
},
},
data() {
return {
isOpenItemTitle: '',
scrollLeft: {
0: 0,
1: 1
},
activeIndex: false,
menuList: [{
title: '加人/加群',
path: './addPeopleGroups/addPeopleGroups',
srcName: 'search'
},
{
title: '群聊列表',
path: './groupList/groupList',
srcName: 'group'
},
{
title: '创建群聊',
path: './createGroup/createGroup',
srcName: 'createGroup'
}
]
}
},
computed: {
//是否为pc宽屏(width>960px)
isWidescreen() {
return uniIm.isWidescreen
},
friendList() {
return uniIm.friend.dataList
},
friendHasMore() {
return uniIm.friend.hasMore
},
noticeList() {
return [{
title: "新朋友",
param: {
type: ['uni-im-friend-invite']
},
icon: "/uni_modules/uni-im/static/noticeIcon/newFriend.png"
},
{
title: "群通知",
param: {
type: ['uni-im-group-join-request']
},
icon: "/uni_modules/uni-im/static/noticeIcon/groupNotice.png"
},
{
title: "系统通知",
param: {
excludeType: ['uni-im-group-join-request', 'uni-im-friend-invite']
},
icon: "/uni_modules/uni-im/static/noticeIcon/notification.png"
}
].reduce((sum, item, index) => {
let {
param: filterNotice,
title
} = item,
param = {
filterNotice,
title
}
// console.log('param----',param);
sum.push({
title: item.title,
badge: this.getUnreadCount(item.param),
badgeStyle: {
backgroundColor: '#d60000'
},
path: "./notification/notification?param=" + encodeURIComponent(JSON.stringify(param)),
param,
icon: item.icon,
id: Date.now() + '-' + index
})
return sum
}, [])
}
},
onPullDownRefresh() {
this.$refs.udb.loadData({
clear: true
}, () => {
uni.stopPullDownRefresh()
})
},
onReachBottom() {
// this.$refs.udb.loadMore()
},
mounted() {},
methods: {
openPages(item) {
this.isOpenItemTitle = item.title
// #ifdef H5
if (this.isWidescreen) {
let componentName = 'uni-im-' + item.path.split('/')[1],
param = item.param
return this.$emit('clickMenu', {
componentName,
param,
title: item.title
})
}
// #endif
// console.log('item',item);
uni.navigateTo({
url: item.path,
fail: (e) => {
console.error(e, item.path);
}
})
},
getUnreadCount(param) {
return uniIm.notification.unreadCount(param)
},
toChat(item) {
uniIm.toChat({user_id: item._id})
},
hiddenDeleteBtn() {
this.activeIndex = false
this.$nextTick(() => {
for (let i in this.scrollLeft) {
this.$set(this.scrollLeft, i, 0)
// this.scrollLeft[i] = 0
}
})
},
async deleteItem(item, index, event) {
uni.showModal({
title: '确认要删除好友吗',
content: '此操作不可撤销',
showCancel: true,
cancelText: '取消',
confirmText: '确认',
complete: async (e) => {
if (e.confirm) {
uni.showLoading({
mask: true
});
await db.collection('uni-im-friend').where({
friend_uid: item._id,
user_id: uniCloud.getCurrentUserInfo().uid
}).remove()
uni.hideLoading()
// 收到push消息后会自动,将此用户从列表中移除
}
}
});
this.hiddenDeleteBtn()
event.stopPropagation()
event.preventDefault()
},
scroll(e) {
// console.log(this.inMove);
this.$set(this.scrollLeft, this.activeIndex, e.detail.scrollLeft)
// this.scrollLeft[this.activeIndex] = e.detail.scrollLeft
for (let i in this.scrollLeft) {
if (i != this.activeIndex) {
this.$set(this.scrollLeft, i, 0)
// this.scrollLeft[i] = 0
}
}
},
handleItemClick(id) {
uni.navigateTo({
url: './detail?id=' + id
})
},
fabClick() {
// 打开新增页面
uni.navigateTo({
url: './add',
events: {
// 监听新增数据成功后, 刷新当前页面数据
refreshData: () => {
this.$refs.udb.loadData({
clear: true
})
}
}
})
}
}
}
</script>
<style lang="scss" scoped>
.contacts-pages {
flex: 1;
position: relative;
}
/* #ifdef H5 */
@media screen and (min-device-width:960px) {
.contacts-pages {
height: 100%;
}
.user-list-box {
overflow: auto;
}
}
/* #endif */
.title {
padding: 8px;
font-size: 14px;
}
.user-list-box {
flex: 1;
}
.user-list-item {
padding: 0;
}
.user-list-item-scroll-view {
width: 750rpx;
background-color: #ffffff;
}
.user-list-item-scroll-view-item {
width: 880rpx;
position: relative;
height: 60px;
align-items: center;
padding: 8px 15px;
flex-direction: row;
border-bottom: 1px solid #f0f0f0;
}
.avatar {
background-color: #fefefe;
width: 40px;
height: 40px;
border-radius: 5px;
}
.username {
line-height: 30px;
margin-left: 30rpx;
font-size: 16px;
}
.delete-btn {
border-radius: 0;
position: absolute;
right: 0;
top: 0;
height: 60px;
line-height: 60px;
width: 130rpx;
font-size: 26rpx;
padding: 0;
}
.slot-icon-box {
width: 45px;
height: 45px;
align-items: center;
justify-content: center;
border-radius: 10rpx;
margin-right: 20rpx;
}
.slot-icon {
width: 25px;
height: 25px;
}
.warn {
background-color: #FA9E3B;
}
.green {
background-color: #08C060;
}
.blue {
background-color: #5DBAFF;
}
@media screen and (min-device-width:960px) {
.activeMenu {
background-color: #f5f5f5 !important;
}
}
</style>
\ No newline at end of file
<template>
<view class="create-group-box" :class="{'join-grpop':group_id}">
<view class="header-box">
<uni-search-bar v-model="keyword" placeholder="搜索" bgColor="#fff" :radius="100"
@cancel="doClear();isFocus = true" @clear="doClear" :isFocus="isFocus" @focus="isFocus = true" @blur="isFocus = false" ></uni-search-bar>
</view>
<uni-list class="content-box">
<uni-list-chat @click="checkboxChange(item._id)" v-for="(item,index) in friendList" :key="index" :avatar-circle="true" :title="item.nickname" :border="false" :clickable="true"
:avatar="item.avatar_file && item.avatar_file.url ? item.avatar_file.url : '/uni_modules/uni-im/static/avatarUrl.png'">
<template v-slot:left>
<view class="checkbox">
<uni-icons type="checkmarkempty" color="#007aff" v-if="checkFriendIds.includes(item._id)"></uni-icons>
</view>
</template>
</uni-list-chat>
<uni-im-load-state :status="loading?'loading':(hasMore?'hasMore':'noMore')"
:contentText='{"contentnomore": friendList.length?"没有更多好友":"没有可以选择的好友"}'></uni-im-load-state>
<uni-list-item style="height: 60px;" :border="false">
<!-- 占位,用于此元素上方显示 操作按钮 -->
</uni-list-item>
</uni-list>
<view class="foot-box">
<!-- 创建新群可以不选择好友直接创建,邀请好友进群必须选择好友 -->
<button :disabled="group_id? !checkFriendIds.length : false " class="btn" type="primary"
@click="createGroup">{{btnText}}{{checkFriendNum}}</button>
</view>
</view>
</template>
<script>
import uniIm from '@/uni_modules/uni-im/sdk/index.js';
const db = uniCloud.database();
export default {
data() {
return {
loading: true,
hasMore: false,
keyword: '',
checkFriendIds: [],
friendData: [],
groupMemberUid: [], //选人进群时,已经在群里的人的id
group_id: false,
isFocus: false
}
},
computed: {
friendList() {
return this.friendData.filter(item => {
//转小写筛选
return !this.groupMemberUid.includes(item._id)
&&
(this.keyword == '' || item.nickname.toLowerCase().includes(this.keyword.toLowerCase()))
})
},
checkFriendNum() {
return this.checkFriendIds.length > 0 ? '(' + this.checkFriendIds.length + ')' : ''
},
btnText() {
return this.group_id ? '立即邀请' : '立即创建'
},
checkFriendsWidth() {
return this.checkFriendIds.length > 6 ? '100%' : this.checkFriendIds.length * 80 + 'px'
},
// checkFriendsSearchWidth() {
// return this.checkFriendIds.length > 6 ? '360' : 720 - (this.checkFriendIds.length * 60)
// },
translateXWidth() {
return this.checkFriendIds.length > 6 ? this.checkFriendIds.length * 65 : '60'
},
checkFriendImg() {
return this.friendList.reduce((sum, current) => {
if (this.checkFriendIds.includes(current._id)) {
sum.push(current)
}
return sum
}, []).map(item => item.avatar_file)
}
},
async onLoad(options) {
this.setParam(options)
},
methods: {
async setParam(options = {}) {
console.log("group_id", options);
if (options.group_id) {
this.group_id = options.group_id
uni.setNavigationBarTitle({
title: '邀请新成员'
})
//查本群,成员,
let res = await db.collection('uni-im-group-member').where({
group_id: options.group_id
})
.get()
console.log("res:查本群,成员 ", res);
this.groupMemberUid = res.result.data.map(item => item.user_id)
console.log('this.groupMemberUid', this.groupMemberUid);
}
this.getFriendsData()
},
async getFriendsData() {
let whereString = {}
if (this.keyword) {
whereString = `
"_id" == "${this.keyword}" ||
"username" == "${this.keyword}" ||
"nickname" == "${this.keyword}" ||
"email" == "${this.keyword}" ||
"mobile" == "${this.keyword}"
`
}
let res = await db.collection(
db.collection('uni-im-friend').where('"user_id" == $cloudEnv_uid').field('friend_uid,mark,class_name')
.getTemp(),
db.collection('uni-id-users').where(whereString).field('_id,nickname,avatar_file').getTemp()
).get()
// console.log(res);
let data = res.result.data
data.forEach((item, index) => {
if (item.friend_uid[0]) {
data[index] = item.friend_uid[0]
} else {
delete data[index]
}
})
this.friendData = data
this.loading = false
this.hasMore = this.friendList.length != 0
},
doClear() {
this.keyword = ''
this.getFriendsData()
},
checkboxChange(user_id) {
console.log("checkboxChange-value",user_id);
this.checkFriendIds = this.checkFriendIds.includes(user_id) ? this.checkFriendIds.filter(item => item != user_id) : this.checkFriendIds.concat(user_id)
console.log("checkboxChange",this.checkFriendIds);
},
async createGroup() {
// console.log('创建', this.checkFriendIds.length)
const uniImCo = uniCloud.importObject("uni-im-co")
let res = await uniImCo.chooseUserIntoGroup({
user_ids: this.checkFriendIds,
group_id: this.group_id
})
this.checkFriendIds = []
// console.log('createGroup',res);
if (this.group_id) {
uni.navigateBack({
delta: 1
})
} else {
// #ifdef H5
if (uniIm.isWidescreen) {
uni.$emit('uni-im-toChat', 'group_' + res.data.group_id)
} else {
uni.redirectTo({
url: '/uni_modules/uni-im/pages/chat/chat?conversation_id=' + 'group_' + res.data.group_id,
animationDuration: 300,
fail: (e) => {
console.log(e);
}
})
}
// #endif
// #ifndef H5
uni.redirectTo({
url: '/uni_modules/uni-im/pages/chat/chat?conversation_id=' + 'group_' + res.data.group_id,
animationDuration: 300,
complete: (e) => {
console.log(e);
}
})
// #endif
}
}
}
}
</script>
<style lang="scss" scoped>
.create-group-box {
position: relative;
background-color: #F9F9F9;
flex: 1;
}
.header-box {
flex-direction: column;
background-color: #F9F9F9;
}
.content-box {
/* #ifndef APP-NVUE */
width: 100%;
overflow: auto;
/* #endif */
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
}
.label-box {
flex-direction: row;
align-items: center;
background-color: #FFF;
padding: 5px 0;
}
.checkbox {
margin: 12px 10px 0 0;
border: 1px solid #DDD;
width: 20px;
height: 20px;
justify-content: center;
align-items: center;
border-radius: 3px;
}
.foot-box {
position: fixed;
bottom: 0;
/* #ifdef APP-NVUE */
width: 750rpx;
/* #endif */
// 注意:此页面可能显示在pc端所以不能用750rpx,而用100%
/* #ifndef APP-NVUE */
width: 100%;
/* #endif */
height: 60px;
justify-content: center;
align-items: center;
}
/* #ifdef H5 */
@media screen and (min-device-width:960px){
.create-group-box {
width: 100%;
margin: 10px auto;
}
.join-grpop {
width: 800px;
}
.content-box {
height: calc(95vh - 200px);
}
.content-box ::v-deep .uni-list--border {
display: none;
}
.content-box ::v-deep .uni-list-chat {
width: 100%;
}
.content-box ::v-deep .uni-list-chat__container {
padding-left: 10px;
}
.foot-box {
position: absolute;
}
}
/* #endif */
.foot-box .btn {
width: 300px;
}
</style>
\ No newline at end of file
<template>
<view>
<uni-search-bar placeholder="搜索群号/群名称" :radius="100" bgColor="#eeeeee" v-model="keyword"
@cancel="doClear"
@clear="doClear"
></uni-search-bar>
<uni-list class="uni-list">
<uni-list-chat v-for="(item,index) in groupList" :key="index"
@click="toChat(item.group_info._id)" link
:title="item.group_info.name"
:avatar="item.group_info.avatar_file&&item.group_info.avatar_file.url ? item.group_info.avatar_file.url : '/uni_modules/uni-im/static/avatarUrl.png'">
</uni-list-chat>
</uni-list>
<uni-im-load-state :status="groupHasMore?'loading':'noMore'"></uni-im-load-state>
</view>
</template>
<script>
import uniIm from '@/uni_modules/uni-im/sdk/index.js';
export default {
data() {
return {
keyword: '',
groupData:false
}
},
computed: {
//是否为pc宽屏(width>960px)
isWidescreen(){
return uniIm.isWidescreen
},
groupList() {
let groupList = uniIm.group.get()
if(this.keyword){
return groupList.filter(item=>{
return item.group_info.name.includes(this.keyword) || item.group_info._id.includes(this.keyword)
})
}else{
return groupList
}
},
groupHasMore(){
return uniIm.group.hasMore
}
},
async onLoad(options) {
this.setParam(options)
},
methods: {
setParam(param = {}){
if(param.group_id){
this.keyword = param.group_id
}
},
doClear(){
this.keyword = ''
},
toChat(group_id) {
let conversation_id = 'group_' + group_id
uniIm.toChat({conversation_id})
}
}
}
</script>
<style lang="scss" scoped>
/* #ifdef H5 */
@media screen and (min-device-width:960px){
.uni-list {
height: calc(100vh - 185px);
overflow: auto;
}
}
/* #endif */
</style>
<template>
<view class="notification-box">
<text class="tips" v-if="tips">{{tips}}</text>
<uni-list :border="false">
<template v-if="notificationDatas && notificationDatas.length">
<uni-list-chat v-for="(item,index) in notificationDatas" :key="item.id" :avatarCircle="true"
:clickable="true" :badge-text="item.is_read?'':'dot'" badgePositon="left"
:title="item.payload.title||item.title" :note="item.payload.content||item.content||'无'"
:avatar="item.payload.avatar_file&&item.payload.avatar_file.url ? item.payload.avatar_file.url : '/uni_modules/uni-im/static/noticeIcon/notification2.png'"
@click.native="clickHandle(index,item)" direction="column" :time="friendlyTime(item.create_time)">
<view class="handle-box">
<template v-if="item.payload.state">
<text class="handle done">
{{'已'+(item.payload.state == 'confirm'?item.payload.confirmText:item.payload.cancelText)}}
</text>
</template>
<template v-else>
<text class="handle" @click.stop="doAction(index,0)"
v-if="item.payload.cancelText">{{item.payload.cancelText}}</text>
<text class="handle" @click.stop="doAction(index,1)"
v-if="item.payload.confirmText">{{item.payload.confirmText}}</text>
<uni-icons v-if="!item.payload.cancelText && !item.payload.confirmText && item.path" type="right"
color="#cccccc"></uni-icons>
</template>
</view>
</uni-list-chat>
</template>
<uni-list-item v-else class="load-more">
<template v-slot:body>
<uni-im-load-state :contentText="contentText" class="tip" :status="hasMore?'loading':'noMore'"></uni-im-load-state>
</template>
</uni-list-item>
</uni-list>
</view>
</template>
<script>
import uniIm from '@/uni_modules/uni-im/sdk/index.js';
import action from './action.js';
const db = uniCloud.database();
export default {
data() {
return {
contentText: {
"contentrefresh": "加载中...",
"contentnomore": "- 暂无相关数据 -"
},
filterNotice: {},
tips: "",
hasMore: true
// notificationDatas:[]
}
},
async onLoad({
param
}) {
// console.log(param,decodeURIComponent(param))
param = JSON.parse(decodeURIComponent(param))
// console.log(param)
this.setParam(param)
},
computed: {
//是否为pc宽屏(width>960px)
isWidescreen() {
return uniIm.isWidescreen
},
notificationDatas() {
let notificationDatas = uniIm.notification.get(this.filterNotice)
if (notificationDatas.length == 0) {
setTimeout(() => {
this.hasMore = false
}, 100);
}
return notificationDatas
}
},
mounted() {
this.hasMore = uniIm.notification.hasMore
},
methods: {
setParam({
filterNotice,
title
}) {
if (typeof filterNotice == 'string') {
filterNotice = JSON.parse(decodeURIComponent(filterNotice))
}
this.filterNotice = filterNotice
console.log('filterNotice', filterNotice)
uni.setNavigationBarTitle({
title
})
if (title == '新朋友' && !this.isWidescreen) {
this.tips = '好友请求通知'
}
},
async setItem({
_id
}, param) {
const datas = uniIm.notification.get(this.filterNotice)
for (let i = 0; i < datas.length; i++) {
if (datas[i]._id == _id) {
datas[i] = deepAssign(datas[i], param)
uniIm.notificationDatas = datas
console.log('uniIm.notificationDatas', uniIm.notificationDatas)
break;
}
}
let ares = await db.collection('uni-im-notification')
.where(`"_id" == "${_id}" && "user_id" == $cloudEnv_uid`)
.get()
// console.log(13231,ares);
let res = await db.collection('uni-im-notification')
.where(`"_id" == "${_id}" && "user_id" == $cloudEnv_uid`)
.update(param)
// console.log('res---66666',param,res.result.updated);
/**
*判断对象是否是一个纯粹的对象
*/
function isPlainObject(obj) {
return typeof obj === 'object' && Object.prototype.toString.call(obj) === '[object Object]'
}
/**
*深度合并多个对象的方法
*/
function deepAssign() {
let len = arguments.length,
target = arguments[0]
if (!isPlainObject(target)) {
target = {}
}
for (let i = 1; i < len; i++) {
let source = arguments[i]
if (isPlainObject(source)) {
for (let s in source) {
if (s === '__proto__' || target === source[s]) {
continue
}
if (isPlainObject(source[s])) {
target[s] = deepAssign(target[s], source[s])
} else {
target[s] = source[s]
}
}
}
}
return target
}
},
async clickHandle(index, item) {
// console.log('index',index,item);
//如果未读,设置为已读
if (!item.is_read) {
this.setItem(item, {
is_read: true
})
}
//存在链接就跳转
let path = item.path || item.payload.path
if (path) {
uni.navigateTo({
url: path,
fail: (e) => {
console.error(e);
}
})
}
// let item = this.notificationDatas[index]
// item.data.is_read = true
// this.notificationDatas[index] = Object.assign({},item)
// console.log(this.notificationDatas);
},
doAction(index, type) {
let item = this.notificationDatas[index]
let e = {
subType: item.payload.subType,
confirm: type === 1,
cancel: type === 0,
item
}
action(e, data => {
console.log('doAction', data)
this.setItem(item, {
is_read: true,
payload: {
state: type === 1 ? 'confirm' : 'cancel'
}
})
})
// console.log(index);
},
friendlyTime(timestamp) {
return uniIm.utils.toFriendlyTime(timestamp)
},
handleText(state) {
switch (state) {
case 0:
return '同意'
break;
case 100:
return '已同意'
break;
case -100:
return '已拒绝'
break;
default:
return '其他'
break;
}
}
}
}
</script>
<style lang="scss" scoped>
.notification-box {
/* #ifndef APP-NVUE */
height: 100vh;
// width: 750rpx;
/* #endif */
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
background-color: #f5f5f5;
}
.tips {
height: 40px;
line-height: 40px;
padding-left: 20rpx;
font-size: 26rpx;
color: #666;
}
.handle-box {
flex-direction: row;
height: 40px;
align-items: center;
}
.handle {
width: 50px;
text-align: center;
height: 25px;
line-height: 25px;
background-color: #efefef;
border-radius: 50px;
font-size: 12px;
margin: 0 5px;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
.done {
width: 50px;
background-color: #FFF;
color: #aaa;
/* #ifdef H5 */
cursor: default;
/* #endif */
}
.load-more {
background-color: #f5f5f5 !important;
justify-content: center;
}
.tip {
position: relative;
left: -15px;
width: 750rpx;
/* #ifndef APP-NVUE */
/* pc宽屏时需要使用100vw */
width: 100%;
/* #endif */
}
/* #ifdef MP-WEIXIN */
.load-more ::v-deep .uni-list-item {
background-color: #f5f5f5 !important;
}
/* #endif */
</style>
\ No newline at end of file
<template>
<view class="container">
<view class="qr-code">
<view class="code-info">
<text class="group-name">{{name}}</text>
<text class="group-id" @click="copyGroupID">群号:{{group_id}}</text>
<!--uqrcode 组件来源,插件Sansnn-uQRCode 链接地址:https://ext.dcloud.net.cn/plugin?id=1287 -->
<!-- #ifndef MP-WEIXIN -->
<!-- <uqrcode ref="uqrcode" :start="false" :size="200" canvas-id="qrcode" :value="qrcodeData"></uqrcode> -->
<!-- #endif -->
</view>
<!-- #ifndef APP-NVUE -->
<image class="group-avatar" :src="avatar_file || '/uni_modules/uni-im/static/avatarUrl.png'" mode="">
</image>
<!-- #endif -->
</view>
<!-- #ifdef APP-NVUE -->
<image class="group-avatar" :src="avatar_file || '/uni_modules/uni-im/static/avatarUrl.png'" mode=""></image>
<!-- #endif -->
<!-- <view class="btn-box">
<view class="btn-item" @click="save">
<uni-icons type="arrow-down" size="30"></uni-icons>
<text class="btn-text">保存</text>
</view>
<view class="btn-item" @click="share">
<uni-icons type="paperplane" size="30"></uni-icons>
<text class="btn-text">分享</text>
</view>
</view> -->
</view>
</template>
<script>
import uqrcode from "@/uni_modules/Sansnn-uQRCode/components/uqrcode/uqrcode"
export default {
components: {
uqrcode
},
data() {
return {
group_id: '',
name: '',
avatar_file: ''
}
},
computed: {
qrcodeData() {
let data = {
"type": "uni-im",
"subType": "groupInfo",
"data": {
group_id: this.group_id,
name: this.name,
avatar_file: this.avatar_file
}
}
return JSON.stringify(data)
}
},
onLoad(options) {
// console.log("options: ",options);
this.group_id = options.id
this.name = options.name
this.avatar_file = options.avatar_file
},
onReady(){
setTimeout(()=>{
this.$refs.uqrcode.make({
success: () => {
// console.log('生成成功');
},
fail: err => {
// console.log(err)
}
});
},1000)
},
methods: {
copyGroupID() {
uni.setClipboardData({
data: this.group_id,
success: function() {
console.log('success');
}
});
},
save() {
console.log('保存');
},
share() {
console.log('分享');
}
}
}
</script>
<style lang="scss" scoped>
.container {
/* #ifndef APP-NVUE */
height: 100vh;
/* #endif */
flex: 1;
padding-top: 200rpx;
// justify-content: center;
align-items: center;
background-color: #f5f5f5;
}
.qr-code {
width: 550rpx;
height: 780rpx;
align-items: center;
justify-content: center;
border-radius: 20rpx;
background-color: #fff;
position: relative;
}
.code-info {
align-items: center;
justify-content: center;
}
.group-avatar {
width: 150rpx;
height: 150rpx;
border-radius: 100rpx;
position: absolute;
/* #ifdef APP-NVUE */
top: 150rpx;
/* #endif */
/* #ifndef APP-NVUE */
top: -60rpx;
/* #endif */
}
.group-name {
width: 400rpx;
font-size: 46rpx;
/* #ifdef APP-NVUE */
lines: 1;
/* #endif */
/* #ifndef APP-PLUS */
white-space: nowrap;
overflow: hidden;
/* #endif */
text-overflow: ellipsis;
margin-top: 120rpx;
text-align: center;
}
.group-id {
margin: 40rpx 0;
font-size: 30rpx;
}
.btn-box {
flex-direction: row;
margin-top: 100rpx;
}
.btn-item {
align-items: center;
justify-content: center;
width: 130rpx;
height: 130rpx;
border-radius: 100rpx;
background-color: #fff;
margin: 0 80rpx;
border: 1px solid #eee;
}
.btn-text {
font-size: 28rpx;
}
</style>
<template>
<view class="group-info-box">
<uni-im-group-members v-if="conversation_id" :conversation_id="conversation_id"></uni-im-group-members>
<uni-list>
<uni-list-item v-if="!leave_group" title="消息免打扰" @switchChange="changeConversationMute"
:switchChecked="conversation.mute" :showSwitch="true"></uni-list-item>
<uni-list-item v-for="(val,key) in editorFields" :key="key" @click.native="openPopupInfo(key)" :title="val"
:showArrow="isAdmin" :clickable="isAdmin">
<template v-slot:footer>
<text
class="group-info-text">{{ (key == "notification" ? conversation.group_info[key]?.content : conversation.group_info[key]) || '未设置' }}</text>
</template>
</uni-list-item>
<uni-list-item @click.native="setAvatar" title="群头像" :clickable="isAdmin">
<template v-slot:footer>
<image class="logo" :src="logoUrl||'/uni_modules/uni-im/static/avatarUrl.png'" mode=""></image>
</template>
</uni-list-item>
<template v-if="isAdmin">
<uni-list-item @click.native="setAddGroupType" title="加群方式" note="申请加入本群的验证规则" :clickable="isAdmin">
<template v-slot:footer>
<text class="join_option">{{join_option}}</text>
</template>
</uni-list-item>
<uni-list-item title="全员禁言" @switchChange="setMuteAllMembers"
:switchChecked="conversation.group_info.mute_all_members" :showSwitch="true"></uni-list-item>
</template>
<!-- #ifdef H5 -->
<uni-list-item @click="share" :clickable="true" title="分享此群">
<template v-slot:footer>
<uni-icons size="25px" color="#666" type="redo"></uni-icons>
</template>
</uni-list-item>
<!-- #endif -->
</uni-list>
<view v-if="leave_group">
<text style="padding: 15px;text-align: center;color: #666;">- 你不是此群成员 -</text>
<!-- <text @click="joinGroup" style="padding: 15px;text-align: center;color: #005eca;cursor: pointer;">申请加入</text> -->
</view>
<text v-else class="exitGroup" @click="exitGroup">{{isGroupCreator?'解散群聊':'退出群聊'}}</text>
<uni-popup ref="popupInfo" type="dialog">
<uni-popup-dialog mode="input" :title="editorFields[editorType]" :placeholder="'请输入'+editorFields[editorType]"
:duration="2000" :before-close="true" :value="editorDefaultValue" @close="closePopupInfo"
@confirm="confirmPopupInfo"></uni-popup-dialog>
</uni-popup>
<!-- <uni-im-contextmenu ref="uni-im-contextmenu"></uni-im-contextmenu> -->
</view>
</template>
<script>
const db = uniCloud.database()
import uniIm from '@/uni_modules/uni-im/sdk/index.js';
import members from '@/uni_modules/uni-im/pages/group/members.nvue';
export default {
components: {
'uni-im-group-members':members
},
data() {
return {
conversation: {
group_info: {
user_id: "",
mute_all_members: false
},
group_member: {},
mute: false
},
leave_group: false,
member_list: [],
isManage: false,
editorFields: {
"name": " 群聊名称",
"introduction": "群简介",
"notification": "群公告"
},
editorType: '',
editorDefaultValue: '',
groupType: '',
isAdmin: false,
keyword: '',
mute_all_members: false,
// #ifdef APP-NVUE
showAllMember: true,
conversationId:'',
// #endif
// #ifndef APP-NVUE
showAllMember: false,
// #endif
// 鼠标在哪个用户id上
hoverUserId: ''
};
},
computed: {
...uniIm.mapState(['isWidescreen']),
logoUrl() {
return this.conversation.group_info.avatar_file ? this.conversation.group_info.avatar_file.url : false
},
join_option() {
let val = this.conversation.group_info.join_option
return {
needPermission: "需要验证权限",
freeAccess: "自由加入",
disableApply: "禁止加入"
} [val]
},
memberList() {
return this.member_list
// 根据关键词搜索
.filter(member => {
// 忽略大小写
return member.users.nickname.toLowerCase().includes(this.keyword.toLowerCase())
})
// 是管理员排序靠前
.sort((a, b) => {
if (a.role.includes('admin') && !b.role.includes('admin')) {
return -1
} else if (!a.role.includes('admin') && b.role.includes('admin')) {
return 1
} else {
return 0
}
})
},
isGroupCreator() {
return this.conversation.group_info.user_id == uniCloud.getCurrentUserInfo().uid
},
canPrivateChat(){
// 当前登录的账号是管理员,或者当前消息是群管理员发的
return this.uniIDHasRole('staff') || this.hoverUserId && this.conversation.group_member[this.hoverUserId].role.includes('admin')
}
},
watch: {
"conversation.group_info.user_id"(adminUserId) {
// 当前用户是群的创建者或者管理员(在群成员中找到当前用户的角色包含admin)
const currentUserId = uniCloud.getCurrentUserInfo().uid
this.isAdmin = this.isGroupCreator || this.conversation.group_member[currentUserId]?.role.includes('admin')
//非群主 隐藏【管理】按钮
if (!this.isAdmin) {
if (!this.isWidescreen) {
// #ifdef H5
let dom = document.getElementsByClassName('uni-page-head-btn')[1]
dom.style.visibility = 'hidden'; // 隐藏元素
// #endif
// #ifdef APP-PLUS
var webview = this.$scope.$getAppWebview();
// console.log('webview', webview);
webview.setStyle({
titleNView: {
buttons: []
}
});
// #endif
}
} else {
if (this.isWidescreen) {
this.isManage = true
}
}
},
"conversation.group_member": {
handler(group_member, oldValue) {
// console.log('group_member',group_member);
this.member_list = []
for (let key in group_member) {
this.member_list.push(group_member[key])
}
if (!uniIm.isWidescreen) { // pc宽屏不需要
let title = "群信息(" + this.member_list.length + "人)"
uni.setNavigationBarTitle({
title
});
}
},
deep: true,
immediate: true
},
// (后续)通过监听实现实时切换管理员实时刷新权限
// console.log('isAdmin',isAdmin);
conversation: {
handler(conversation, oldValue) {
const currentUserId = uniCloud.getCurrentUserInfo().uid
this.isAdmin = this.isGroupCreator || this.conversation.group_member[currentUserId]?.role.includes('admin')
this.leave_group = conversation.leave
this.mute_all_members = conversation.group_info.mute_all_members
},
deep: true
},
},
props: {
conversation_id: {
default () {
return false
}
},
},
async onLoad(e) {
if (!e.conversation_id) {
throw new Error("会话id不能为空")
}
this.conversationId = e.conversation_id
this.load(e.conversation_id)
},
mounted() { //pc端以组件模式加载时逻辑
if (this.conversation_id) {
this.load(this.conversation_id)
}
},
onShow() {
// console.log("this.conversation: ", this.conversation.group_member);
},
methods: {
navToMembers(conversationId){
uni.navigateTo({
url:"/uni_modules/uni-im/pages/group/members?conversation_id=" + conversationId
})
},
async load(conversation_id) {
this.conversation = await uniIm.conversation.get(conversation_id)
},
async expelAndToBlack(item) {
uni.showModal({
title: '确定要将该用户移出本群并拉黑吗?',
content: '拉黑后此用户将不能再次加入本群,不能撤销,请谨慎操作',
cancelText: '取消',
confirmText: '确认',
complete: async (e) => {
// console.log(e);
if (e.confirm) {
uni.showLoading({
mask: true
});
try {
let res = await db.collection('uni-im-group-member').where({
user_id: item.users._id,
group_id: this.conversation.group_info._id
})
.remove()
console.log('expel', res);
const uniImCo = uniCloud.importObject("uni-im-co")
res = await uniImCo.addToGroupMenberBlackList({
user_id: item.users._id,
group_id: this.conversation.group_info._id
})
console.log('expelAndToBlack', res);
} catch (error) {
uni.showToast({
title: error.message,
icon: 'error',
complete: () => {}
});
}
uni.hideLoading()
}
}
});
},
async expel(item) {
uni.showModal({
title: '确定要将该用户移出本群吗?',
content: '不能撤销,请谨慎操作',
cancelText: '取消',
confirmText: '确认',
complete: async (e) => {
// console.log(e);
if (e.confirm) {
uni.showLoading({
mask: true
});
try {
let res = await db.collection('uni-im-group-member').where({
user_id: item.users._id,
group_id: this.conversation.group_info._id
})
.remove()
if (res.result.deleted) {
uni.showToast({
title: '成功移除',
icon: 'none',
complete: () => {}
});
// console.log('exitGroup', res);
}
} catch (error) {
uni.showToast({
title: error.message,
icon: 'error',
complete: () => {}
});
}
uni.hideLoading()
}
}
});
},
async changeMemberMute(item) {
let nickname = item.users.nickname || '匿名用户'
uni.showModal({
title: '确定要' + (item.mute_type ? `为"${nickname}"解除禁言吗?` : `禁言"${nickname}"吗?`),
cancelText: '取消',
confirmText: '确认',
complete: async (e) => {
// console.log(e);
if (e.confirm) {
uni.showLoading({
mask: true
});
try {
let res = await db.collection('uni-im-group-member').where({
_id: item._id,
mute_type: item.mute_type // 防止此时云端已经变化
})
.update({
mute_type: item.mute_type ? 0 : 1
})
// console.log('mute_type', res);
if (res.result.updated) {
item.mute_type = item.mute_type ? 0 : 1
uni.showToast({
title: '设置成功',
icon: 'none',
complete: () => {}
});
}
} catch (error) {
// console.log('error',merror)
uni.showToast({
title: error.message,
icon: 'error',
complete: () => {}
});
}
uni.hideLoading()
}
}
});
},
openConversationMenu(e, index) {
let member = this.memberList[index]
// console.log('member', member);
let menuList = []
if (this.isAdmin) {
menuList.unshift({
"title": "移除",
"action": () => {
// console.log('移除');
this.expel(member)
}
})
menuList.unshift({
"title": "移除并拉黑",
"action": () => {
console.log('移除并拉黑');
this.expelAndToBlack(member)
}
})
if (!this.conversation.group_info.mute_all_members) {
menuList.unshift({
"title": member.mute_type ? "解除禁言" : '设为禁言',
"action": () => {
// console.log('禁言');
this.changeMemberMute(member)
}
})
}
const isAdmin = member.role.includes('admin')
menuList.push({
"title": isAdmin ? "取消管理员" : "设置管理员",
"action": () => {
let role = member.role;
if (isAdmin) {
// console.log('取消管理员');
role = member.role.filter(item => item !== 'admin')
} else {
role.push('admin')
// console.log('设置管理员');
}
uni.showLoading({
mask: true
});
db.collection('uni-im-group-member').doc(member._id).update({
"role": role
}).then(res => {
// console.log('res', res);
member.role = role
})
.catch(err => {
console.error(err)
uni.showToast({
title: err.message,
icon: 'none'
});
})
.finally(() => {
uni.hideLoading()
})
}
})
}
if (menuList.length > 0) {
member.focus = true
const myContextmenu = this.$refs['uni-im-contextmenu']
const position = {
"contextmenu": {
"top": e.clientY,
"left": e.clientX
},
"longpress": {
"top": e.touches[0].screenY || e.touches[0].clientY,
"left": e.touches[0].screenX || e.touches[0].clientX
}
} [e.type]
// #ifdef H5
position.top = position.top + 120
// #endif
myContextmenu.show(position, menuList)
myContextmenu.onClose(() => {
member.focus = false
})
}
},
invite() {
// console.log('group_info._id', this.conversation.group_info._id);
uni.navigateTo({
url: '/uni_modules/uni-im/pages/contacts/createGroup/createGroup?group_id=' + this.conversation.group_info
._id
})
},
async exitGroup() {
const group_id = this.conversation.group_info._id
if (this.isGroupCreator) {
uni.showModal({
title: '确认要解散群聊吗?',
content: '不能撤销,请谨慎操作',
cancelText: '取消',
confirmText: '确认',
complete: async (e) => {
// console.log(e);
if (e.confirm) {
uni.showLoading({
mask: true
});
let res = await db.collection('uni-im-group')
.where({
_id: group_id
})
.remove()
.finally((res) => {
// uni.navigateBack({ // 收到离群的推送通知会自动关闭当前页面
// delta: 2
// })
uni.hideLoading()
})
// console.log('exitGroup', res);
}
}
});
} else {
uni.showModal({
title: '确认要退出群聊吗?',
content: '不能撤销,请谨慎操作',
cancelText: '取消',
confirmText: '确认',
complete: async (e) => {
// console.log(e);
if (e.confirm) {
// uni.navigateBack({ // 收到离群的推送通知会自动关闭当前页面
// delta: 2
// })
uni.showLoading({
mask: true
});
let res = await db.collection('uni-im-group-member').where({
user_id: uniCloud.getCurrentUserInfo().uid,
group_id
})
.remove()
// console.log(res.result);
if (res.result.deleted) {
uni.showToast({
title: '成功退出',
icon: 'none'
});
}
uni.hideLoading()
// console.log('exitGroup', res);
}
}
});
}
},
openPopupInfo(type) {
// console.log(type);
if (!this.isAdmin) return
this.editorType = type
this.editorDefaultValue = this.conversation.group_info[type]
if (this.editorType == "notification") {
this.editorDefaultValue = this.editorDefaultValue?.content || ""
}
this.$refs.popupInfo.open()
},
closePopupInfo() {
this.$refs.popupInfo.close()
},
confirmPopupInfo(value) {
if (!value) {
uni.showToast({
title: '内容不能为空!',
icon: 'none'
});
return
}
// console.log('value', value);
const updateData = {};
if (this.editorType == 'notification') {
updateData[this.editorType] = {
"content": value
}; // 创建时间服务端生成
} else {
updateData[this.editorType] = value;
}
this.updateGroupInfo(updateData)
this.$refs.popupInfo.close()
},
setAddGroupType() {
if (!this.isAdmin) return
uni.showActionSheet({
itemList: ['自由加入', '需要验证权限', '禁止加入'],
success: (e) => {
let join_option = ['freeAccess', 'needPermission', 'disableApply'][e.tapIndex]
this.updateGroupInfo({
join_option
})
},
fail: (err) => {
console.error("err: ", err);
}
})
},
async updateGroupInfo(group_info) {
// console.log('group_info---------',group_info);
this.conversation.group_info = Object.assign(this.conversation.group_info, group_info)
let res = await db.collection('uni-im-group')
.doc(this.conversation.group_id)
.update(group_info)
// console.log('change group info', res.result.updated,this.conversation);
},
async setAvatar() {
if (!this.isAdmin) return
const crop = {
quality: 100,
width: 600,
height: 600,
resize: true
};
uni.chooseImage({
count: 1,
crop,
success: async (res) => {
let tempFile = res.tempFiles[0],
avatar_file = {
// #ifdef H5
extname: tempFile.name.split('.')[tempFile.name.split('.').length - 1],
// #endif
// #ifndef H5
extname: tempFile.path.split('.')[tempFile.path.split('.').length - 1]
// #endif
},
filePath = res.tempFilePaths[0]
// #ifndef APP-PLUS
//非app端用前端组件剪裁头像,app端用内置的原生裁剪
let isPC = false
// #ifdef H5
isPC = !['ios', 'android'].includes(uni.getSystemInfoSync().platform)
// #endif
if (!isPC) {
filePath = await new Promise((callback) => {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/users/cropImage/cropImage?path=' +
filePath + `&options=${JSON.stringify(crop)}`,
animationType: "fade-in",
events: {
success: url => {
callback(url)
}
},
complete(e) {
// console.log(e);
}
});
})
}
// #endif
// console.log(this.users);
let cloudPath = uniCloud.getCurrentUserInfo().uid + '' + Date.now()
avatar_file.name = cloudPath
uni.showLoading({
title: "更新中",
mask: true
});
let {
fileID
} = await uniCloud.uploadFile({
filePath,
cloudPath,
fileType: "image"
});
// console.log(result)
avatar_file.url = fileID
// console.log({avatar_file});
uni.hideLoading()
this.updateGroupInfo({
avatar_file
})
}
})
},
// #ifdef H5
share() {
// 获取当前域名
const data = location.origin + "/#/?joinGroup=" + this.conversation.group_info._id
uni.setClipboardData({
data,
showToast: false,
success: () => {
uni.showToast({
title: "已成功复制分享链接",
duration: 2000,
icon: 'none'
});
}
})
// uni.navigateTo({
// url: '/uni_modules/uni-im/pages/group/groupQRCode?id=' +
// this.conversation.group_info._id +
// '&name=' + this.conversation.group_info.name +
// '&avatar_file=' + url,
// complete: (e) => {
// // console.log(e);
// }
// });
},
// #endif
joinGroup() {
db.collection('uni-im-group-join').add({
"group_id": this.conversation.group_id,
"message": ''
}).then((res) => {
// console.log("res: ", res);
uni.showToast({
icon: 'none',
title: '已申请'
})
})
},
setMuteAllMembers(e) {
for (let user_id in this.conversation.group_member) {
const member = this.conversation.group_member[user_id]
member.mute_type += (e.value ? 1 : -1)
}
this.updateGroupInfo({
"mute_all_members": e.value
})
},
changeConversationMute(e) {
this.conversation.changeMute()
}
}
}
</script>
<style lang="less" scoped>
/* #ifndef APP-NVUE */
page,
/* #endif */
.group-info-box {
width: 750rpx;
flex: 1;
background-color: #f5f5f5;
/* #ifdef H5 */
@media screen and (min-device-width:960px) {
height: calc(95vh - 70px);
overflow-y: auto;
flex: none;
}
/* #endif */
}
.user-list-container {
width: 750rpx;
flex-direction: row;
flex-wrap: wrap;
/* #ifndef APP-NVUE */
overflow-y: hidden;
/* #endif */
/* #ifdef APP-NVUE */
height: 150px;
/* #endif */
}
/* #ifdef H5 */
.windows .user-list-container {
padding-right: 10rpx;
}
/* #endif */
.item {
width: 150rpx;
height: 150rpx;
margin: 5px 0;
align-items: center;
justify-content: center;
position: relative;
}
.item.focus {
border: 1px dashed #ccc;
}
.group-admin {
position: absolute;
top: 5px;
right: 5px;
padding: 1px 3px;
border-radius: 6px;
background-color: #e64141;
color: #fff;
font-size: 12px;
}
.mute-type-1 {
position: absolute;
padding: 1px 10px;
background-color: #0b60ff;
color: #fff;
font-size: 10px;
bottom: 28px;
}
/* #ifdef H5 */
.pointer {
cursor: pointer;
}
.windows .user-list-container .item {
width: 145rpx;
}
/* #endif */
.avatar {
width: 100rpx;
height: 100rpx;
border-radius: 10px;
box-shadow: 0 0 1px #aaa;
}
.nickname {
width: 150rpx;
text-align: center;
font-size: 14px;
color: #666;
padding: 0 16rpx;
/* #ifndef APP-NVUE */
white-space: nowrap;
/* #endif */
overflow: hidden;
text-overflow: ellipsis;
lines: 1;
}
.logo {
width: 50px;
height: 50px;
}
.exitGroup {
margin: 10px 0;
background-color: #FFFFFF;
padding: 6px 0;
color: #e64141;
border-radius: 0;
font-size: 16px;
text-align: center;
padding: 15px 0;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
/* #ifndef APP-NVUE */
.exitGroup::after {
display: none;
}
/* #endif */
.group-info-text {
color: #666;
font-size: 14px;
/* #ifndef APP-NVUE */
max-width: 560rpx;
/* #endif */
/* #ifdef APP-NVUE */
width: 560rpx;
/* #endif */
text-align: right;
}
.join_option {
color: #666;
font-size: 14px;
}
</style>
\ No newline at end of file
<template>
<view class="group-members-box">
<uni-search-bar v-model="keyword" class="search-bar" radius="5" placeholder="输入昵称搜索" clearButton="auto"
cancelButton="none"></uni-search-bar>
<view v-if="!leave_group" class="members-list-container" :class="{'show-less':!showAllMember}">
<view class="invite-box item" v-if="isAdmin">
<view class="invite">
<uni-icons @click="invite" color="#989898" size="20px" type="plusempty"></uni-icons>
</view>
<text class="nickname">邀请</text>
</view>
<template v-for="(member,index) in memberList" :key="index">
<view class="item" :title="member.users.nickname"
:class="{'pointer': canPrivateChat,'focus':member.focus}" @click="toChat(member.users._id)"
@longpress.prevent="openConversationMenu($event,index)"
@contextmenu.prevent="openConversationMenu($event,index)"
@mousemove="hoverUserId = member.users._id"
>
<image class="avatar"
:src="(member.users.avatar_file && member.users.avatar_file.url) ? member.users.avatar_file.url:'/uni_modules/uni-im/static/avatarUrl.png'"
mode="widthFix"></image>
<text class="nickname">{{member.users.nickname||'匿名用户'}}</text>
<text v-if="member.role.includes('admin')" class="group-admin">管</text>
<text v-if="!mute_all_members && member.mute_type" class="mute-type-1">已被禁言</text>
</view>
</template>
</view>
<view class="show-all-menber" v-if="showMoreBtn">
<view class="show-all-menber-btn" @click="onClickShowAllMenber">
<text>{{showAllMember ? '收起' : '查看更多'}}</text>
</view>
</view>
<uni-im-contextmenu ref="uni-im-contextmenu"></uni-im-contextmenu>
</view>
</template>
<script>
const db = uniCloud.database()
import uniIm from '@/uni_modules/uni-im/sdk/index.js';
export default {
data() {
return {
conversation: {
group_info: {
user_id: "",
mute_all_members: false
},
group_member: {},
mute: false
},
leave_group: false,
member_list: [],
isManage: false,
editorFields: {
"name": " 群聊名称",
"introduction": "群简介",
"notification": "群公告"
},
editorType: '',
editorDefaultValue: '',
groupType: '',
isAdmin: false,
keyword: '',
mute_all_members: false,
showAllMember: false,
// 鼠标在哪个用户id上
hoverUserId: '',
showMoreBtn: true,
// 延迟渲染,避免页面卡顿
laterRenderIndex: 1
};
},
computed: {
...uniIm.mapState(['isWidescreen']),
logoUrl() {
return this.conversation.group_info.avatar_file ? this.conversation.group_info.avatar_file.url : false
},
join_option() {
let val = this.conversation.group_info.join_option
return {
needPermission: "需要验证权限",
freeAccess: "自由加入",
disableApply: "禁止加入"
} [val]
},
memberList() {
let memberList = this.member_list
// 根据关键词搜索
.filter(member => {
// 忽略大小写
return member.users.nickname.toLowerCase().includes(this.keyword.toLowerCase())
})
// 是管理员排序靠前
.sort((a, b) => {
if (a.role.includes('admin') && !b.role.includes('admin')) {
return -1
} else if (!a.role.includes('admin') && b.role.includes('admin')) {
return 1
} else {
return 0
}
})
// laterRenderIndex
.filter((item, index) => {
return index < this.laterRenderIndex * 50
})
const memberCount = memberList.length
if(memberCount && memberCount < 10){
console.log('this.member_list.length',this.member_list.length);
this.showMoreBtn = false
}
if (!this.showAllMember) {
memberList = memberList.slice(0, 9)
}
return memberList
},
isGroupCreator() {
return this.conversation.group_info.user_id == uniCloud.getCurrentUserInfo().uid
},
canPrivateChat(){
// 当前登录的账号是管理员,或者当前消息是群管理员发的
return this.uniIDHasRole('staff') || this.hoverUserId && this.conversation.group_member[this.hoverUserId].role.includes('admin')
}
},
onReachBottom() {
this.laterRenderIndex++
},
watch: {
"conversation.group_info.user_id"(adminUserId) {
// 当前用户是群的创建者或者管理员(在群成员中找到当前用户的角色包含admin)
const currentUserId = uniCloud.getCurrentUserInfo().uid
this.isAdmin = this.isGroupCreator || this.conversation.group_member[currentUserId]?.role.includes('admin')
//非群主 隐藏【管理】按钮
if (!this.isAdmin) {
if (!this.isWidescreen) {
// #ifdef H5
let dom = document.getElementsByClassName('uni-page-head-btn')[1]
dom.style.visibility = 'hidden'; // 隐藏元素
// #endif
// #ifdef APP-PLUS
var webview = this.$scope.$getAppWebview();
// console.log('webview', webview);
webview.setStyle({
titleNView: {
buttons: []
}
});
// #endif
}
} else {
if (this.isWidescreen) {
this.isManage = true
}
}
},
"conversation.group_member": {
handler(group_member, oldValue) {
// console.log('group_member',group_member);
this.member_list = []
for (let key in group_member) {
this.member_list.push(group_member[key])
}
if (!uniIm.isWidescreen) { // pc宽屏不需要
let title = "群信息(" + this.member_list.length + "人)"
uni.setNavigationBarTitle({
title
});
}
},
deep: true,
immediate: true
},
// (后续)通过监听实现实时切换管理员实时刷新权限
// console.log('isAdmin',isAdmin);
conversation: {
handler(conversation, oldValue) {
const currentUserId = uniCloud.getCurrentUserInfo().uid
this.isAdmin = this.isGroupCreator || this.conversation.group_member[currentUserId]?.role.includes('admin')
this.leave_group = conversation.leave
this.mute_all_members = conversation.group_info.mute_all_members
},
deep: true
},
},
props: {
conversation_id: {
default () {
return false
}
}
},
async onLoad(e) {
if (!e.conversation_id) {
throw new Error("会话id不能为空")
}
// 以页面的方式打开,不需要显示更多的按钮和隐藏部分用户
this.showMoreBtn = false
this.showAllMember = true
this.load(e.conversation_id)
},
mounted() { //pc端以组件模式加载时逻辑
if (this.conversation_id) {
this.load(this.conversation_id)
}
},
onShow() {
// console.log("this.conversation: ", this.conversation.group_member);
},
// 长按头像出现管理菜单
// onNavigationBarButtonTap(e) {
// if (e.index === 0) {}
// },
methods: {
async load(conversation_id) {
this.conversation = await uniIm.conversation.get(conversation_id)
},
async expelAndToBlack(item) {
uni.showModal({
title: '确定要将该用户移出本群并拉黑吗?',
content: '拉黑后此用户将不能再次加入本群,不能撤销,请谨慎操作',
cancelText: '取消',
confirmText: '确认',
complete: async (e) => {
// console.log(e);
if (e.confirm) {
uni.showLoading({
mask: true
});
try {
let res = await db.collection('uni-im-group-member').where({
user_id: item.users._id,
group_id: this.conversation.group_info._id
})
.remove()
console.log('expel', res);
const uniImCo = uniCloud.importObject("uni-im-co")
res = await uniImCo.addToGroupMenberBlackList({
user_id: item.users._id,
group_id: this.conversation.group_info._id
})
console.log('expelAndToBlack', res);
} catch (error) {
uni.showToast({
title: error.message,
icon: 'error',
complete: () => {}
});
}
uni.hideLoading()
}
}
});
},
async expel(item) {
uni.showModal({
title: '确定要将该用户移出本群吗?',
content: '不能撤销,请谨慎操作',
cancelText: '取消',
confirmText: '确认',
complete: async (e) => {
// console.log(e);
if (e.confirm) {
uni.showLoading({
mask: true
});
try {
let res = await db.collection('uni-im-group-member').where({
user_id: item.users._id,
group_id: this.conversation.group_info._id
})
.remove()
if (res.result.deleted) {
uni.showToast({
title: '成功移除',
icon: 'none',
complete: () => {}
});
// console.log('exitGroup', res);
}
} catch (error) {
uni.showToast({
title: error.message,
icon: 'error',
complete: () => {}
});
}
uni.hideLoading()
}
}
});
},
async changeMemberMute(item) {
let nickname = item.users.nickname || '匿名用户'
uni.showModal({
title: '确定要' + (item.mute_type ? `为"${nickname}"解除禁言吗?` : `禁言"${nickname}"吗?`),
cancelText: '取消',
confirmText: '确认',
complete: async (e) => {
// console.log(e);
if (e.confirm) {
uni.showLoading({
mask: true
});
try {
let res = await db.collection('uni-im-group-member').where({
_id: item._id,
mute_type: item.mute_type // 防止此时云端已经变化
})
.update({
mute_type: item.mute_type ? 0 : 1
})
// console.log('mute_type', res);
if (res.result.updated) {
item.mute_type = item.mute_type ? 0 : 1
uni.showToast({
title: '设置成功',
icon: 'none',
complete: () => {}
});
}
} catch (error) {
// console.log('error',merror)
uni.showToast({
title: error.message,
icon: 'error',
complete: () => {}
});
}
uni.hideLoading()
}
}
});
},
openConversationMenu(e, index) {
let member = this.memberList[index]
// console.log('member', member);
let menuList = []
if (this.isAdmin) {
menuList.unshift({
"title": "移除",
"action": () => {
// console.log('移除');
this.expel(member)
}
})
menuList.unshift({
"title": "移除并拉黑",
"action": () => {
console.log('移除并拉黑');
this.expelAndToBlack(member)
}
})
if (!this.conversation.group_info.mute_all_members) {
menuList.unshift({
"title": member.mute_type ? "解除禁言" : '设为禁言',
"action": () => {
// console.log('禁言');
this.changeMemberMute(member)
}
})
}
const isAdmin = member.role.includes('admin')
menuList.push({
"title": isAdmin ? "取消管理员" : "设置管理员",
"action": () => {
let role = member.role;
if (isAdmin) {
// console.log('取消管理员');
role = member.role.filter(item => item !== 'admin')
} else {
role.push('admin')
// console.log('设置管理员');
}
uni.showLoading({
mask: true
});
db.collection('uni-im-group-member').doc(member._id).update({
"role": role
}).then(res => {
// console.log('res', res);
member.role = role
})
.catch(err => {
console.error(err)
uni.showToast({
title: err.message,
icon: 'none'
});
})
.finally(() => {
uni.hideLoading()
})
}
})
}
if (menuList.length > 0) {
member.focus = true
const myContextmenu = this.$refs['uni-im-contextmenu']
const position = {
"contextmenu": {
"top": e.clientY,
"left": e.clientX
},
"longpress": {
"top": e.touches[0].screenY || e.touches[0].clientY,
"left": e.touches[0].screenX || e.touches[0].clientX
}
} [e.type]
// #ifdef H5
position.top = position.top + 120
// #endif
myContextmenu.show(position, menuList)
myContextmenu.onClose(() => {
member.focus = false
})
}
},
invite() {
// console.log('group_info._id', this.conversation.group_info._id);
uni.navigateTo({
url: '/uni_modules/uni-im/pages/contacts/createGroup/createGroup?group_id=' + this.conversation.group_info
._id
})
},
async exitGroup() {
const group_id = this.conversation.group_info._id
if (this.isGroupCreator) {
uni.showModal({
title: '确认要解散群聊吗?',
content: '不能撤销,请谨慎操作',
cancelText: '取消',
confirmText: '确认',
complete: async (e) => {
// console.log(e);
if (e.confirm) {
uni.showLoading({
mask: true
});
let res = await db.collection('uni-im-group')
.where({
_id: group_id
})
.remove()
.finally((res) => {
// uni.navigateBack({ // 收到离群的推送通知会自动关闭当前页面
// delta: 2
// })
uni.hideLoading()
})
// console.log('exitGroup', res);
}
}
});
} else {
uni.showModal({
title: '确认要退出群聊吗?',
content: '不能撤销,请谨慎操作',
cancelText: '取消',
confirmText: '确认',
complete: async (e) => {
// console.log(e);
if (e.confirm) {
// uni.navigateBack({ // 收到离群的推送通知会自动关闭当前页面
// delta: 2
// })
uni.showLoading({
mask: true
});
let res = await db.collection('uni-im-group-member').where({
user_id: uniCloud.getCurrentUserInfo().uid,
group_id
})
.remove()
// console.log(res.result);
if (res.result.deleted) {
uni.showToast({
title: '成功退出',
icon: 'none'
});
}
uni.hideLoading()
// console.log('exitGroup', res);
}
}
});
}
},
openPopupInfo(type) {
// console.log(type);
if (!this.isAdmin) return
this.editorType = type
this.editorDefaultValue = this.conversation.group_info[type]
if (this.editorType == "notification") {
this.editorDefaultValue = this.editorDefaultValue?.content || ""
}
this.$refs.popupInfo.open()
},
closePopupInfo() {
this.$refs.popupInfo.close()
},
confirmPopupInfo(value) {
if (!value) {
uni.showToast({
title: '内容不能为空!',
icon: 'none'
});
return
}
// console.log('value', value);
const updateData = {};
if (this.editorType == 'notification') {
updateData[this.editorType] = {
"content": value
}; // 创建时间服务端生成
} else {
updateData[this.editorType] = value;
}
this.updateGroupInfo(updateData)
this.$refs.popupInfo.close()
},
setAddGroupType() {
if (!this.isAdmin) return
uni.showActionSheet({
itemList: ['自由加入', '需要验证权限', '禁止加入'],
success: (e) => {
let join_option = ['freeAccess', 'needPermission', 'disableApply'][e.tapIndex]
this.updateGroupInfo({
join_option
})
},
fail: (err) => {
console.error("err: ", err);
}
})
},
async updateGroupInfo(group_info) {
// console.log('group_info---------',group_info);
this.conversation.group_info = Object.assign(this.conversation.group_info, group_info)
let res = await db.collection('uni-im-group')
.doc(this.conversation.group_id)
.update(group_info)
// console.log('change group info', res.result.updated,this.conversation);
},
async setAvatar() {
if (!this.isAdmin) return
const crop = {
quality: 100,
width: 600,
height: 600,
resize: true
};
uni.chooseImage({
count: 1,
crop,
success: async (res) => {
let tempFile = res.tempFiles[0],
avatar_file = {
// #ifdef H5
extname: tempFile.name.split('.')[tempFile.name.split('.').length - 1],
// #endif
// #ifndef H5
extname: tempFile.path.split('.')[tempFile.path.split('.').length - 1]
// #endif
},
filePath = res.tempFilePaths[0]
// #ifndef APP-PLUS
//非app端用前端组件剪裁头像,app端用内置的原生裁剪
let isPC = false
// #ifdef H5
isPC = !['ios', 'android'].includes(uni.getSystemInfoSync().platform)
// #endif
if (!isPC) {
filePath = await new Promise((callback) => {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/users/cropImage/cropImage?path=' +
filePath + `&options=${JSON.stringify(crop)}`,
animationType: "fade-in",
events: {
success: url => {
callback(url)
}
},
complete(e) {
// console.log(e);
}
});
})
}
// #endif
// console.log(this.users);
let cloudPath = uniCloud.getCurrentUserInfo().uid + '' + Date.now()
avatar_file.name = cloudPath
uni.showLoading({
title: "更新中",
mask: true
});
let {
fileID
} = await uniCloud.uploadFile({
filePath,
cloudPath,
fileType: "image"
});
// console.log(result)
avatar_file.url = fileID
// console.log({avatar_file});
uni.hideLoading()
this.updateGroupInfo({
avatar_file
})
}
})
},
// #ifdef H5
share() {
// 获取当前域名
const data = location.origin + "/#/?joinGroup=" + this.conversation.group_info._id
uni.setClipboardData({
data,
showToast: false,
success: () => {
uni.showToast({
title: "已成功复制分享链接",
duration: 2000,
icon: 'none'
});
}
})
// uni.navigateTo({
// url: '/uni_modules/uni-im/pages/group/groupQRCode?id=' +
// this.conversation.group_info._id +
// '&name=' + this.conversation.group_info.name +
// '&avatar_file=' + url,
// complete: (e) => {
// // console.log(e);
// }
// });
},
// #endif
joinGroup() {
db.collection('uni-im-group-join').add({
"group_id": this.conversation.group_id,
"message": ''
}).then((res) => {
// console.log("res: ", res);
uni.showToast({
icon: 'none',
title: '已申请'
})
})
},
toChat(user_id) {
if (this.canPrivateChat) {
uniIm.toChat({
user_id,
source:{
group_id: this.conversation.group_id
}
})
}
},
onClickShowAllMenber() {
if(this.isWidescreen){
this.showAllMember = !this.showAllMember
}else{
uni.navigateTo({
url: '/uni_modules/uni-im/pages/group/members?conversation_id=' + this.conversation.id
})
}
},
setMuteAllMembers(e) {
for (let user_id in this.conversation.group_member) {
const member = this.conversation.group_member[user_id]
member.mute_type += (e.value ? 1 : -1)
}
this.updateGroupInfo({
"mute_all_members": e.value
})
},
changeConversationMute(e) {
this.conversation.changeMute()
}
}
}
</script>
<style lang="less" scoped>
/* #ifndef APP-NVUE */
page,
/* #endif */
.group-members-box {
width: 750rpx;
// flex: 1;
background-color: #f5f5f5;
/* #ifdef H5 */
@media screen and (min-device-width:960px) {
// height: calc(95vh - 70px);
// overflow-y: auto;
// flex: none;
}
/* #endif */
}
.members-list-container {
width: 750rpx;
flex-direction: row;
flex-wrap: wrap;
}
.item {
width: 150rpx;
height: 140rpx;
margin: 5px 0;
align-items: center;
justify-content: center;
position: relative;
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
}
/* #ifdef H5 */
// 收缩
.show-less {
max-height: 400px;
}
.windows .item {
width: 146rpx;
}
/* #endif */
.item.focus {
border: 1px dashed #ccc;
}
.group-admin {
position: absolute;
top: 5px;
right: 5px;
padding: 1px 3px;
border-radius: 6px;
background-color: #e64141;
color: #fff;
font-size: 12px;
}
.mute-type-1 {
position: absolute;
padding: 1px 10px;
background-color: #0b60ff;
color: #fff;
font-size: 10px;
bottom: 28px;
}
/* #ifdef H5 */
.pointer {
cursor: pointer;
}
/* #endif */
.avatar {
width: 100rpx;
height: 100rpx;
border-radius: 10px;
box-shadow: 0 0 1px #aaa;
}
.nickname {
width: 140rpx;
text-align: center;
font-size: 14px;
color: #666;
padding: 0 16rpx;
/* #ifndef APP-NVUE */
white-space: nowrap;
/* #endif */
overflow: hidden;
text-overflow: ellipsis;
lines: 1;
}
.logo {
width: 50px;
height: 50px;
}
.invite-box {
align-items: center;
}
.invite {
width: 100rpx;
height: 100rpx;
justify-content: center;
border-radius: 10px;
border: #ccc dashed 1px;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
.exitGroup {
margin: 10px 0;
background-color: #FFFFFF;
padding: 6px 0;
color: #e64141;
border-radius: 0;
font-size: 16px;
text-align: center;
padding: 15px 0;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
/* #ifndef APP-NVUE */
.exitGroup::after {
display: none;
}
/* #endif */
.group-info-text {
color: #666;
font-size: 14px;
/* #ifndef APP-NVUE */
max-width: 560rpx;
/* #endif */
/* #ifdef APP-NVUE */
width: 560rpx;
/* #endif */
text-align: right;
}
.join_option {
color: #666;
font-size: 14px;
}
.slot-code {
align-items: center;
flex-direction: row;
}
.group-code {
width: 50rpx;
height: 50rpx;
margin-left: 10rpx;
}
.show-all-menber {
margin: 5px 0;
flex: 1;
justify-content: center;
}
.show-all-menber-btn {
flex-direction: row;
font-size: 14px;
justify-content: center;
/* #ifndef APP-NVUE */
cursor: pointer;
/* #endif */
color: #888;
}
.show-all-menber-btn:hover {
color: #666;
}
</style>
\ No newline at end of file
<template>
<view id="page">
<!-- #ifdef H5 -->
<!-- 底部栏 -->
<view id="foot" v-if="isWidescreen">
<view class="item">
<image class="icon" src="https://web-assets.dcloud.net.cn/unidoc/zh/git-1.png" mode="widthFix"></image>
<text>本项目已开源</text>
<uni-link class="link" href="https://gitcode.net/dcloud/hello-uni-im/-/tree/v3" text="git仓库地址"></uni-link>
</view>
<view class="item">
<image class="icon" src="https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/hx.png" mode="widthFix"></image>
<text>离线通知插件</text>
<uni-link class="link" href="https://ext.dcloud.net.cn/plugin?id=16984" text="下载地址"></uni-link>
</view>
</view>
<!-- 布局最左侧 菜单栏 -->
<view id="left-view" v-if="isWidescreen" @click="chatInfoIsShow = false;">
<cloud-image class="user-avatar" @click="toUcenter" :src="avatarUrl" width="40px" height="40px"
borderRadius="100px"></cloud-image>
<!-- {{currentUserInfo.username}} -->
<uni-badge @contextmenu.prevent.native="openConversationMenu($event,'unreadMsgCount')" class="chat-icon" size="small" :text="unreadMsgCount" absolute="rightTop" type="error">
<uni-icons @click="showChatView"
:color="contactsViewIsShow?'#c5c5c5':'#5fc08e'" size="32" type="chatbubble-filled"></uni-icons>
</uni-badge>
<uni-badge id="show-contacts-btn" size="small" :text="unreadnotificationCount" absolute="rightTop" type="error">
<uni-icons @click="showContactsView" :color="contactsViewIsShow?'#5fc08e':'#c5c5c5'" size="32"
type="staff-filled"></uni-icons>
</uni-badge>
</view>
<!-- #endif -->
<!-- 会话列表 -->
<view id="center-view">
<!-- #ifdef H5 -->
<!-- 搜索会话用户、聊天记录... -->
<view id="search-bar-box" v-if="isWidescreen">
<uni-search-bar v-model="keyword" id="search-bar" radius="5" placeholder="搜索" clearButton="auto" cancelButton="none"></uni-search-bar>
<uni-icons class="pointer" @click="beforeJoinGroup" color="#aaa" size="26" type="plus"></uni-icons>
</view>
<view id="uni-im-contacts-box" v-show="contactsViewIsShow" v-if="isWidescreen">
<uni-im-contacts @clickMenu="clickMenu" id="uni-im-contacts" ref="uni-im-contacts"></uni-im-contacts>
</view>
<!-- #endif -->
<!-- 会话用户列表 -->
<uni-im-conversation-list
v-if="!keyword"
ref="uni-im-conversation-list" @clickItem="toChat($event.id)"
@change="conversationList = $event" :active-conversation-id="currentConversationId"
id="conversation-list-box"
></uni-im-conversation-list>
<!-- 会话查找结果列表 -->
<uni-im-filtered-conversation-list
v-else
ref="uni-im-filtered-conversation-list"
id="conversation-list-box"
:keyword="keyword"
@to-chat="toChat($event)"
@to-chat-filtered="toChatFiltered($event)"
></uni-im-filtered-conversation-list>
</view>
<!-- #ifdef H5 -->
<view id="right-view" v-if="isWidescreen">
<!-- 聊天窗口 -->
<view id="chat-view-box">
<template v-if="!contactsViewIsShow && currentConversationId && !filteredConversationId">
<view id="chat-header" v-if="!contactsViewIsShow">
<uni-icons @click="showChatInfo" class="more" type="more-filled" size="20"></uni-icons>
</view>
<view id="chat-view">
<chat-view ref="chat-view"></chat-view>
</view>
<view v-if="chatInfoIsShow" class="chatInfoBox" @click.stop="chatInfoIsShow = false">
<uni-im-group-info @click.native.stop v-if="currentConversation.group_id"
:conversation_id="currentConversation.id"></uni-im-group-info>
<uni-im-chat-info @click.native.stop v-else
:conversation_id="currentConversation.id"></uni-im-chat-info>
</view>
</template>
<template v-else-if="!contactsViewIsShow && currentConversationId && filteredConversationId">
<view id="chat-header">
<uni-icons @click="showChatInfo" class="more" type="more-filled" size="20"></uni-icons>
</view>
<view id="chat-view">
<chat-filtered
ref="chat-filtered"
@to-chat="toChat($event)"
/>
</view>
<view v-if="chatInfoIsShow" class="chatInfoBox" @click.stop="chatInfoIsShow = false">
<uni-im-group-info
v-if="currentConversation.group_id"
@click.native.stop
:conversation_id="currentConversation.id"
/>
<uni-im-chat-info
v-else
@click.native.stop
:conversation_id="currentConversation.id"
/>
</view>
</template>
<view v-else id="ccid-is-null-tip">
未选择会话对象
</view>
</view>
<view id="dynamic-component-box" v-show="contactsViewIsShow">
<view class="dynamic-component-title">{{view2Title}}</view>
<component ref="dynamicComponent" :is="dynamicComponentName"></component>
</view>
</view>
<uniImVideo></uniImVideo>
<uni-im-contextmenu ref="uni-im-contextmenu" />
<!-- #endif -->
</view>
</template>
<script>
import {
store as uniIdStore,
mutations as uniIdMutations
} from '@/uni_modules/uni-id-pages/common/store.js';
import uniIm from '@/uni_modules/uni-im/sdk/index.js';
import contacts from '@/uni_modules/uni-im/pages/contacts/contacts';
// #ifdef H5
import chatView from '@/uni_modules/uni-im/pages/chat/chat';
import chatFiltered from '@/uni_modules/uni-im/pages/chat/chat-filtered';
import notification from '@/uni_modules/uni-im/pages/contacts/notification/notification';
import addPeopleGroups from '@/uni_modules/uni-im/pages/contacts/addPeopleGroups/addPeopleGroups';
import groupList from '@/uni_modules/uni-im/pages/contacts/groupList/groupList';
import createGroup from '@/uni_modules/uni-im/pages/contacts/createGroup/createGroup';
import chatInfo from '@/uni_modules/uni-im/pages/chat/info';
import groupInfo from '@/uni_modules/uni-im/pages/group/info';
import uniImVideo from '@/uni_modules/uni-im/pages/common/video/video';
let currentScrollTop = 0
// #endif
let lastConversationId = false
export default {
// #ifdef H5
components: {
chatView,
chatFiltered,
"uni-im-contacts": contacts,
"uni-im-notification": notification,
"uni-im-addPeopleGroups": addPeopleGroups,
"uni-im-groupList": groupList,
"uni-im-createGroup": createGroup,
"uni-im-chat-info": chatInfo,
"uni-im-group-info": groupInfo,
uniImVideo
},
// #endif
data() {
return {
wHeight: 'auto',
users: {},
dynamicComponentName: 'uni-im-addPeopleGroups', //通过动态组件引入页面在pc端显示
view2Title: '加人/加群',
contactsViewIsShow: false,
chatInfoIsShow: false,
currentConversation: {},
keyword:'',
conversationList: [],
filteredConversationId: false, // 仅显示匹配的聊天记录的会话
};
},
computed: {
// 导入uniIm响应式数据,支持别名:比如:['a as b']
...uniIm.mapState(['currentConversationId', 'isWidescreen']),
unreadMsgCount() {
return uniIm.conversation.unreadCount()
},
unreadnotificationCount() {
return uniIm.notification.unreadCount()
},
currentUserInfo() {
return uniIdStore.userInfo
},
avatarUrl() {
if (this.currentUserInfo.avatar_file && this.currentUserInfo.avatar_file.url) {
return this.currentUserInfo.avatar_file.url
}
return '/uni_modules/uni-im/static/avatarUrl.png'
}
},
watch: {
unreadMsgCount: {
handler(unreadMsgCount) {
// console.log({
// unreadMsgCount
// });
// #ifdef APP
plus.runtime.setBadgeNumber(unreadMsgCount)
// #endif
if (unreadMsgCount == 0) {
uni.removeTabBarBadge({
index: 0,
complete: (e) => {
// console.log(e)
}
})
} else {
uni.setTabBarBadge({
index: 0,
text: unreadMsgCount + '',
complete: (e) => {
// console.log(e)
}
})
}
// 调用扩展点,更新未读消息总数。
uniIm.extensions.invokeExts('ui-update-unread-count', unreadMsgCount)
},
immediate: true,
},
contactsViewIsShow(contactsViewIsShow) {
if (contactsViewIsShow) {
lastConversationId = this.currentConversationId
uniIm.currentConversationId = false
} else {
if (lastConversationId) {
uniIm.currentConversationId = lastConversationId
this.$nextTick(() => {
this.toChat(lastConversationId)
})
}
}
},
// 根据当前会话id,设置会话对象
async currentConversationId(id) {
this.currentConversation = await uniIm.conversation.get(id)
// 如果是被隐藏的会话,取消隐藏
if(this.currentConversation.hidden){
this.currentConversation.hidden = false
}
}
},
beforeCreate() {
// #ifdef APP-NVUE
this.wHeight = uni.getSystemInfoSync().windowHeight + 'px'
// #endif
},
async onLoad(param) {
console.log('onLoad',param)
/**
* 打开index页面之前的扩展点,用于自己扩展登录等逻辑
*/
await Promise.all(uniIm.extensions.invokeExts('index-load-before-extra', param))
const {tokenExpired} = uniCloud.getCurrentUserInfo()
if (tokenExpired < Date.now()) {
console.info('当前用户的登录状态无效,将自动跳转至登录页面。', param)
let url = '/uni_modules/uni-id-pages/pages/login/login-withpwd?uniIdRedirectUrl='
let paramString = '/uni_modules/uni-im/pages/index/index?'
for (let key in param) {
paramString += `${key}=${param[key]}&`
}
paramString = paramString.substring(0, paramString.length - 1) //携带参数,实现登录成功后 跳回首页时传回
url += encodeURIComponent(paramString)
return uni.reLaunch({
url
})
}
uniIm.onInitDataAfter(()=>{
// console.log('onUniImInitDataAfter');
// 执行当前页面初始化
this.init(param)
})
},
async onReady() {
uni.$on('uni-im-toChat', param => {
if (param) {
// 关闭最后一次的会话id,防止切回后重新显示最后一次会话,而指定显示到某个会话
lastConversationId = false
this.toChat(param)
}
this.contactsViewIsShow = false
})
// #ifdef H5
const shortcutKeyFn = (keyName,event)=>{
const index = this.conversationList.findIndex(item=>item.id == this.currentConversationId)
if(keyName == 'ArrowUp' && index > 0){
this.toChat( this.conversationList[index - 1].id )
event.preventDefault();
}else if(keyName == 'ArrowDown' && index < this.conversationList.length){
this.toChat( this.conversationList[index + 1].id )
event.preventDefault();
}
}
uniIm.utils.shortcutKey.withMeta(shortcutKeyFn)
uniIm.utils.shortcutKey.withCtrl(shortcutKeyFn)
let systemInfo = uni.getSystemInfoSync()
uniIm.systemInfo = systemInfo
if (systemInfo.browserName != 'chrome' && (this.isWidescreen == true || systemInfo.osName != 'ios')) {
let newElement = document.createElement('div')
newElement.innerHTML = `
<div id="tip-browser-compatibility" style="background: #fff9ea;color: #ff9a43;position: fixed;top: 0;left: 0;width: 100vw;padding: 15px;font-size: 18px;">
注意:本系统仅兼容chrome浏览器,其他浏览器可能出现异常。<a href="https://www.google.cn/chrome/">点此下载chrome浏览器</a>
</div>
`
document.body.appendChild(newElement)
}
// 设置 right-view的宽为屏幕的百分之99
// document.querySelector('#right-view').style.width = systemInfo.screenWidth - 360 + 'px'
// #endif
},
onUnload() {
},
onHide() {},
methods: {
clickMenu(data) {
// console.log('79879789798898798978789', data);
this.dynamicComponentName = data.componentName
if (data.title) {
this.view2Title = data.title
}
this.$nextTick(() => {
this.$refs.dynamicComponent.setParam(data.param || {})
if (data.componentName == 'uni-im-createGroup') {
this.$refs.dynamicComponent.getFriendsData()
}
})
// console.log('data.componentName----',data.componentName);
},
/**
* @description 根据群id,申请加入群聊
* @param {Object} 群id
*/
joinGroup(group_id){
console.log('group_id',group_id);
const db = uniCloud.database();
uni.showLoading({
title: '正在申请加群...',
mask: true
});
db.collection('uni-im-group-join').add({
group_id,
"message":''
}).then((res) => {
console.log("res: ",res);
if(res.result.isPass){
this.toChat("group_" + group_id)
}else{
uni.showToast({
icon: 'none',
title: '已提交加群申请,等待管理员审核'
})
}
}).catch((err) => {
if(err.message === "已经是群成员"){
console.log('已经是群成员 直接打开对应页面');
return this.toChat("group_" + group_id)
}
uni.showModal({
content: err.message || '请求服务失败',
showCancel: false
})
})
.finally(()=>{
uni.hideLoading()
})
},
// #ifdef H5
beforeJoinGroup(){
let group_id = prompt("请输入群id", "");
if (group_id) {
this.joinGroup(group_id)
}
},
// #endif
readQrCode() {
uni.scanCode({
complete: (e) => {
// console.log(e);
try {
let data = JSON.parse(e.result)
// console.log(data);
if (data.type == 'uni-im' && data.subType == "groupInfo") {
}
} catch (e) {
}
}
})
},
async init({
conversation_id,
goods,
user_id,
joinGroup
}) {
// console.log('init', {
// conversation_id,
// goods,
// user_id
// });
// 如果列表小于30个会话,尝试着从云端拉取一次
if( this.conversationList.length < 30 ){
await this.$nextTick()
await this.$refs['uni-im-conversation-list'].loadMore()
}else{
console.log('会话列表已满一页,需要用户自己滚动到底,再拉取更多');
}
// console.log('this.conversationList.length',this.conversationList.length);
if (conversation_id) {
console.log('conversation_id', conversation_id);
this.toChat(conversation_id)
} else if (user_id) {
//创建会话
const currentConversation = await uniIm.conversation.get({
friend_uid: user_id
})
// console.log('currentConversation', currentConversation);
// 当前用户给对方发个消息
this.toChat(currentConversation.id)
} else {
if (this.isWidescreen) {
let [firstConversation] = this.conversationList
if (firstConversation) {
this.currentConversation = await uniIm.conversation.get(firstConversation.id)
this.toChat(firstConversation.id)
} else {
// uni.showModal({
// content: '没有任何会话,请先到用户列表选择用户',
// showCancel: false
// });
}
}
}
if(user_id){
// 如果初始化时,指定了要访问的user会话。将指定要访问的会话排序位置置顶,方便看到
// 场景:插件市场,点击联系作者。自动将此会话放到首个
setTimeout(()=> {
this.currentConversation.customIndex = Date.now()
}, 0);
}
// 传递参数goods(对象格式,包含:商品名称name,链接url。自动设置对话框默认内容
if (this.isWidescreen && goods) {
// console.log(goods);
if (typeof goods != 'object') {
goods = JSON.parse(goods)
}
const {
name,
url
} = goods
if (name && url) {
setTimeout(()=>{
this.$refs['chat-view'].chatInputContent = '【' + name + ':' + url + '】'
}, 1000);
}
}
/**
* 在本页面链接传递参数 joinGroup=group_id即可申请加入群,
* 比如:http://localhost:8082/#/uni_modules/uni-im/pages/index/index?joinGroup=xxx
*/
if(joinGroup){
// #ifdef H5
//删除URL后面的参数(主要是删除joinGroup=xxx),且不刷新页面
history.pushState({}, '', '/#/');
// #endif
this.joinGroup(joinGroup)
};
},
search(e) {
// console.log("search-e: " + JSON.stringify(e));
uni.showToast({
title: '加好友功能入口,暂时在左侧菜单的通讯录中',
icon: 'none',
duration: 3000
});
},
addUser() {
uni.showToast({
title: '加好友功能入口,暂时在左侧菜单的通讯录中',
icon: 'none',
duration: 3000
});
},
showChatView() {
this.contactsViewIsShow = false
// 拿到所有存在未读消息的会话对象
const ucId = uniIm.conversation.dataList
.filter(item => item.unread_count > 0)
.filter(item => !item.mute)
.map(item => item.id)
if(ucId.length > 0){
let index = ucId.findIndex(item => item.id == this.currentConversation.id)
// 如果当前会话存在未读消息,则切换到下一个会话。如果当前会话不存在未读消息,则切换到第一个存在未读消息的会话
index >= 0 ? index ++ : index = 0;
this.toChat(ucId[index])
}
},
showContactsView() {
this.contactsViewIsShow = true
// 判断是不是第一次打开
if(!this.showContactsView.firstOpen){
this.showContactsView.firstOpen = true
this.$nextTick(() => {
const contactsRef = this.$refs['uni-im-contacts']
contactsRef.openPages(contactsRef.menuList[0])
})
}
},
toChatFiltered({ conversation_id, count, keyword }) {
this.chatInfoIsShow = false;
this.filteredConversationId = conversation_id
uniIm.currentConversationId = conversation_id
if (this.isWidescreen) { // 若为宽屏,则切换右侧的组件
this.$nextTick(() => {
let chatFilteredRef = this.$refs['chat-filtered']
if (chatFilteredRef) {
chatFilteredRef.load({
conversation_id,
keyword,
count,
})
}
})
} else { // 若为窄屏,则打开新窗体
uni.navigateTo({
url: '/uni_modules/uni-im/pages/chat/chat-filtered'
+ `?conversation_id=${conversation_id}`
+ `&keyword=${encodeURIComponent(keyword)}`
+ `&count=${count}`,
animationDuration: 300
})
}
},
async toChat(param) {
this.chatInfoIsShow = false;
// console.log('toChat param',param);
let conversation_id = getConversationId(param)
this.keyword = ''
this.filteredConversationId = false
uniIm.currentConversationId = conversation_id
console.log('conversation_id',conversation_id);
if(conversation_id.indexOf('group_') === 0){
uni.showLoading({
title: '加载中',
mask: false
});
const conversation = await uniIm.conversation.get(conversation_id)
uni.hideLoading()
}
if (this.isWidescreen) { // 若为宽屏,则切换右侧的组件
this.$nextTick(() => {
let chatViewRef = this.$refs['chat-view']
if (chatViewRef) {
chatViewRef.load(conversation_id)
}
})
} else { // 若为窄屏,则打开新窗体
uni.navigateTo({
url: '/uni_modules/uni-im/pages/chat/chat?conversation_id=' + conversation_id,
animationDuration: 300
})
}
function getConversationId(param){
if (typeof param == 'string') {
return param
} else {
if (param.conversation_id) {
return param.conversation_id
} else if (param.group_id) {
return 'group_' + param.group_id
} else if (param.user_id || param.friend_uid) {
return uniIm.utils.getConversationId(param.user_id || param.friend_uid)
} else {
throw new Error("toChat param is error")
}
}
}
},
showChatInfo() {
this.chatInfoIsShow = !this.chatInfoIsShow
},
toUcenter() {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/userinfo/userinfo?showLoginManage=true',
complete(e) {
console.log("e: " + JSON.stringify(e));
}
})
},
openConversationMenu(e,name){
const myContextmenu = this.$refs['uni-im-contextmenu']
let menuList = []
if(name == 'unreadMsgCount' && this.unreadMsgCount > 0){
menuList.push({
"title": "清空所有未读消息数",
"action": () => {
console.log('清空所有未读消息数')
uniIm.conversation.clearUnreadCount()
}
})
}
if(menuList.length == 0){
return
}
console.log('menuList.length',menuList.length)
myContextmenu.show({
"top": e.clientY + 35,
"left": e.clientX
}, menuList)
// myContextmenu.onClose(() => {
// console.log('关闭右键菜单')
// })
}
},
async onReachBottom() {
console.error('onReachBottom');
await this.$refs['uni-im-conversation-list']?.loadMore()
},
onNavigationBarButtonTap() {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/userinfo/userinfo?showLoginManage=true',
complete: e => {
console.log(e);
}
});
}
}
</script>
<style lang="scss" scoped>
@import url("./index.scss");
</style>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册