下图是我测试的截图
/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;
感谢大家观看,我们下次见