下图是我测试的截图
/pages/find/add-moment/add-moment.vue
<template> <view class="px-3"> <!-- 导航栏 --> <free-nav-bar showBack :showRight="true"> <free-main-button name="发表" slot="right" @click="submit"></free-main-button> </free-nav-bar> <!-- 文字 --> <textarea value="" placeholder="这一刻的想法" v-model="content" class="font-md mb-3" /> <!-- 图文 --> <free-upload-image :data="imageList" v-if="type==='image'" @update='uploadImage'></free-upload-image> <!-- 视频 --> <block v-if="type==='video'"> <view v-if="!video" class="flex align-center justify-center bg-light rounded" style="height: 350rpx;" hover-class="bg-hover-light" @click="uploadVideo"> <text class="text-muted" style="font-size:100rpx;">+</text> </view> <video v-if="type === 'video' && video && video.src" :src="video.src" :poster="video.poster" controls></video> <view v-if="type === 'video' && video && video.src" class="my-3 flex align-center justify-center bg-light" hover-class="bg-hover-light" style="height:100rpx;" @click="uploadVideo"> <text class="font-md text-muted">点击切换视频</text> </view> </block> <free-list-item title="所在位置" showRight :showLeftIcon="false"> <text slot="right" class="font-md">位置</text> </free-list-item> <free-list-item title="提醒谁看" showRight :showLeftIcon="false" @click="openRemind"> <view slot="right" class="flex"> <view class="ml-1" v-for="(item,index) in remindList" :key="index"> <free-avatar :src="item.avatar" size="50"> </free-avatar> </view> </view> </free-list-item> <free-list-item title="谁可以看" showRight :showLeftIcon="false" @click="openSee"> <text slot="right" class="font-md">{{seeText}}</text> </free-list-item> </view> </template> <script> import freeNavBar from '@/components/free-ui/free-nav-bar.vue'; import freeMainButton from '@/components/free-ui/free-main-button.vue'; import freeListItem from "@/components/free-ui/free-list-item.vue"; import freeUploadImage from '@/components/free-ui/free-upload-image.vue'; import freeAvatar from '@/components/free-ui/free-avatar.vue'; import $H from '@/common/free-lib/request.js' export default { components: { freeNavBar, freeMainButton, freeListItem, freeUploadImage, freeAvatar }, data() { return { content: '', type: 'image', imageList: [], video: false, remindList: [], names:'', seeObj:{ k:'all', v:[] } } }, onLoad(e) { this.type = e.type; uni.$on('sendResult', this.sendResult); }, destroyed() { uni.$off('sendResult', this.sendResult); }, computed:{ seeData(){ if(this.seeObj.k === 'all' || this.seeObj.k === 'none'){ return this.seeObj.k; } let ids = (this.seeObj.v.map(item=> item.user_id)).join(','); return `${this.seeObj.k}:${ids}`; }, seeText(){ let type = { all:'公开', none:'私密', only:'谁可以看', except:'不给谁看' } if(this.seeObj.k === 'all' || this.seeObj.k === 'none'){ return type[this.seeObj.k]; } let names = (this.seeObj.v.map(item=> item.name)).join(','); return `${type[this.seeObj.k]}:${names}`; } }, methods: { sendResult(e) { if (e.type === 'remind') { this.remindList = e.data } if(e.type === 'see'){ this.seeObj = e.data } }, openRemind() { uni.navigateTo({ url: '../../mail/mail/mail?type=remind' }) }, openSee() { uni.navigateTo({ url: '../../mail/mail/mail?type=see' }) }, // 发布 submit() { $H.post('/moment/create', { content: this.content, image: this.imageList.join(','), video: this.video ? JSON.stringify(this.video) : '', type: this.type, location: '', remind: (this.remindList.map(item=>item.user_id)).join(','), see: this.seeData }).then(res => { uni.showToast({ title: '发布成功', icon: 'none' }); uni.navigateBack({ delta: 1 }) }) }, // 上传图片 uploadImage(list) { this.imageList = list; }, // 上传视频 uploadVideo() { uni.chooseVideo({ maxDuration: 10, success: (e) => { // this.video = e.tempFilePath; $H.upload('/upload', { filePath: e.tempFilePath }, (progress) => { console.log('上传进度', progress); }).then(url => { this.video = { src: url, poster: url + '?x-oss-process=video/snapshot,t_10,m_fast,w_300,f_png' } }) } }) } } } </script> <style> </style>
/WChatH5/pages/mail/mail/mail.vue
<template> <view> <!-- 导航栏 --> <free-nav-bar title="选择" showBack :showRight="true"> <free-main-button :name="buttonText" slot="right" @click="submit"></free-main-button> </free-nav-bar> <!-- 通讯录列表 --> <scroll-view scroll-y="true" :style="'height:'+scrollHeight+'px;'" :scroll-into-view="scrollInto"> <template v-if="type === 'see'"> <free-list-item v-for="(item,index) in typeList" :key="item.key" :title="item.name" :showRightIcon="false" showRight @click="typeIndex = index"> <view slot="right" style="width: 40rpx;height: 40rpx;" class="border rounded-circle flex align-center justify-center mr-4"> <view v-if="typeIndex === index" style="width: 30rpx;height: 30rpx;" class="main-bg-color rounded-circle"></view> </view> </free-list-item> </template> <template v-if="type !== 'see' || (type === 'see' && (typeIndex === 1 || typeIndex === 2)) "> <view v-for="(item,index) in list" :key="index" :id="'item-'+item.title"> <view v-if="item.list.length" class="py-2 px-3 border-bottom bg-light"> <text class="font-md text-dark">{{item.title}}</text> </view> <free-list-item v-for="(item2,index2) in item.list" :key="index2" :title="item2.name" :cover="item2.avatar || '/static/images/userpic.png'" :showRightIcon="false" showRight @click="selectItem(item2)"> <view slot="right" style="width: 40rpx;height: 40rpx;" class="border rounded-circle flex align-center justify-center mr-4"> <view v-if="item2.checked" style="width: 30rpx;height: 30rpx;" class="main-bg-color rounded-circle"></view> </view> </free-list-item> </view> </template> </scroll-view> <!-- 侧边导航条 --> <view class="position-fixed right-0 bottom-0 bg-light flex flex-column" :style="'top:'+top+'px;'" style="width: 50rpx;" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend"> <view class="flex-1 flex align-center justify-center" v-for="(item,index) in list" :key="index"> <text class="font-sm text-muted">{{item.title}}</text> </view> </view> <view class="position-fixed rounded-circle bg-light border flex align-center justify-center" v-if="current" style="width: 150rpx;height: 150rpx;left: 300rpx;" :style="'top:'+modalTop+'px;'"> <text class="font-lg">{{current}}</text> </view> </view> </template> <script> import freeNavBar from "@/components/free-ui/free-nav-bar.vue" import freeListItem from "@/components/free-ui/free-list-item.vue" import freeMainButton from '@/components/free-ui/free-main-button.vue'; import { mapState } from 'vuex' import $H from '@/common/free-lib/request.js'; export default { components: { freeNavBar, freeListItem, freeMainButton }, data() { return { typeIndex:0, typeList:[{ name:"公开", key:"all" },{ name:"谁可以看", key:"only" },{ name:"不给谁看", key:"except" },{ name:"私密", key:"none" }], top:0, scrollHeight:0, scrollInto:'', current:'', selectList:[], type:"", limit:9, id:0 } }, onLoad(e) { let res = uni.getSystemInfoSync() this.top = res.statusBarHeight + uni.upx2px(90) this.scrollHeight = res.windowHeight - this.top if(e.type){ this.type = e.type } if(e.limit){ this.limit = parseInt(e.limit) } if(e.id){ this.id = e.id if(e.type === 'inviteGroup'){ this.limit = 1 } } this.$store.dispatch('getMailList') }, computed: { ...mapState({ list:state=>state.user.mailList }), buttonText(){ let text = '发送' if(this.type === 'createGroup'){ text = '创建群组' } return text + ' ('+this.selectCount+')' }, modalTop(){ return (this.scrollHeight - uni.upx2px(150)) / 2 }, // 每个索引的高度 itemHeight() { let count = this.list.length if(count < 1){ return 0 } return this.scrollHeight / count }, // 选中数量 selectCount(){ return this.selectList.length } }, methods: { touchstart(e){ this.changeScrollInto(e) }, touchmove(e){ this.changeScrollInto(e) }, touchend(e){ this.current = '' }, // 联动 changeScrollInto(e){ let Y = e.touches[0].pageY // #ifdef MP Y = Y - this.top // #endif let index = Math.floor(Y / this.itemHeight) let item = this.list[index] if(item){ this.scrollInto = 'item-'+item.title this.current = item.title } }, // 选中/取消选中 selectItem(item){ if(!item.checked && this.selectCount === this.limit){ // 选中|限制选中数量 return uni.showToast({ title: '最多选中 '+this.limit+' 个', icon: 'none' }); } item.checked = !item.checked if(item.checked){ // 选中 this.selectList.push(item) } else { // 取消选中 let index = this.selectList.findIndex(v=> v === item) if(index > -1){ this.selectList.splice(index,1) } } }, submit(){ if(this.type !== 'see' && this.selectCount === 0){ return uni.showToast({ title: '请先选择', icon: 'none' }); } switch (this.type){ case 'createGroup': // 创建群组 $H.post('/group/create',{ ids:this.selectList.map(item=>item.user_id) }).then(res=>{ // 见鬼这里跳转不行啊 uni.navigateTo({ url:'../../tabbar/index/index' }); uni.showToast({ title: '创建群聊成功', duration: 200 }); }) break; case 'sendCard': let item = this.selectList[0] uni.$emit('sendItem',{ sendType:"card", data:item.name, type:"card", options:{ avatar:item.avatar, id:item.user_id } }) uni.navigateBack({ delta: 1 }); break; case 'remind': uni.$emit('sendResult',{ type:"remind", data:this.selectList }) uni.navigateBack({ delta: 1 }); break; case 'see': let k = this.typeList[this.typeIndex].key if(k !== 'all' && k!== 'none' && !this.selectCount){ return uni.showToast({ title: '请先选择', icon: 'none' }); } uni.$emit('sendResult',{ type:"see", data:{ k, v:this.selectList } }) uni.navigateBack({ delta: 1 }); break; case 'inviteGroup': console.log(this.selectList); $H.post('/group/invite',{ id:this.id, user_id:this.selectList[0].user_id }).then(res=>{ uni.showToast({ title: '邀请成功', icon: 'none' }); uni.navigateBack({ delta: 1 }); }) break; } } } } </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;
感谢大家观看,我们下次见