微信朋友圈-高仿
程序员文章站
2022-05-13 08:15:34
...
1.项目介绍
使用Vue进行开发,运用到了vuex、页面结构使用了weui框架,实现的效果跟温馨朋友圈基本相似,发表朋友圈、私信聊天、下拉刷新等等
2.项目代码
2.1登录注册
手机号用正则表达式进行了手机合法校验,然后进行配对验证码,验证码每六十秒才能点击一次。
2.2 发表朋友圈功能
图片上传使用了weui里面的上传图片插件
把上传的js写到mouted函数中,发表前进行预验证,如果没有发表心情就提示。
mounted () {
/**
* 上传图片之前,确保服务端程序根目录下存储 public/upload 目录,否则会报错
*/
const self = this
weui.uploader('#uploader', {
url: service.baseURL + '/post/uploadimg',
auto: true,
type: 'file', // 将图片文件上传,而不是转换为 base64再上传编码
fileVal: 'image',
compress: {
width: 1600,
height: 1600,
quality: 0.8
},
onBeforeQueued: function (files) {
// `this` 是轮询到的文件, `files` 是所有文件
if (['image/jpg', 'image/jpeg', 'image/png', 'image/gif'].indexOf(this.type) < 0) {
weui.alert('请上传符合条件的图片')
return false // 阻止文件添加
}
if (this.size > 10 * 1024 * 1024) {
weui.alert('请上传不超过10M的图片')
return false
}
// 控制不能多选超过5张图片
if (files.length > self.totalUploadCount) { // 防止一下子选择过多文件
weui.alert('最多只能上传' + self.totalUploadCount + '张图片,请重新选择')
return false
}
// 控制如果当前已经上传了5张,那么就不能再选择图片上传
if (self.uploadCount + 1 > self.totalUploadCount) {
weui.alert('最多只能上传' + self.totalUploadCount + '张图片')
return false
}
self.uploadCount++
// return true; // 阻止默认行为,不插入预览图的框架
},
onBeforeSend: function (data, headers) {
const token = document.cookie.split('=')[1]
// console.log(token)
// console.log(this)
// console.log(data)
// console.log(headers)
headers['wec-access-token'] = token
// return false; // 阻止文件上传
},
onProgress: function (procent) {
// console.log(this, procent)
// return true; // 阻止默认行为,不使用默认的进度显示
},
onSuccess: function (ret) {
ret.data.id = this.id
self.picList.push(ret.data)
// return true; // 阻止默认行为,不使用默认的成功态
}
})
}
2.3朋友圈
发表成功跳转到朋友圈列表展示,一次只能展示5条信息,上拉到底部进行更新,每次更新5条数据,
下拉刷新更新最新的朋友圈。
功能代码
mounted () {
this.$bus.$on('dataLoadReady', () => {
// // 为动画设置定时器
if (this.$refs.circleIconInner) {
setTimeout(() => {
// 取消圆球的旋转
this.$refs.circleIconInner.classList.remove('circle-rotate')
// 让圆球的位置归位
this.$refs.circleIcon.style.transition = 'all 500ms'
this.$refs.circleIcon.style.transform = 'translate(0,-30px) rotate(0deg)'
this.pullRefresh.isPull = false
}, 800)
}
})
},
methods: {
touchstart (e) {
// 记录触摸的起始纵坐标
this.pullRefresh.dragStart = e.targetTouches[0].clientY
},
touchmove (e) {
console.log('2222')
const target = e.targetTouches[0]
// 记录(手指现在位置-初始位置)/屏幕高度 计算得来的数值
this.pullRefresh.percentage = (this.pullRefresh.dragStart - target.clientY) / window.screen.height
// 获取scrollTop的值,只有值为0时,才会开始下拉刷新逻辑
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop
if (scrollTop === 0) {
// 必须是向下拖动
if (this.pullRefresh.percentage < 0 && e.cancelable) {
// 满足上面两个条件,才是真正进入到下拉逻辑
this.pullRefresh.isPull = true
// 禁用浏览器的默认行为
e.preventDefault()
// 计算圆球的纵向移动距离
const translateY = -this.pullRefresh.percentage * this.pullRefresh.moveCount
if (Math.abs(this.pullRefresh.percentage) <= this.pullRefresh.dragEnd) {
// 计算圆球的旋转角度
const rotate = translateY / 100 * 360
// 设置圆球的纵向位置
this.$refs.circleIcon.style.transform = `translate(0,${translateY}px) rotate(${rotate}deg)`
}
} else {
// 向上拖动,就不会进入下拉刷新逻辑
this.pullRefresh.dragStart = null
}
} else {
// 如果没有在页面顶部执行拖动事件,则不执行下拉刷新逻辑
this.pullRefresh.dragStart = null
}
},
// 手指松开屏幕后,圆球归位,加载最新数据
touchend (e) {
if (!this.pullRefresh.isPull) {
return
}
console.log(1111)
// console.log(Math.abs(this.pullRefresh.percentage) > this.pullRefresh.dragEnd)
if (Math.abs(this.pullRefresh.percentage) > this.pullRefresh.dragEnd) {
// 为小圆球引用动画
this.$refs.circleIconInner.classList.add('circle-rotate')
// 通知使用此组件的组件加载最新数据
this.$emit('onRefresh')
} else {
// 如果用户松开手指时,下拉的距离没有达到临界值,就自动收回
this.$refs.circleIcon.style.transition = 'all 500ms'
this.$refs.circleIcon.style.transform = 'translate(0,0) rotate(0deg)'
}
// 重置dragstart
this.pullRefresh.dragStart = null
this.pullRefresh.percentage = null
}
}
上拉更新功能代码
mounted () {
// 为 window 对象添加一个滚动事件
window.addEventListener('scroll', () => {
// 获取clientHeight
const clientHeight = document.documentElement.clientHeight
// 获取scrollHeight
const scrollHeight = document.body.scrollHeight
// 获取scrollTop,注意兼容问题
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop
// 通知headerbar组件可以根据当前的scrollTop的值
this.$bus.$emit('scroll', scrollTop)
if ((clientHeight + scrollTop) >= (scrollHeight - 50)) {
if (this.$store.state.flag) {
if (!this.readyToLoad) {
return false
}
// 通知父组件加载数据
this.$emit('loadData')
}
}
})
}
}
2.4 切换背景图片
样式是使用weui进行编写的,上传代码跟发表里的上传代码是一样的。
2.5 点赞评论
点赞就是调用需要的接口进行验证是否点赞,如果点赞就在那条朋友圈下面显示点赞人,取消点赞随之消失。
点击评论弹出对话框,输入完成隐藏。
功能代码
// 展示点赞和评论面板
showPanel (e) {
this.showOpera = !this.showOpera
},
// 点赞和取消点赞
operaLike () {
if (this.data.isLike) {
// 取消点赞
this.removeLike()
} else {
// 点赞
this.addLike()
}
},
// 取消点赞
async removeLike () {
const res = await service.post('likecomment/removelike', {
postId: this.data._id
})
if (res.data.code === 0) {
// 通知vuex的store更新朋友圈的列表的数据
this.$store.dispatch('removeLike', {
pid: this.data._id,
user: this.$store.state.currentUser
})
}
},
// 点赞
async addLike () {
const res = await service.post('likecomment/addlike', {
postId: this.data._id
})
if (res.data.code === 0) {
// 通知vuex的store更新朋友圈的列表的数据
this.$store.dispatch('addLike', {
pid: this.data._id,
user: this.$store.state.currentUser
})
}
},
// 点击评论触发的事件
addComment (e) {
// 获取当前点击的坐标
this.data.pageY = e.pageY
this.data.clientY = e.clientY
this.$bus.$emit('showInput', this.data)
},
goPersonPage (userId) {
// console.log(userId)
// console.log()
if (userId === this.$store.state.currentUser._id) {
return this.$router.push('mypage')
}
this.$router.push({
path: 'personPage',
query: {
id: userId
}
})
}
2.6 查看用户信息
点击用户头像可以查看用户信息,可以进行与用户的私信
3.个人信息功能
点击自己的个人信息查看自己的信息,点击每一条信息都可进行编辑信息。
代码都是一些简单的查改
4.私信功能
4.1.私信
点击发消息就可与用户进行聊天,运用vue-socket.io和socket.io-client插件,实现了聊天的实时通讯。
代码
<template>
<div class="container">
<navHeader :title="topName"></navHeader>
<!-- 聊天界面 -->
<div class="chat-view" ref="chatView" @touchstart="touchstart">
<chatItem v-for="(item) in dataList" :data="item" :key="item._id"/>
</div>
<!-- 输入框 -->
<div :class="bottomClass">
<inputbar ref="inputBar"
@uploaded="uploaded"
@showMorePanel="showMorePanel"
:option="option"
@publish="publish"></inputbar>
</div>
</div>
</template>
<script>
import navHeader from '@/components/navHeader'
import inputbar from '@/components/inputbar'
import chatItem from '@/components/chatItem'
import service from '@/utils/service'
export default {
components: {
navHeader,
inputbar,
chatItem
},
data () {
return {
bottomClass: 'bottom-view',
topName: this.$route.query.name,
toUserId: this.$route.query.id, // 聊天对象的id
dataList: [], // 存储的聊天信息,
option: { noPlus: false } // 设置输入框组件是否显示更多按钮和更多面板:noPlus=true:不显示,noPlus=false:显示
}
},
// 页面加载时,首先当前用户登录socket,获取当前登录用户与当前指定用户的聊天记录
created () {
if (this.$store.state.currentUser && this.$store.state.currentUser._id) {
// 登录socket
this.$socket.emit('login', this.$store.state.currentUser)
}
// 获取历史聊天数据
this.fetchData()
},
sockets: {
// 当服务器端有消息推送过来的时候,这个方法就会执行,推送的数据会赋值给参数obj
recieveMsg: function (obj) {
console.log(obj.fromUser._id, this.toUserId)
if (obj.fromUser._id === this.toUserId) {
this.addMessage({
content: obj.content,
fromUser: obj.fromUser,
mine: false // 对方发送的消息
})
}
},
/**
* 服务器掉之后,客户端会重新连接,连接成功后会触发下面的事件
* 我们就在这个事件中,重新登录
*/
reconnect (obj) {
if (this.$store.state.currentUser && this.$store.state.currentUser._id) {
// 登录socket
this.$socket.emit('login', this.$store.state.currentUser)
}
}
},
methods: {
touchstart () {
this.bottomClass = this.bottomClass.replace('show', '')
},
// 获取聊天记录
async fetchData () {
const res = await service.get('message/getchathistory', {
toUser: this.toUserId
})
if (res.data.code === 0) {
this.dataList = (res.data.data)
}
},
// 发表文字内容
async publish (data) {
// 将当前登录用户发表的消息存储到数据库中
const res = await service.post('message/addmsg', {
content: { type: 'str', value: data.value },
toUser: this.toUserId
})
// 如果发表成功,将当前消息,添加到dataList中
this.addMessage({
content: { type: 'str', value: data.value },
fromUser: this.$store.state.currentUser,
mine: true
})
if (res.data.code !== 0) {
weui.toast('消息发送失败')
}
},
// 向dataList中加入最新的消息(可能时自己发的,也可能是最新收到的)
addMessage (message) {
console.log(message)
this.dataList.push(message)
// 设置页面滚动到底部
setTimeout(() => {
this.$nextTick(() => {
this.$refs.chatView.scrollTop = this.$refs.chatView.scrollHeight
})
}, 500)
},
// 展示更多面板
showMorePanel () {
if (this.bottomClass.indexOf('show') > -1) {
this.bottomClass = this.bottomClass.replace('show', '')
} else {
this.bottomClass += ' show'
}
},
// 图片上传成功之后,将图片路径作为一条消息进行保存
async uploaded (data) {
// console.log(data)
// 将当前登录用户发表的消息存储到数据库中
const res = await service.post('message/addmsg', {
content: { type: 'pic', value: data.data },
toUser: this.toUserId
})
// 如果发表成功,将当前消息,添加到dataList中
this.addMessage({
content: { type: 'pic', value: data.data },
fromUser: this.$store.state.currentUser,
mine: true
})
if (res.data.code !== 0) {
weui.toast('消息发送失败')
}
}
}
}
</script>
4.2 查看私信
查看与你聊天人的信息。
<template>
<div class="container">
<navHeader title="消息列表"></navHeader>
<!-- 搜索框 -->
<div class="weui-search-bar" id="searchBar" :class="searchBarClass">
<form class="weui-search-bar__form">
<div class="weui-search-bar__box">
<i class="weui-icon-search"></i>
<input v-model="keyword" type="search" class="weui-search-bar__input" id="searchInput" placeholder="搜索" @input="searchChat($event)" required/>
<a href="javascript:" @click="clearSearch" class="weui-icon-clear" id="searchClear"></a>
</div>
<label class="weui-search-bar__label" id="searchText">
<i class="weui-icon-search"></i>
<span>搜索</span>
</label>
</form>
<a href="javascript:" class="weui-search-bar__cancel-btn" id="searchCancel">取消</a>
</div>
<!-- 消息列表展示 -->
<div class="content-list">
<div class="weui-loadmore" v-show="loading">
<i class="weui-loading"></i>
<span class="weui-loadmore__tips">正在加载</span>
</div>
<div class="weui-loadmore weui-loadmore_line weui-loadmore_dot" v-show="!loading&&dataList.length===0">
<span class="weui-loadmore__tips"></span>
</div>
<!-- 消息列表展示 -->
<div @click="goChat(item)" class="item" v-for="item in dataList" :key="item.id">
<!-- 用户头像 -->
<!-- 用户头像 -->
<img :src="item.user.avatar" class="avatar">
<div class="right-content scale-1px">
<p class="nickname one-line">{{item.user.nickname}}</p>
<p v-if="item.msg.content && item.msg.content.type==='str'" class="text one-line">{{item.msg.content.value}}</p>
<p v-if="item.msg.content && item.msg.content.type==='pic'" class="text one-line">[图片]</p>
</div>
<div class="time">{{item.msg.create|ceshi}}</div>
</div>
</div>
</div>
</template>
<script>
import navHeader from '@/components/navHeader'
import service from '@/utils/service'
import moment from 'moment'
import Vue from 'vue'
// import formatTime from '@/utils/formatTime'
Vue.filter('ceshi', (e) => {
var now = moment().locale('zh-cn').format('YYYY-MM-DD HH:mm:ss')
return moment(now).diff(moment(e), 'minutes') + '分钟前'
})
export default {
components: {
navHeader
},
mounted () {
// 没有这行代码,文本框无法输入内容
weui.searchBar('#searchBar')
},
data () {
return {
searchBarClass: '',
loading: true,
dataList: [] // 存放消息列表的数组
}
},
computed: {
keyword: {
get: function () {
return this.$store.state.keyword
},
set: function (newvalue) {
this.$store.dispatch('setkeyword', newvalue)
}
}
},
created () {
if (this.keyword) {
this.searchBarClass = 'weui-search-bar_focusing'
} else {
this.searchBarClass = ''
}
this.fetchData()
},
methods: {
async fetchData () {
this.loading = true
const res = await service.get('message/getchatlist', {
keyword: this.keyword
})
if (res.data.code === 0) {
this.loading = false
this.dataList = res.data.data
// console.log(this.dataList)
}
},
searchChat () {
this.fetchData()
},
clearSearch () {
this.keyword = ''
this.fetchData()
},
goChat (item) {
this.$router.push({
path: 'chat',
query: {
id: item.user._id,
name: item.user.nickname
}
})
}
}
}
</script>