前端
H5同步APP代码
/common/free-lib/chat.js
import $U from "./util.js"; import $H from './request.js'; import $store from '@/store/index.js'; class chat { constructor(arg) { this.url = arg.url this.isOnline = false this.socket = null this.reconnectConfirm = 0 this.isOpenReconnect = false // 获取当前用户相关信息 let user = $U.getStorage('user'); this.user = user ? JSON.parse(user) : {}, // 初始化聊天对象 this.TO = false; this.platform = uni.getSystemInfoSync().platform; // 创建背景音频管理器 this.bgAudioManager = uni.getBackgroundAudioManager(); bgAudioManager.title = '致爱丽丝'; bgAudioManager.singer = '暂无'; bgAudioManager.coverImgUrl = 'https://bjetxgzv.cdn.bspapp.com/VKCEYUGU-uni-app-doc/7fbf26a0-4f4a-11eb-b680-7980c8a877b8.png'; bgAudioManager.src = 'https://bjetxgzv.cdn.bspapp.com/VKCEYUGU-hello-uniapp/2cc220e0-c27a-11ea-9dfb-6da8e309e0d8.mp3'; // 连接和监听 if (this.user.token) { this.connectSocket() } } // 断线重连 reconnect(){ if(this.isOnline){ return; } if(this.reconnectTime >= 3){ return this.reconnectConfirm(); } this.reconnectTime += 1; this.connectSocket(); } // 连接socket connectSocket() { //console.log(this.user); this.socket = uni.connectSocket({ url: this.url + '?token=' + this.user.token, complete: () => {} }) // 监听连接成功 this.socket.onOpen(() => this.onOpen()) // 监听接收信息 this.socket.onMessage((res) => this.onMessage(res)) // 监听断开 this.socket.onClose(() => this.onClose()) // 监听错误 this.socket.onError(() => this.onError()) } // 监听打开 onOpen() { // 用户状态上线 this.isOnline = true; //console.log('socket连接成功'); this.reconnectTime = 0; this.isOpenReconnect = true; // 获取用户离线消息 this.getMessage(); } // 获取离线消息 getMessage() { $H.post('/chat/getmessage'); } // 获取聊天记录 getChatDetail(key = false) { key = key ? key : `chatDetail_${this.user.id}_${this.TO.chat_type}_${this.TO.id}` return this.getStorage(key) } // 监听关闭 onClose() { // 用户下线 this.isOnline = false; this.socket = null; if(this.isOpenReconnect){ this.reconnect(); } //console.log('socket连接关闭'); } // 监听消息 onMessage(data) { //console.log('监听消息', data); let res = JSON.parse(data.data) // 错误 switch (res.msg) { case 'fail': return uni.showToast({ title: res.data, icon: 'none' }); break; case 'recall': // 撤回消息 this.handleOnRecall(res.data) break; case 'updateApplyList': // 新的好友申请 $store.dispatch('getApply'); break; case 'moment': // 朋友圈更新 this.handleMoment(res.data) break; default: // 处理消息 this.handleOnMessage(res.data) break; } } // 获取本地存储中的朋友圈动态通知 getNotice(){ let notice = $U.getStorage('moment_'+this.user.id); return notice ? JSON.parse(notice) : { avatar:'', user_id:0, num:0 } } // 处理朋友圈通知 async handleMoment(message){ let notice = this.getNotice(); switch(message.type){ case 'new': if(message.user_id !== this.user.id){ notice.avatar = message.avatar; notice.user_id = message.user_id; uni.showTabBarRedDot({ index:2 }) } break; default: if(message.user_id !== this.user.id){ notice.avatar = message.avatar notice.user_id = message.user_id notice.num += 1 } if(notice.num > 0){ uni.setTabBarBadge({ index:2, text:notice.num > 99 ? '99+' : notice.num.toString() }) }else{ uni.removeTabBarBadge({ index:2 }) } break; } uni.$emit('momentNotice',notice); $U.setStorage('moment_'+this.user.id,JSON.stringify(notice)); } // 读取朋友圈动态 async readMoments(){ let notice = { avatar:'', user_id:0, num:0 }; $U.setStorage('moment_'+this.user.id,JSON.stringify(notice)); uni.hideTabBarRedDot({ index:2 }) uni.removeTabBarBadge({ index:2 }) uni.$emit('momentNotice',notice); } // 监听撤回消息处理 async handleOnRecall(message) { // 通知聊天页撤回消息 uni.$emit('onMessage', { ...message, isremove: 1 }) // 修改聊天记录 let id = message.chat_type === 'group' ? message.to_id : message.from_id // key值:chatDetail_当前用户id_会话类型_接收人/群id let key = `chatDetail_${this.user.id}_${message.chat_type}_${id}` // 获取原来的聊天记录 let list = this.getChatDetail(key) // 根据k查找对应聊天记录 let index = list.findIndex(item => item.id === message.id) if (index === -1) return; list[index].isremove = 1 // 存储 this.setStorage(key, list) // 当前会话最后一条消息的显示 this.updateChatItem({ id, chat_type: message.chat_type }, (item) => { item.data = '对方撤回了一条消息' item.update_time = (new Date()).getTime() return item }) } // 处理消息 async handleOnMessage(message) { // 添加消息记录到本地存储中 let { data } = this.addChatDetail(message, false) // 更新会话列表 this.updateChatList(data, false) // 全局通知 uni.$emit('onMessage', data) // 消息提示 this.messageNotice() } // 监听连接错误 onError() { // 用户下线 this.isOnline = false; this.socket = null; //console.log('socket连接错误'); } // 消息提示 messageNotice(){ if(this.platform === 'android'){ uni.vibrateShort(); }else{ uni.vibrateLong(); } // 提示音 if(this.bgAudioManager.src){ this.bgAudioManager.play(); }else{ // 路径 this.bgAudioManager.src = '/static/notice.mp3'; } } // 关闭连接 close() { if(this.socket){ this.socket.close() } this.isOpenReconnect = false } // 创建聊天对象 createChatObject(detail) { this.TO = detail; //console.log('创建聊天对象', this.TO) } // 销毁聊天对象 destoryChatObject() { this.TO = false } // 组织发送信息格式 formatSendData(params) { return { id: 0, // 唯一id,后端生成,用于撤回指定消息 from_avatar: this.user.avatar, // 发送者头像 from_name: this.user.nickname || this.user.username, // 发送者昵称 from_id: this.user.id, // 发送者id to_id: params.to_id || this.TO.id, // 接收人/群 id to_name: params.to_name || this.TO.name, // 接收人/群 名称 to_avatar: params.to_avatar || this.TO.avatar, // 接收人/群 头像 chat_type: params.chat_type || this.TO.chat_type, // 接收类型 type: params.type, // 消息类型 data: params.data, // 消息内容 options: params.options ? params.options : {}, // 其他参数 create_time: (new Date()).getTime(), // 创建时间 isremove: 0, // 是否撤回 sendStatus: params.sendStatus ? params.sendStatus : "pending" // 发送状态,success发送成功,fail发送失败,pending发送中 } } // 发送信息 send(message, onProgress = false) { return new Promise(async (result, reject) => { // 添加消息历史记录 // this.addChatDetail(); let { k } = this.addChatDetail(message); // 更新会话列表 this.updateChatList(message); // 验证是否上线 if (!this.checkOnLine()) return reject('未上线'); // 上传文件 let isUpload = (message.type !== 'text' && message.type !== 'emoticon' && message.type !== 'card' && !message.data.startsWith('http://akyan.oss-cn-beijing.aliyuncs.com/')) let uploadResult = '' if (isUpload) { uploadResult = await $H.upload('/upload', { filePath: message.data }, onProgress); if(!uploadResult){ message.sendStatus = 'fail'; // 更新指定历史记录 this.updateChatDetail(message, k); // 断线重连提示 return result(err); } } // 提交到后端 let data = isUpload ? uploadResult : message.data; $H.post('/chat/send', { to_id: message.to_id || this.TO.id, type:message.type, chat_type:message.chat_type || this.TO.chat_type, data, options: JSON.stringify(message.options) }).then(res => { // 发送成功 //console.log('chat.js发送成功'); message.id = res.id message.sendStatus = 'success'; if (message.type === 'video') { message.data = res.data; message.options = res.options; } // 更新指定历史记录 //console.log('更新指定历史记录',message); this.updateChatDetail(message, k); result(res); }).catch(err => { // 发送失败 //console.log('chat.js发送失败'); message.sendStatus = 'fail'; // 更新指定历史记录 this.updateChatDetail(message, k); // 断线重连提示 result(err); }); }) } // 验证是否上线 checkOnLine() { if (!this.isOnline) { // 断线重连提示 this.reconnectConfirm(); return false; } return true; } // 断线重连提示 reconnectConfirm() { uni.showModal({ title: '你已经断线,是否重新连接?', content: '重新连接', success: res => { if (res.confirm) { this.connectSocket(); } }, }); } // 添加聊天记录 addChatDetail(message, isSend = true) { //console.log('添加到聊天记录'); // 获取对方id let id = message.chat_type === 'user' ? (isSend ? message.to_id : message.from_id) : message.to_id; if (!id) { return { data: {}, k: 0 } } // key值:chatDetail_当前用户id_会话类型_接收人/群id let key = `chatDetail_${this.user.id}_${message.chat_type}_${id}`; //console.log(key); // 获取原来的聊天记录 let list = this.getChatdetail(key) //console.log('获取原来的聊天记录', list); // 标识 message.k = 'k' + list.length list.push(message) // 加入存储 //console.log('加入存储', message); this.setStorage(key, list); // 返回 return { data: message, k: message.k } } // 删除指定聊天记录 async deleteChatDetailItem(message,isSend = true){ // 获取对方id let id = message.chat_type === 'user' ? (isSend ? message.to_id : message.from_id) : message.to_id; // key值:chatDetail_当前用户id_会话类型_接收人/群id let key = `chatDetail_${this.user.id}_${message.chat_type}_${id}`; // 获取原来的聊天记录 let list = this.getChatdetail(key); // 根据k查找对应聊天记录 let index = list.findIndex(item => item.k === message.k || item.id === message.id); if (index === -1) return; list.splice(index,1); // 存储 this.setStorage(key, list); } // 更新指定历史记录 async updateChatDetail(message, k, isSend = true) { // 获取对方id let id = message.chat_type === 'user' ? (isSend ? message.to_id : message.from_id) : message.to_id; // key值:chatDetail_当前用户id_会话类型_接收人/群id let key = `chatDetail_${this.user.id}_${message.chat_type}_${id}`; // 获取原来的聊天记录 let list = this.getChatdetail(key); // 根据k查找对应聊天记录 let index = list.findIndex(item => item.k === k); if (index === -1) return; list[index] = message; // 存储 this.setStorage(key, list); } // 获取聊天记录 getChatdetail(key = false) { key = key ? key : `chatDetail_${this.user.id}_${this.TO.chat_type}_${this.TO.id}`; return this.getStorage(key); } // 格式化会话最后一条消息显示 formatChatItemData(message, isSend) { let data = message.data.length > 18 ? message.data.slice(0, 17) + '...' : message.data; switch (message.type) { case 'emoticon': data = '[表情]' break; case 'image': data = '[图片]' break; case 'audio': data = '[语音]' break; case 'video': data = '[视频]' break; case 'card': data = '[名片]' break; } data = isSend ? data : `${message.from_name}: ${data}` return data } // 更新会话列表 updateChatList(message, isSend = true) { // 获取本地存储会话列表 let list = this.getChatList() // 是否处于当前聊天中 let isCurrentChat = false // 接收人/群 id/头像/昵称 let id = 0 let avatar = '' let name = '' // 判断私聊还是群聊 if (message.chat_type === 'user') { // 私聊 // 聊天对象是否存在 isCurrentChat = this.TO ? (isSend ? this.TO.id === message.to_id : this.TO.id === message.from_id) : false id = isSend ? message.to_id : message.from_id avatar = isSend ? message.to_avatar : message.from_avatar name = isSend ? message.to_name : message.from_name } else { // 群聊 isCurrentChat = this.TO && (this.TO.id === message.to_id) id = message.to_id avatar = message.to_avatar name = message.to_name } // 会话是否存在 let index = list.findIndex(item => { return item.chat_type === message.chat_type && item.id === id }) // 最后一条消息展现形式 // let data = isSend ? message.data : `${message.from_name}: ${message.data}` let data = this.formatChatItemData(message, isSend) // 会话不存在,创建会话 // 未读数是否 + 1 let noreadnum = (isSend || isCurrentChat) ? 0 : 1 if (index === -1) { let chatItem = { id, // 接收人/群 id chat_type: message.chat_type, // 接收类型 user单聊 group群聊 avatar, // 接收人/群 头像 name, // 接收人/群 昵称 update_time: (new Date()).getTime(), // 最后一条消息的时间戳 data, // 最后一条消息内容 type: message.type, // 最后一条消息类型 noreadnum, // 未读数 istop: false, // 是否置顶 shownickname: false, // 是否显示昵称 nowarn: false, // 消息免打扰 strongwarn: false, // 是否开启强提醒 } // 群聊 if (message.chat_type === 'group' && message.group) { chatItem.shownickname = true chatItem.name = message.to_name chatItem = { ...chatItem, user_id: message.group.user_id, // 群管理员id remark: "", // 群公告 invite_confirm: 1, // 邀请确认 } } list.unshift(chatItem) } else { // 存在,更新会话 // 拿到当前会话 let item = list[index] // 更新该会话最后一条消息时间,内容,类型 item.update_time = (new Date()).getTime() item.name = message.to_name item.data = data item.type = message.type // 未读数更新 item.noreadnum += noreadnum // 置顶会话 list = this.listToFirst(list, index) } // 存储 let key = `chatlist_${this.user.id}` this.setStorage(key, list) // 更新未读数 this.updateBadge(list) // 通知更新vuex中的聊天会话列表 uni.$emit('onUpdateChatList', list) return list } // 获取聊天记录 getChatList() { let key = `chatlist_${this.user.id}` return this.getStorage(key) } getChatList_old(message, isSend = true) { // 获取本地存储会话列表 let list = this.getChatList(); // 是否处在当前聊天中 let isCurrentChat = false // 接收人/群 id/头像/昵称 let id = 0; let avatar = ''; let name = ''; // 判断私聊还是群聊 if (message.chat_type === 'user') { // 私聊 isCurrentChat = this.TO ? (isSend ? this.TO.id === message.to_id : message.from_id) : false; id = isSend ? message.to_id : message.from_id; avatar = isSend ? message.to_avatar : message.from_avatar name = isSend ? message.to_name : message.from_name } else { // 群聊 } // 会话是否存在 let index = list.findIndex(item => { return item.chat_type === message.chat_type && item.id === id; }) // 最后一条消息展现形式 let data = isSend ? message.data : `${message.from_name}:${message.data}`; // 未读数是否 +1 let noreadnum = (isSend || isCurrentChat) ? 0 : 1; // 会话不存在 创建会话 if (index === -1) { let chatItem = { id, // 接收人/群 id chat_type: message.chat_type, // 接收类型 user 单聊 group群聊 name, // 接收人/群 昵称 avatar, // 接收人/群 头像 update_time: (new Date()).getTime(), // 最后发送的时间 data, // 最后一条消息的内容 type: message.type, noreadnum: 1, // 未读数 istop: false, // 是否置顶 shownickname: false, // 是否显示昵称 nowarn: false, // 是否免打扰 strongwarn: false, // 是否强提醒 } if (message.chat_type === 'group') { chatItem = { ...chatItem, user_id: 0, // 管理员id remark: '', // 群公告 invite_confirm: 0 // 邀请确认 } } list.unshift(chatItem) } else { // 存在,更新会话 // 拿到当前会话 let item = list[index] // 更新改会话最后一条消息时间,内容,类型 item.update_time = (new Date()).getTime(); item.data = data; item.type = message.type; // 未读数更新 item.noreadnum += noreadnum // 置顶会话 list = this.listToFirst(list, index); } // 存储 let key = `chatlist_${this.user.id}`; this.setStorage(key, list); // 更新未读数 this.updateBadge(list); // 更新vuex中的聊天会话列表 uni.$emit('onUpdateChatList', list); //console.log('获取到的会话列表:', list) return list; /** * { id:1, // 接收人/群 id chat_type:'user', // 接收类型 user 单聊 group群聊 name:'昵称', // 接收人/群 昵称 avatar:"/static/images/demo/demo6.jpg", // 接收人/群 头像 type:'',// 最后一条消息类型 update_time:1628069958, // 最后发送的时间 data:"你好啊,哈哈哈", // 最后一条消息的内容 noreadnum:1, // 未读数 istop:false, // 是否置顶 shownickname:0, // 是否显示昵称 nowarn:0, // 是否免打扰 strongwarn:0, // 是否强提醒 user_id://管理员id, remark:'公告', // 群公告 invite_confirm:0, // 邀请确认 }, **/ } // 获取本地存储会话列表 getChatList() { let key = `chatlist_${this.user.id}`; return this.getStorage(key); } // 更新指定会话 async updateChatItem(where, data) { // 获取所有会话列表 let list = this.getChatList(); // 找到当前会话 let index = list.findIndex(item => item.id === where.id && item.chat_type === where.chat_type); if (index === -1) return; // 更新数据 if(typeof data === 'function'){ list[index] = data(list[index]) }else{ list[index] = data } let key = `chatlist_${this.user.id}`; this.setStorage(key, list); // 更新会话列表状态 uni.$emit('onUpdateChatList', list); } // 读取指定会话 async readChatItem(id, chat_type) { // 获取所有会话列表 let list = this.getChatList(); // 找到当前会话 let index = list.findIndex(item => item.id === id && item.chat_type === chat_type); if (index !== -1) { list[index].noreadnum = 0; let key = `chatlist_${this.user.id}`; this.setStorage(key, list); // 重新获取未读数 this.updateBadge(); // 更新会话列表状态 uni.$emit('onUpdateChatList', list); } } // 删除指定会话 async removeChatItem(id, chat_type) { // 获取所有会话列表 let list = this.getChatList(); // 找到当前会话 let index = list.findIndex(item => item.id === id && item.chat_type === chat_type); if (index !== -1) { list.splice(index, 1); let key = `chatlist_${this.user.id}`; this.setStorage(key, list); // 重新获取未读数 this.updateBadge(); // 更新会话列表状态 uni.$emit('onUpdateChatList', list); } } // 清空聊天记录 async clearChatDetail(id, chat_type) { let key = `chatDetail_${this.user.id}_${chat_type}_${id}`; $U.removeStorage(key); // 获取所有会话列表 let list = this.getChatList(); // 找到当前会话 let index = list.findIndex(item => item.id === id && item.chat_type === chat_type); if (index !== -1) { list[index].data = ''; let key = `chatlist_${this.user.id}`; this.setStorage(key, list); // 更新会话列表状态 uni.$emit('onUpdateChatList', list); } } /** { id:1, // 接收人/群 id chat_type:'user', // 接收类型 user单聊 group群聊 avatar:'', // 接收人/群 头像 name:'昵称', // 接收人/群 昵称 update_time:(new Date()).getTime(), // 最后一条消息的时间戳 data:"最后一条消息内容", // 最后一条消息内容 type:'text', // 最后一条消息类型 noreadnum:0, // 未读数 istop:false, // 是否置顶 shownickname:0, // 是否显示昵称 nowarn:0, // 消息免打扰 strongwarn:0, // 是否开启强提醒 user_id:0, // 群管理员id remark:"公告", // 群公告 invite_confirm:0, // 邀请确认 } * **/ // 初始化会话 initChatListItem(message) { // 获取本地存储会话列表 let list = this.getChatList() // 会话是否存在 let index = list.findIndex(item => { return item.chat_type === message.chat_type && item.id === message.to_id }) // 最后一条消息展现形式 let data = this.formatChatItemData(message, true) // 会话不存在,创建会话 if (index === -1) { let chatItem = { id: message.to_id, // 接收人/群 id chat_type: message.chat_type, // 接收类型 user单聊 group群聊 avatar: message.to_avatar, // 接收人/群 头像 name: message.to_name, // 接收人/群 昵称 update_time: (new Date()).getTime(), // 最后一条消息的时间戳 data: message.data, // 最后一条消息内容 type: 'system', // 最后一条消息类型 noreadnum: 0, // 未读数 istop: false, // 是否置顶 shownickname: false, // 是否显示昵称 nowarn: false, // 消息免打扰 strongwarn: false, // 是否开启强提醒 } // 群聊 if (message.chat_type === 'group' && message.group) { chatItem = { ...chatItem, user_id: message.group.user_id, // 群管理员id remark: '', // 群公告 invite_confirm: message.group.invite_confirm, // 邀请确认 } } list.unshift(chatItem) // 存储 let key = `chatlist_${this.user.id}` this.setStorage(key, list) // 通知更新vuex中的聊天会话列表 uni.$emit('onUpdateChatList', list) } } // 获取指定会话 getChatListItem(id, chat_type) { // 获取所有会话列表 let list = this.getChatList() // 找到当前会话 let index = list.findIndex(item => item.id === id && item.chat_type === chat_type) if (index !== -1) { return list[index] } return false } // 更新未读数 async updateBadge(list = false) { // 获取所有会话列表 list = list ? list : this.getChatList() // 统计所有未读数 let total = 0 list.forEach(item => { total += item.noreadnum }) // 设置底部导航栏角标 if (total > 0) { uni.setTabBarBadge({ index: 0, text: total <= 99 ? total.toString() : '99+' }) } else { uni.removeTabBarBadge({ index: 0 }) } uni.$emit('totalNoreadnum', total) } // 获取存储 getStorage(key) { let list = $U.getStorage(key); return list ? JSON.parse(list) : []; } // 设置存储 setStorage(key, value) { return $U.setStorage(key, JSON.stringify(value)); } // 数组置顶 listToFirst(arr, index) { if (index != 0) { arr.unshift(arr.splice(index, 1)[0]); } return arr; } // 撤回消息 recall(message) { return new Promise((result, reject) => { $H.post('/chat/recall', { to_id: message.to_id, chat_type: message.chat_type, id: message.id, }).then(res => { // key值:chatDetail_当前用户id_会话类型_接收人/群id let key = `chatDetail_${this.user.id}_${message.chat_type}_${message.to_id}` // 获取原来的聊天记录 let list = this.getChatDetail(key) // 根据k查找对应聊天记录 let index = list.findIndex(item => item.id === message.id) if (index === -1) return; list[index].isremove = 1 // 存储 this.setStorage(key, list) result(res) // 更新会话最后一条消息显示 this.updateChatItem({ id: message.to_id, chat_type: message.chat_type }, (item) => { item.data = '你撤回了一条消息' item.update_time = (new Date()).getTime() return item }) }).catch(err => { reject(err) }) }) } } export default chat
/pages/mail/user-base/user-base.nvue
<template> <view class="page"> <!-- 导航栏 --> <free-nav-bar showBack :showRight="detail.friend" bgColor="bg-white"> <view slot="right"> <free-icon-button v-if="detail.friend"><text class="iconfont font-md" @click="openAction"></text></free-icon-button> </view> </free-nav-bar> <view class="px-3 py-4 flex align-center bg-white border-bottom"> <free-avatar :src="detail.avatar" size="120"></free-avatar> <view class="flex flex-column ml-3 flex-1"> <view class="font-lg font-weight-bold flex justify-between"> <text class="font-lg font-weight-bold mb-1">{{detail.nickname}}</text> <image v-if="detail.star" src="/static/images/star.png" style="width: 40rpx;height: 40rpx;"></image> </view> <text class="font-md text-light-muted mb-1">账号:{{detail.username}}</text> <!-- <text class="font-md text-light-muted">地区:广东广州</text> --> </view> </view> <free-list-item v-if="detail.friend" showRight :showLeftIcon="false" @click="navigate(tagPath)"> <view class="flex align-center"> <text class="font-md text-dark mr-3">标签</text> <text class="font-md text-light-muted mr-2" v-for="(item,index) in detail.tags" :key="index">{{item}}</text> </view> </free-list-item> <free-divider></free-divider> <free-list-item v-if="detail.friend" showRight :showLeftIcon="false"> <view class="flex align-center"> <text class="font-md text-dark mr-3">朋友圈</text> <image src="/static/images/demo/cate_01.png" style="width: 90rpx; height: 90rpx;" class=" mr-2"></image> <image src="/static/images/demo/cate_01.png" style="width: 90rpx; height: 90rpx;" class=" mr-2"></image> <image src="/static/images/demo/cate_01.png" style="width: 90rpx; height: 90rpx;" class=" mr-2"></image> </view> </free-list-item> <free-list-item title="更多信息" showRight :showLeftIcon="false"></free-list-item> <free-divider></free-divider> <view v-if="detail.friend" class="py-3 flex align-center justify-center bg-white" hover-class="bg-light" @click="doEvent"> <text class="iconfont text-primary mr-1" v-if="!detail.isBlack"></text> <text class="font-md text-primary">{{detail.isblack ? '移除黑名单' : '发信息'}}</text> </view> <view v-else class="py-3 flex align-center justify-center bg-white" hover-class="bg-light" @click="navigate(addFriend())"> <text class="font-md text-primary">添加好友</text> </view> <!-- 扩展菜单 --> <free-popup ref="action" bottom transformOrigin="center bottom" maskColor> <scroll-view style="height: 580rpx;" scroll-y="true" class="bg-white" :show-scrollbar="false"> <free-list-item v-for="(item,index) in actions" :key="index" :title="item.title" :showRight="false" :border="false" @click="popupEvent(item)"> <text slot="icon" class="iconfont font-lg py-1">{{item.icon}}</text> </free-list-item> </scroll-view> </free-popup> </view> </template> <script> import freeNavBar from '@/components/free-ui/free-nav-bar.vue'; import freeIconButton from '@/components/free-ui/free-icon-button.vue'; import freeChatItem from '@/components/free-ui/free-chat-item.vue'; import freePopup from '@/components/free-ui/free-popup.vue'; import freeListItem from '@/components/free-ui/free-list-item.vue'; import freeDivider from '@/components/free-ui/free-divider.vue'; import freeAvatar from '@/components/free-ui/free-avatar.vue'; import auth from '@/common/mixin/auth.js'; import $H from '@/common/free-lib/request.js'; export default { mixins: [auth], components: { freeNavBar, freeIconButton, freeChatItem, freePopup, freeListItem, freeDivider, freeAvatar }, data() { return { detail: { id: 0, username: '', nickname: '', avatar: '', sex: '', sign: '', area: '', friend: false, lookhim: 1, lookme: 1, star: 0, isblack: 0, tags: [] }, } }, onShow() { this.getData(); }, onLoad(e) { uni.$on('saveRemarkTag', (e) => { this.detail.tagList = e.detail.tagList this.nickname = e.nickname; }) if (!e.user_id) { return this.backToast(); } this.detail.id = e.user_id; // 获取当前用户资料 this.getData(); }, beforeDestroy() { this.$refs.action.hide(); uni.$off('saveRemarkTag') }, computed: { tagPath() { return "mail/user-remark-tag/user-remark-tag?params="+JSON.stringify({ user_id:this.detail.id, nickname:this.detail.nickname, tags:this.detail.tags ? this.detail.tags.join(',') : '' }) }, actions() { return [{ icon: "\ue6b3", title: "设置备注和标签", type: "navigate", path: "mail/user-remark-tag/user-remark-tag?params="+JSON.stringify({ user_id:this.detail.id, nickname:this.detail.nickname, tags:this.detail.tags ? this.detail.tags.join(',') : '' }) }, { icon: "\ue613", title: "把他推荐给朋友", type: "navigate", path: "chat/chat-list/chat-list?params="+encodeURIComponent(JSON.stringify({ type: "card", data: this.detail.nickname || this.detail.username, options: { avatar: this.detail.avatar, id: this.detail.id } })) }, { icon: "\ue6b0", title: this.detail.star ? '取消星标好友' : "设为星标朋友", type: "event", event: "setStar" }, { icon: "\ue667", title: "设置朋友圈和动态权限", type: "navigate", path: "mail/user-moments-auth/user-moments-auth?user_id="+this.detail.id+"¶ms="+JSON.stringify({ lookme:this.detail.lookme, lookhim:this.detail.lookhim, }) }, { icon: "\ue638", title: this.detail.isblack ? '移出黑名单' : "加入黑名单", type: "event", event: "setBlack" }, { icon: "\ue61c", title: "投诉", type: "navigate", path: "mail/user-report/user-report?params="+JSON.stringify({ user_id:this.detail.id, type:'user' }) }, { icon: "\ue638", title: "删除", type: "event", event: "deleteItem" }] } }, methods: { addFriend() { let obj = { friend_id: this.detail.id, nickname: this.detail.nickname, lookme: typeof this.detail.lookme === 'number' ? this.detail.lookme : 1, lookhim: typeof this.detail.lookhim === 'number' ? this.detail.lookhim : 1, }; return 'mail/add-friend/add-friend?params=' + JSON.stringify(obj); }, getData() { $H.get('/friend/read/' + this.detail.id).then(res => { if (!res) { return this.backToast('该用户不存在'); } this.detail = res; console.log(res); }); }, openAction() { this.$refs.action.show() }, navigate(url) { console.log(url) uni.navigateTo({ url: '/pages/' + url, }); }, // 操作菜单事件 popupEvent(e) { if (!e.type) { return; } setTimeout(() => { // 关闭弹出层 this.$refs.action.hide() }, 300) switch (e.type) { case 'navigate': this.navigate(e.path); break; case 'event': this[e.event](e); break; } }, // 删除好友 deleteItem(){ uni.showModal({ title: '是否要删除好友?', success: res => { if(res.confirm){ $H.post('/friend/destroy',{friend_id:this.detail.id}).then(res=>{ uni.showToast({ title:'删除好友成功', icon:'none' }); uni.reLaunch({ url:'/pages/tabbar/index/index' }) }) } }, fail: () => {}, complete: () => {} }); }, // 设为星标 setStar(e) { let star = this.detail.star == 0 ? 1 : 0; $H.post('/friend/setstar/' + this.detail.id, { star }).then(res => { this.detail.star = star; e.title = this.detail.star ? '取消标星好友' : '设为标星好友'; }); }, // 加入黑名单 setBlack(e) { let msg = this.detail.isblack ? '移出黑名单' : '加入黑名单'; uni.showModal({ content: '是否要' + msg, success: (res) => { if (res.confirm) { let isblack = this.detail.isblack == 0 ? 1:0 $H.post('/friend/setblack/' + this.detail.id, { isblack }).then(res => { this.detail.isblack = isblack; }); // this.detail.isBlack = !this.detail.isBlack; // e.title = this.isBlack ? '移出黑名单' : '加入黑名单'; uni.showToast({ title: msg + '成功', icon: 'none' }) } } }) }, // 发送消息 doEvent(e){ if(this.detail.isblack){ return this.setBlack(); } uni.navigateTo({ url:'../../chat/chat/chat?params='+encodeURIComponent(JSON.stringify({ id:this.detail.id, name:this.detail.nickname ? this.detail.nickname : this.detail.username, avatar:this.detail.avatar, chat_type:'user' })) }) } } } </script> <style> </style>
后端
app/controller/moment.js
'use strict'; const Controller = require('egg').Controller; class MomentController extends Controller { // 发布朋友圈 async create() { const { ctx, app } = this; let current_user_id = ctx.authUser.id; // 参数验证 ctx.validate({ content: { type: 'string', required: false, desc: '内容' }, image: { type: 'string', required: false, desc: '图片' }, video: { type: 'string', required: false, desc: '视频' }, type: { type: 'string', required: true, range: { in: ['content', 'image', 'video'] }, desc: '朋友圈类型' }, location: { type: 'string', required: false, desc: '位置' }, remind: { type: 'string', required: false, defValue: "", desc: '提醒谁看' }, see: { type: 'string', required: false, defValue: "all", desc: '谁可以看' } }); let { content, image, video, type, location, remind, see } = ctx.request.body; if (!ctx.request.body[type]) { return ctx.apiFail(`${type} 不能为空`); } let moment = await app.model.Moment.create({ content, image, video, location, remind, see, user_id: current_user_id }); if (!moment) { return ctx.apiFail('发布失败'); } // 推送到好友的时间轴 this.toTimeline(moment); ctx.apiSuccess('ok'); } // 推送到好友的时间轴 async toTimeline(moment) { const { ctx, app } = this; let current_user_id = ctx.authUser.id; // 获取当前用户所有好友 let friends = await app.model.Friend.findAll({ where: { user_id: current_user_id, isblack: 0 }, attributes: ['friend_id'] }); // 谁可以看 /** all 全部人可看 only:1,2,3 指定人可见 except:1,2,3 谁不可看 none 仅自己可见 */ let sees = moment.see.split(':'); let o = { only: [], except: [] } let oType = sees[0]; if ((sees[0] === 'only' || sees[0] === 'except') && sees[1]) { o[sees[0]] = (sees[1].split(',')).map(v => parseInt(v)); } let addData = friends.filter(item => { return oType === 'all' || (oType === 'only' && o.only.includes(item.friend_id)) || (oType === 'except' && !o.except.includes(item.friend_id)); }); addData = addData.map(item => { return { user_id: item.friend_id, moment_id: moment.id, own: 0 } }); addData.push({ user_id: current_user_id, moment_id: moment.id, own: 1 }); // 推送到时间轴当中 await app.model.MomentTimeline.bulkCreate(addData); // 消息推送 let message = { avatar: ctx.authUser.avatar, user_id: current_user_id, type: "new" } addData.forEach(item => { ctx.sendAndSaveMessage(item.user_id, message, 'moment'); }); // 提醒用户 if (moment.remind) { let arr = moment.remind.split(','); arr.forEach(user_id => { ctx.sendAndSaveMessage(user_id, { avatar: ctx.authUser.avatar, user_id: current_user_id, type: "remind" }, 'moment'); }); } } // 点赞 async like() { const { ctx, app } = this; let current_user_id = ctx.authUser.id; ctx.validate({ id: { type: "int", required: true, desc: "朋友圈id" } }); let { id } = ctx.request.body; let MomentTimeline = await app.model.MomentTimeline.findOne({ where: { user_id: current_user_id, moment_id: id }, include: [{ model: app.model.Moment, attributes: ['user_id'], include: [{ model: app.model.MomentLike, attributes: ['user_id'], }] }] }); if (!MomentTimeline) { return ctx.apiFail('朋友圈消息不存在'); } let like = await app.model.MomentLike.findOne({ where: { user_id: current_user_id, moment_id: id } }); let message = { avatar: ctx.authUser.avatar, user_id: current_user_id, type: "like" } if (like) { await like.destroy(); ctx.apiSuccess(MomentTimeline.moment.moment_likes); } else { await app.model.MomentLike.create({ user_id: current_user_id, moment_id: id }); ctx.apiSuccess(MomentTimeline.moment.moment_likes); } // 通知作者 if (MomentTimeline.moment.user_id && MomentTimeline.moment.user_id !== current_user_id) { ctx.sendAndSaveMessage(MomentTimeline.moment.user_id, message, 'moment'); } // 通知相关人 MomentTimeline.moment.moment_likes.forEach(item => { if (item.user_id !== current_user_id) { ctx.sendAndSaveMessage(item.user_id, message, 'moment'); } }); } // 朋友圈评论 async comment() { const { ctx, app } = this; let current_user_id = ctx.authUser.id; ctx.validate({ id: { type: "int", required: true, desc: "朋友圈id" }, content: { type: "string", required: true, desc: "评论内容" }, reply_id: { type: "int", required: true, defValue: 0, desc: "回复id" } }); let { id, content, reply_id } = ctx.request.body; let MomentTimeline = await app.model.MomentTimeline.findOne({ where: { user_id: current_user_id, moment_id: id }, include: [{ model: app.model.Moment, attributes: ['user_id'], include: [{ model: app.model.MomentLike, attributes: ['user_id'] }] }] }); if (!MomentTimeline) { return ctx.apiFail('朋友圈消息不存在'); } let comment = await app.model.MomentComment.create({ user_id: current_user_id, moment_id: id, content, reply_id }); ctx.apiSuccess(comment); let message = { avatar: ctx.authUser.avatar, user_id: current_user_id, type: "comment" } // 通知作者 if (MomentTimeline.moment.user_id && MomentTimeline.moment.user_id !== current_user_id) { ctx.sendAndSaveMessage(MomentTimeline.moment.user_id, message, 'moment'); } // 通知相关人 MomentTimeline.moment.moment_likes.forEach(item => { if (item.user_id !== current_user_id) { ctx.sendAndSaveMessage(item.user_id, message, 'moment'); } }); // 通知被回复人 if (reply_id > 0) { let index = MomentTimeline.moment.moment_likes.findIndex(item => { return item.user_id === reply_id }); if (index === -1) { ctx.sendAndSaveMessage(reply_id, message, 'moment'); } } } // 朋友圈列表 async timeline() { const { ctx, app } = this; let current_user_id = ctx.authUser.id; let page = ctx.params.page ? parseInt(ctx.params.page) : 1; let limit = ctx.query.limit ? parseInt(ctx.query.limit) : 10; let offset = (page - 1) * limit; let rows = await app.model.MomentTimeline.findAll({ where: { user_id: current_user_id }, include: [{ model: app.model.Moment, include: [{ model: app.model.User, attributes: ['id', 'nickname', 'username', 'avatar'] }, { model: app.model.MomentComment, attributes: { exclude: ['created_at', 'updated_at'] }, include: [{ model: app.model.User, as: "momentCommentUser", attributes: ['id', 'nickname', 'username'] }, { model: app.model.User, as: "momentCommentReply", attributes: ['id', 'nickname', 'username'] }] }, { model: app.model.MomentLike, attributes: ['user_id', 'moment_id'], include: [{ model: app.model.User, attributes: ['id', 'nickname', 'username'] }] }] }], offset, limit, order: [ ['id', 'DESC'] ] }); let friends = await app.model.Friend.findAll({ where: { user_id: current_user_id, lookhim: 1 }, attributes: ['friend_id'] }); console.log(friends); let bfriends = await app.model.Friend.findAll({ where: { friend_id: current_user_id, lookme: 1 }, attributes: ['user_id'] }); friends = friends.map(item => item.friend_id); bfriends = bfriends.map(item => item.user_id); friends = friends.filter(item => bfriends.includes(item)); let res = []; rows.forEach(item => { if (friends.includes(item.moment.user_id) || item.moment.user_id === current_user_id) { let comments = []; item.moment.moment_comments.forEach(v => { if (friends.includes(v.momentCommentUser.id) || v.momentCommentUser.id === current_user_id) { comments.push({ content: v.content, user: { id: v.momentCommentUser.id, name: v.momentCommentUser.nickname || v.momentCommentUser.username }, reply: v.momentCommentReply ? { id: v.momentCommentReply.id, name: v.momentCommentReply.nickname || v.momentCommentReply.username } : null }); } }); let likes = []; item.moment.moment_likes.forEach(v => { if (friends.includes(v.user.id) || v.user.id === current_user_id) { likes.push({ id: v.user.id, name: v.user.nickname || v.user.username }); } }); res.push({ id: item.id, user_id: item.moment.user_id, user_name: item.moment.user.nickname || item.moment.user.username, avatar: item.moment.user.avatar, moment_id: item.moment_id, content: item.moment.content, image: item.moment.image ? item.moment.image.split(',') : [], video: item.moment.video ? JSON.parse(item.moment.video) : null, location: item.moment.location, own: item.own, created_at: item.created_at, comments, likes }); } }); ctx.apiSuccess(res); } // 某个用户的朋友圈列表 async list() { const { ctx, app } = this; let current_user_id = ctx.authUser.id; let page = ctx.params.page ? parseInt(ctx.params.page) : 1; let limit = ctx.query.limit ? parseInt(ctx.query.limit) : 10; let offset = (page - 1) * limit; let user_id = ctx.query.user_id ? parseInt(ctx.query.user_id) : 0; // ctx.validate({ // user_id: { // type: "int", // required: false, // defValue: current_user_id, // desc: "用户id" // } // }); let lookIds = []; if (!user_id) { // 本人 user_id = current_user_id; lookIds = false; } else { // 验证我是否具备权限 是否是朋友,是否拉黑,是否删除 let f = await app.model.User.findOne({ where: { id: user_id, status: 1 }, attributes: ['id', 'nickname', 'username', 'avatar'], include: [{ model: app.model.Friend, as: "bfriends", where: { user_id: current_user_id }, attributes: ['lookhim', 'isblack'] }, { model: app.model.Friend, as: "friends", where: { friend_id: current_user_id }, attributes: ['lookme', 'isblack'] }] }); // 用户是否存在 if (!f) { return ctx.apiFail('用户不存在或已被禁用'); } // 是否是好友关系 if (!f.bfriends.length || !f.friends.length) { return ctx.apiSuccess([]); } // 不可见 if (f.bfriends[0].isblack || f.friends[0].isblack || !f.bfriends[0].lookhim || !f.friends[0].lookme) { return ctx.apiSuccess([]); } // 获取当前用户所有好友(查找共同好友) let friends = await app.model.Friend.findAll({ where: { user_id: current_user_id, isblack: 0 }, attributes: ['friend_id'] }); lookIds = friends.map(item => item.friend_id); } let rows = await app.model.Moment.findAll({ where: { user_id }, include: [{ model: app.model.User, attributes: ['id', 'nickname', 'username', 'avatar'] }, { model: app.model.MomentComment, attributes: { exclude: ['created_at', 'updated_at'] }, include: [{ model: app.model.User, as: "momentCommentUser", attributes: ['id', 'nickname', 'username'] }, { model: app.model.User, as: "momentCommentReply", attributes: ['id', 'nickname', 'username'] }] }, { model: app.model.MomentLike, attributes: ['user_id', 'moment_id'], include: [{ model: app.model.User, attributes: ['id', 'nickname', 'username'] }] }], offset, limit, order: [ ['id', 'DESC'] ] }); let res = []; rows.forEach(item => { let comments = []; item.moment_comments.forEach(v => { if (!lookIds || lookIds.includes(v.momentCommentUser.id) || v.momentCommentUser.id === current_user_id) { comments.push({ content: v.content, user: { id: v.momentCommentUser.id, name: v.momentCommentUser.nickname || v.momentCommentUser.username }, reply: v.momentCommentReply ? { id: v.momentCommentReply.id, name: v.momentCommentReply.nickname || v.momentCommentReply.username } : null }) } }); let likes = []; item.moment_likes.forEach(v => { if (!lookIds || lookIds.includes(v.user.id) || v.user.id === current_user_id) { likes.push({ id: v.user.id, name: v.user.nickname || v.user.username }); } }); res.push({ user_id: item.user_id, user_name: item.user.nickname || item.user.username, avatar: item.user.avatar, moment_id: item.id, content: item.content, image: item.image ? item.image.split(',') : [], video: item.video ? JSON.parse(item.video) : null, location: item.location, own: 1, created_at: item.created_at, comments, likes }); }); ctx.apiSuccess(res); } } module.exports = MomentController;
app/model/user.js
// app/model/user.js 'use strict'; const crypto = require('crypto'); module.exports = app => { const { STRING, INTEGER, DATE, ENUM, TEXT } = app.Sequelize; // 配置(重要:一定要配置详细,一定要!!!) const User = app.model.define('user', { id: { type: INTEGER(20).UNSIGNED, primaryKey: true, autoIncrement: true }, username: { type: STRING(30), allowNull: false, defaultValue: '', comment: '用户名称', unique: true }, nickname: { type: STRING(30), allowNull: false, defaultValue: '', comment: '昵称', }, email: { type: STRING(160), comment: '用户邮箱', unique: true }, password: { type: STRING(200), allowNull: false, defaultValue: '', set(val){ const hmac = crypto.createHash("sha256", app.config.crypto.secret); hmac.update(val); let hash = hmac.digest("hex"); this.setDataValue('password',hash); } }, avatar: { type: STRING(200), allowNull: true, defaultValue: '' }, phone: { type: STRING(20), comment: '用户手机', unique: true }, sex: { type: ENUM, values: ['男', '女', '保密'], allowNull: true, defaultValue: '男', comment: '用户性别' }, status: { type: INTEGER(1), allowNull: false, defaultValue: 1, comment: '状态 0禁用 1启用' }, sign: { type: STRING(200), allowNull: true, defaultValue: '', comment: '个性签名' }, area: { type: STRING(200), allowNull: true, defaultValue: '', comment: '地区' }, created_at: DATE, updated_at: DATE }); // 定义关联关系 User.associate = function(model){ User.hasMany(app.model.Friend,{ as:"bfriends", // 当前用户的被好友 foreignKey:'friend_id' }); User.hasMany(app.model.Friend,{ as:"friends", // 当前用户的好友 foreignKey:'user_id' }); }; return User; };
感谢大家观看,我们下次看