此项目运用运用 vue 全家桶技术(vue+vue-cli+vuex+vue-router+node...),前后台分离和组件化的方式开发,使用WeUl基础样式库进行前台页面的搭建,后台使用的是MongoDB数据库进行编写,使用阿里云短信服务进行验证码注册。实现了下拉刷新、消息发送、大图预览、朋友圈发表。。。。
MVVM 框架:Vue.js 2.0
状态管理:Vuex
页面路由:Vue-router
弹窗插件:WeUl
聊天插件:vue-socket.io
环境配置:node.js + cnpm
图片插件:vue-photo-preview
使用WeUl表单实现此样式
使用阿里云短信服务,输入手机号发送验证码,手机号进行验证,输入合法的手机号才可以发送验证码,验证码后台验证,要输入对的验证码才能登陆。验证码一分钟有效,一分钟之后要重新发送,重新获取。
async signup () {
// 验证手机号
if (!(/^1[3456789]\d{9}$/.test(this.mobile))) {
weui.topTips('请输入合法的手机号', 2000)
return false
}
// 验证码
if (!this.code) {
weui.topTips('请填入验证码', 2000)
return false
}
// 发送请求,注册或者登录
const res = await service.post('/users/signup', {
phonenum: this.mobile,
code: this.code
})
/* 注册成功后的处理
1.将登录或者注册的用户信息保存起来,以供其他页面使用
2.跳转到用户本来想要请求的页面
*/
if (res.data.code === 0) {
weui.toast('登录成功', 2000)
this.$store.dispatch('setUser', res.data.data)
this.$router.go(-1)
}
},
// 验证码
async getCode () {
// 验证手机号
if (!(/^1[3456789]\d{9}$/.test(this.mobile))) {
weui.topTips('请输入合法的手机号', 2000)
return false
}
// 向服务器接口发送请求
const res = await service.get('/users/phonecode', {
phonenum: this.mobile
})
console.log(res)
// 开启定时器,60s倒计时
this.countTimeCode()
},
// 倒计时
countTimeCode () {
// 为this对象(当前的vue实例)动态添加一个属性clearFlag
this.clearFlag = setInterval(() => {
if (this.timecode === 0) {
this.timecode = 60
// 清除定时器
clearInterval(this.clearFlag)
return
}
this.timecode--
}, 1000)
}
},
beforeDestroy () {
clearInterval(this.clearFlag)
}
朋友圈列表是分组件开发,几个组件组合成了这个页面。分别有列表组件、头部组件、背景图页面、下拉刷新组件、上拉刷新组件、评论组件和点赞组件。
3.2.1头部组件
滚到到一点高度会显示,点击相机会跳转到发表朋友圈页面,进行判断如果没有登录则返回登录页面若登录则跳转到发布朋友圈页面。
goPublish () {
/**
* 判断用户是否登录
* - 如果没有登录,跳转到登录页面
* - 如果登录,则跳转到publish页面
*/
if (!this.$store.state.currentUser._id) {
this.$router.push('login')
return
}
this.$router.push('publish')
}
3.2.2背景图页面
背景图展示和更换,若此用户没有更换过页面会展示系统默认背景图片和头像(从文件夹里自动分配),若此用户有自己的背景图和头像(数据库会有对应的数据可以拿出来进行展示),会展示自己的背景图和头像
computed: {
// 设置用户的背景图像
topBgImg () {
const url = this.$store.state.currentUser.bgurl || require('../../assets/images/topbg.jpg')
// console.log(url)
return {
backgroundImage: `url(${url})`
}
},
// 设置用户头像
usreAvatar () {
const url = this.$store.state.currentUser.avatar || require('../../assets/images/avatar.jpg')
return url
},
// 设置用户昵称
nickname () {
return this.$store.state.currentUser.nickname
}
}
点击背景图片可以选择更换背景图片,用户从相册中选了了图片后,将图片上传,服务器返回上传成功的信息。根据服务器返回的图片url,更新用户背景图像:1)更新数据库中用户的背景图像;2)更换页面上用户的背景图像(使用了weui里面的upload组件上传图片和actionsheet组件弹出弹窗)
changeBg () {
const sefl = this
// 弹出菜单
weui.actionSheet([
{
label: '从相册选择',
onClick: function () {
// 展示一个用于选择图片的对话框
sefl.$refs.uploaderBg.click()
}
}], [
{
label: '取消',
onClick: function () {
console.log('取消')
}
}
])
},
// 更换用户的背景 图像
async submit (obj) {
// 更换数据库中用户的背景图像
const res = await service.post('users/update', {
userId: this.$store.state.currentUser._id,
bgurl: obj.data.url
})
// 更新本地用户背景图像
if (res.data.code === 0) {
this.$store.dispatch('setUser', {
...this.$store.state.currentUser,
bgurl: obj.data.url
})
}
}
3.2.3列表组件
展示用户头像、帖子内容、帖子的发表时间和点赞和评论功能,实现了大图预览,可以查看朋友发表的图片的高清大图。(使用了vue-photo-preview插件进行图片大图预览的功能)
{
{data.user.nickname}}
{
{data.content}}
{
{formatTime(data.create)}}
赞
取消
评论
{
{item.nickname ||item.user.nickname}}
{
{item.user.nickname}}:
{
{item.content}}
时间展示
formatTime (time) {
return formatTime(new Date(time).getTime() / 1000)
}
3.2.4点赞组件
点赞喜欢的帖子,通知vuex的store更新朋友圈的列表的数据
// 点赞和取消点赞
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) {
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) {
this.$store.dispatch('addLike', {
pid: this.data._id,
user: this.$store.state.currentUser
})
}
}
3.2.5评论组件
点击评论获取用户点击的位置,展示文本框,输入内容成功评论。对安卓系统和iOS系统进行了判断,根据系统的不同展示位置也不同。使用了vuex公交车,进行数据传值,更新展示到页面。
addComment (e) {
// 获取当前点击的坐标
this.data.pageY = e.pageY
this.data.clientY = e.clientY
this.$bus.$emit('showInput', this.data)
}
// 发表评论
async publish (data) {
const res = await service.post('likecomment/addcomment', {
postId: data.data._id,
content: data.value
})
if (res.data.code === 0) {
this.showInput = false
this.$store.dispatch('addComment', {
pid: res.data.data.post, // 帖子的id
content: data.value,
user: this.$store.state.currentUser
})
}
}
3.2.6 上拉刷新组件
每页默认展示五条数据,点击屏幕向上滑动时触发向上刷新加载更多内容。
async getCircleList () {
this.readyToload = false // 表示数据还没有加载完毕
const res = await service.get('post/getcirclepost', {
pageStart: this.pageStart
})
if (res.data.code === 0) {
// 通知下拉刷新组件,数据已经加载完毕
this.$bus.$emit('dataLoadReady')
this.$store.dispatch('setCircleDatalist', res.data.data)
this.readyToload = true
this.$store.commit('setFlag', false)
}
console.log(this.circleList)
},
loadData () {
// console.log('加载更多数据')
this.pageStart++
this.getCircleList()
}
3.2.7下拉刷新
下拉刷新,请求最新的数据,展示到页面。小圆球转动,数据请求完毕小圆球停止转动归位,实现加载页面的功能。
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)
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
}
}
点击列表页的相机图标,跳转到发布朋友圈页面。动态内容限制在200字以内,上传图片限制在五张,上传的图片可以预览,删除。
oninput () {
if (this.content.length > 200) {
this.content = this.content.substr(0, 200)
}
this.letterCount = this.content.length
},
// 查看大图
preImg (e) {
const self = this
const style = e.target.getAttribute('style')
const url = style.split('"')[1]
var gallery = weui.gallery(url, {
onDelete: function () {
self.deleteImg(e.target, gallery)
}
})
},
// 删除图片
deleteImg (target, gallery) {
const self = this
weui.confirm('确定删除该图片?', () => {
// 从数组picList中删除图片
const id = target.dataset.id
const index = self.picList.findIndex(item => {
return item.id === id
})
self.picList.splice(index, 1)
// 删除对应的dom元素
target.remove()
self.uploadCount--
})
gallery.hide(function () {
console.log('`gallery` has been hidden')
})
}
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
}
if (files.length > self.totalUploadCount) { // 防止一下子选择过多文件
weui.alert('最多只能上传' + self.totalUploadCount + '张图片,请重新选择')
return false
}
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]
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)
// console.log(this, ret)
// return true; // 阻止默认行为,不使用默认的成功态
},
onError: function (err) {
console.log(this, err)
// return true; // 阻止默认行为,不使用默认的失败态
}
})
此页面展示了个人详细信息,可以修改个人资料。
点击头像弹出图片选择,选择图片进行头像的修改。
点击昵称进入修改昵称界面,输入名字进行修改,修改的名字不得为空。
点击个性签名进入修改界面,输入内容进行修改。
点击性别弹出修改性别的选择框,修改性别。
3.4.1 消息列表
点击私信进入消息列表页面,展示与好友们的聊天的最后一条消息,可以通过关键字进行消息搜索,点击与该好友的聊天内容,进入到与好友的聊天页面。
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
}
},
searchChat () {
this.fetchData()
}
在朋友圈页面点击好友头像进入到好友名片页面,在跳转的同时将该好友的id传过去,然后根据id查询好友信息,并遍历展示到页面。
点击好友名片中的发消息可以与好友进行聊天进入到聊天页面。获取与好友的历史聊天记录展示到页面。默认情况下只显示文本框,点击加号显示更多内容,可以发送图片。运用vue-socket.io和socket.io-client插件,实现了聊天的实时通讯。
created () {
if (this.$store.state.currentUser && this.$store.state.currentUser._id) {
// 登录socket
this.$socket.emit('login', this.$store.state.currentUser)
}
// 获取历史聊天数据
this.fetchData()
},
sockets: {
// 接收消息
recieveMsg: function (obj) {
if (obj.fromUser._id === this.toUserId) {
this.addMessage({
content: obj.content,
fromUser: obj.fromUser,
main: false
})
}
},
/**
* 服务器掉之后,客户端会重新连接,连接成功后会触发下面的事件
* 我们就在这个事件中,重新登录
*/
reconnect (obj) {
if (this.$store.state.currentUser && this.$store.state.currentUser._id) {
// 登录socket
this.$socket.emit('login', this.$store.state.currentUser)
}
}
}
// 获取聊天记录
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.topTips('消息发送失败')
}
}
// 上传图片成功之后,将图片作为一条消息
async uploaded (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.topTips('图片发送失败')
}
}