# 个人中心
# 组件基本布局
目标:实现个人中心组件基本布局
- 组件模板
<div class="user-profile">
<div class="info">
<van-image round src="https://img.yzcdn.cn/vant/cat.jpeg" />
<h3 class="name">
用户名
<br />
<van-tag size="mini">申请认证</van-tag>
</h3>
</div>
<van-row>
<van-col span="8">
<p>0</p>
<p>动态</p>
</van-col>
<van-col span="8">
<p>0</p>
<p>关注</p>
</van-col>
<van-col span="8">
<p>0</p>
<p>粉丝</p>
</van-col>
</van-row>
</div>
<van-row class="user-links">
<van-col span="8">
<van-icon name="newspaper-o" color="#7af"/>我的作品
</van-col>
<van-col span="8">
<van-icon name="star-o" color="#f00"/>我的收藏
</van-col>
<van-col span="8">
<van-icon name="tosend" color="#fa0"/>阅读历史
</van-col>
</van-row>
<van-cell-group class="user-group">
<van-cell icon="edit" title="编辑资料" to="/user/profile" is-link />
<van-cell icon="chat-o" title="小智同学" to="/user/chat" is-link />
<van-cell icon="setting-o" title="系统设置" is-link />
<van-cell icon="warning-o" title="退出登录" is-link />
</van-cell-group>
- 布局样式
.user {
&-profile {
width: 100%;
height: 150px;
display: block;
background: #3296fa;
color: #fff;
.info {
display: flex;
padding: 20px;
align-items: center;
.van-image {
width: 64px;
height: 64px;
}
.name {
font-size: 16px;
font-weight: normal;
margin-left: 10px;
}
.van-tag {
background: #fff;
color: #3296fa;
}
}
p {
margin: 0;
text-align: center;
}
}
&-group {
margin-bottom: 15px;
}
&-links {
padding: 15px 0;
font-size: 12px;
text-align: center;
background-color: #fff;
.van-icon {
display: block;
font-size: 24px;
padding-bottom: 5px;
}
}
}
总结:工作量(布局样式要熟练)
# 用户信息动态渲染
目标:实现用户信息的动态渲染
- 调用接口获取用户数据
- 渲染模板
- 封装用户信息接口调用方法
export const getUserInfo = () => {
return request({
method: 'get',
url: 'app/v1_0/user'
})
}
- 获取用户个人信息
import { getUserInfo } from '@/api/api-login.js'
export default {
name: 'User',
data () {
return {
info: null
}
},
methods: {
// 调用接口获取用户信息
async getUserInfo () {
try {
const ret = await getUserInfo()
this.info = ret.data
} catch {
this.$toast.fail('获取用户信息失败')
}
}
},
created () {
this.getUserInfo()
}
}
- 组件的动态渲染
<div class="user-profile">
<div class="info">
<van-image round :src="user.photo" />
<h3 class="name">
{{user.name}}
<br />
<van-tag size="mini">申请认证</van-tag>
</h3>
</div>
<van-row>
<van-col span="6">
<p>{{user.art_count}}</p>
<p>动态</p>
</van-col>
<van-col span="6">
<p>{{user.follow_count}}</p>
<p>关注</p>
</van-col>
<van-col span="6">
<p>{{user.fans_count}}</p>
<p>粉丝</p>
</van-col>
<van-col span="6">
<p>{{user.like_count}}</p>
<p>被赞</p>
</van-col>
</van-row>
</div>
总结:调用接口;获取数据;填充页面
# 退出功能
目标:实现退出功能
- 绑定事件
- 删除token(store和缓存)
- 事件绑定
<van-cell @click="logout()" icon="warning-o" title="退出登录" is-link />
- 功能实现
import { mapMutations } from 'vuex'
...mapMutations('login', ['deleteUserInfo']),
logout () {
// 实现退出:确认退出,清除token,跳转到登录页面
this.$dialog.confirm({
title: '退出',
message: '确认要退出吗?'
}).then(() => {
// 点击确定触发
// 1、清除token
this.deleteUserInfo()
// 2、跳转到登录页面
this.$router.push('/login')
// this.$toast.success('确认退出')
}).catch(() => {
// 点击取消触发
this.$toast.fail('取消退出')
})
},
总结:基于vuex实现退出,触发mutation,置空state并且清空缓存中的token信息
# 编辑用户布局
目标:实现用户信息编辑功能
- 准备编辑用户的组件
- 配置组件的路由
- 获取用户的信息
- 填充用户信息到表单
- 提交表单
- 基本布局
<div class="container">
<van-nav-bar left-arrow @click-left="$router.back()" title="编辑资料" right-text="保存" ></van-nav-bar>
<van-cell-group>
<van-cell is-link title="头像" @click="showPhoto=true" center>
<van-image
slot="default"
width="1.5rem"
height="1.5rem"
fit="cover"
round
:src="photo"
/>
</van-cell>
<van-cell is-link title="名称" @click="showName=true" :value="user.name" />
<van-cell is-link title="性别" @click="showGender=true" :value="user.gender===0?'男':'女'" />
<van-cell is-link title="生日" @click="showBirthday=true" :value="user.birthday" />
</van-cell-group>
<van-popup v-model="showPhoto" position="bottom">
<van-cell value="本地相册选择" is-link/>
<van-cell value="拍照" is-link/>
</van-popup>
<van-popup v-model="showName" position="bottom">
<van-field v-model="user.name" required placeholder="请输入用户名" />
</van-popup>
<van-popup v-model="showGender" position="bottom">
<van-cell value="男" @click="changeGender(0)" is-link/>
<van-cell value="女" @click="changeGender(1)" is-link/>
</van-popup>
<van-popup v-model="showBirthday" position="bottom">
<van-datetime-picker
title="选择生日"
v-model="nowDate"
type="date"
:min-date="minDate"
@cancel="dateShow=false"
@confirm="confirmDate"
/>
</van-popup>
</div>
- 状态数据
data () {
return {
// 弹窗控制
showPhoto: false,
showName: false,
showGender: false,
showBirthday: false,
nowDate: new Date(),
minDate: new Date('1950-01-01'),
// 默认用户信息
photo: 'https://img.yzcdn.cn/vant/cat.jpeg',
user: {
name: '默认信息',
gender: 0,
birthday: '2019-10-10'
}
}
},
- 数据处理
// 获取用户信息
export const getUserProfile = () => {
return request({
method: 'get',
url: 'v1_0/user/profile'
})
}
// -------------------------------------------
created () {
this.getUserProfile()
},
methods: {
async getUserProfile () {
try {
const ret = await getUserProfile()
this.user = ret.data
} catch {
this.$toast.fail('获取用户信息失败')
}
},
confirmDate (birthday) {
// 生日选择:参数默认选中的是日期对象,但是我们需要字符串
// 基于dayjs的包提供的api进行日期格式化
const str = dayjs(birthday).format('YYYY-MM-DD')
this.user.birthday = str
this.showBirthday = false
},
changeGender (value) {
// 性别选择
this.user.gender = value
this.showGender = false
}
}
}
# 编辑用户功能
目标:实现编辑用户数据更新
- 点击下拉选项触发弹窗
- 选中图片并进行预览
- 预览头像
<input type="file" @change="preview" ref="myfile" style="display:none">
<van-cell value="本地相册选择" @click="$refs.myfile.click()" is-link />
- 预览图片
// 预览图片
previewImg (e) {
// 预览图片信息
const img = e.target.files
if (img.length === 0) {
return this.$toast.fail('请选择文件')
}
// 根据图片文件预览
const fr = new FileReader()
// 读取图片文件
fr.readAsDataURL(img[0])
fr.onload = () => {
// 文件加载并转换成地址成功后触发
this.user.photo = fr.result
// 隐藏窗口
this.showPhoto = false
}
},
- 提交表单
// 上传头像
export const saveUserPhoto = (formData) => {
return request({
method: 'patch',
url: 'v1_0/user/photo',
data: formData
})
}
// 保存用户信息
export const saveUserInfo = (data) => {
return request({
method: 'patch',
url: 'v1_0/user/profile',
data: data
})
}
// 保存用户信息
async saveInfo () {
// 编辑用户提交表单
// 1、上传文件
try {
const fd = new FormData()
fd.append('photo', this.$refs.myfile.files[0])
await saveUserPhoto(fd)
} catch {
return this.$toast.fail('上传头像失败')
}
// 2、更新用户表单数据
try {
await saveUserInfo({
name: this.user.name,
gender: this.user.gender,
birthday: this.user.birthday
})
} catch {
return this.$toast.fail('更新用户信息失败')
}
},
//总结: //1. 如何触发弹窗选中文件?利用了file类型的input触发 //2. 如何直接操作DOM元素?this.$refs.myfile //3. 如何基于原生js的api实现图片预览 FileReader //4. 提交表单需要分两步:头像单独上传;表单项单独提交
# 小智同学-基本布局
//目标:实现聊天功能基本布局 //1. 实现页面聊天布局 //2. 熟悉websocket技术 //3. 基于websocke实现基本的聊天功能
- 基本布局
<div class="container">
<van-nav-bar fixed left-arrow @click-left="$router.back()" title="小智同学"></van-nav-bar>
<div class="chat-list">
<div class="chat-item left">
<van-image fit="cover" round src="https://img.yzcdn.cn/vant/cat.jpeg" />
<div class="chat-pao">ewqewq</div>
</div>
<div class="chat-item right">
<div class="chat-pao">ewqewq</div>
<van-image fit="cover" round src="https://img.yzcdn.cn/vant/cat.jpeg" />
</div>
</div>
<div class="reply-container van-hairline--top">
<van-field v-model="value" placeholder="说点什么...">
<span @click="send()" slot="button" style="font-size:12px;color:#999">提交</span>
</van-field>
</div>
</div>
- 布局样式
.container {
height: 100%;
width: 100%;
position: absolute;
left: 0;
top: 0;
box-sizing: border-box;
background:#fafafa;
padding: 46px 0 50px 0;
.chat-list {
height: 100%;
overflow-y: scroll;
.chat-item{
padding: 10px;
.van-image{
vertical-align: top;
width: 40px;
height: 40px;
}
.chat-pao{
vertical-align: top;
display: inline-block;
min-width: 40px;
max-width: 70%;
min-height: 40px;
line-height: 38px;
border: 0.5px solid #c2d9ea;
border-radius: 4px;
position: relative;
padding: 0 10px;
background-color: #e0effb;
word-break: break-all;
font-size: 14px;
color: #333;
&::before{
content: "";
width: 10px;
height: 10px;
position: absolute;
top: 12px;
border-top:0.5px solid #c2d9ea;
border-right:0.5px solid #c2d9ea;
background: #e0effb;
}
}
}
}
}
.chat-item.right{
text-align: right;
.chat-pao{
margin-left: 0;
margin-right: 15px;
&::before{
right: -6px;
transform: rotate(45deg);
}
}
}
.chat-item.left{
text-align: left;
.chat-pao{
margin-left: 15px;
margin-right: 0;
&::before{
left: -5px;
transform: rotate(-135deg);
}
}
}
.reply-container {
position: fixed;
left: 0;
bottom: 0;
height: 44px;
width: 100%;
background: #f5f5f5;
z-index: 9999;
}
- 状态数据
export default {
data () {
return {
value: '',
loading: false
}
},
methods: {
send () {
}
}
}
# websocket介绍
目标:熟悉websocket通信规则
- http通信流程
- 建立连接(三次握手)
- 发送接收数据
- 断开连接(四次挥手)
- websocket通信流程(双向的:客户端可以向服务器发送消息,服务器也可以主动向客户端发送消息)
- 首次发送请求时,需要建立连接
- 后续收发消息就不再需要建立连接
- 长时间没有数据交互会自动断开连接(也可以手动断开连接)

结论:websocket做双向通信更加高效(比如聊天功能,推送服务)
# websocket基本使用
//目标:熟悉websocket基本用法 //浏览器为 HTTP 通信提供了 XMLHttpRequest 对象,同样的,也为 WebSocket 通信提供了一个操作接口:WebSocket。(http https) === (ws wss)
- 基本通信流程
- 拨号(建立连接)
- 通话(双向通信)
- 结束通话(关闭连接)
// 创建连接(和服务器建立连接,回复你)
var ws = new WebSocket("wss://echo.websocket.org");
// 连接成功
ws.onopen = function(evt) {
console.log("Connection open ...");
// 发送消息
ws.send("Hello WebSockets!");
};
// 接受信息
ws.onmessage = function(evt) {
// 服务回复消息
console.log( "Received Message: " + evt.data);
// 关闭连接
ws.close();
};
// 连接已经关闭
ws.onclose = function(evt) {
console.log("Connection closed.");
};
# websocket第三方包用法
目标:熟悉socket.io基本信息和用法
官方网站:https://socket.io/get-started/chat/
既支持服务器,也支持客户端
- 服务端代码(基于socket.io自己做)
/*
websocket服务端
作用:1、接收客户端发送的请求;2、向客户端返回数据
*/
var app = require('express')();
var http = require('http').createServer(app);
var io = require('socket.io')(http);
// 监听跟路径
app.get('/', function(req, res){
// 返回的结果是一个网页
res.sendFile(__dirname + '/index.html');
});
// 监听客户端的链接
io.on('connection', function(socket){
// 监听客户端端开链接事件
socket.on('disconnect', function(){
console.log('user disconnected');
});
// 监听客户端发送消息的事件
socket.on('chat message', function(msg){
// 客户端发送的消息msg
// emit方法的作用是向客户端返回消息
// emit参数一表示客户端监听的接收消息的事件
// emit参数二表示服务器返回给客户端的消息
io.emit('chat message', msg);
});
});
http.listen(3000, function(){
console.log('listening on *:3000');
});
- 客户端代码
<!doctype html>
<html>
<head>
<title>Socket.IO chat</title>
<meta charset="utf-8" >
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font: 13px Helvetica, Arial; }
form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
#messages { list-style-type: none; margin: 0; padding: 0; }
#messages li { padding: 5px 10px; }
#messages li:nth-child(odd) { background: #eee; }
</style>
</head>
<body>
<!-- 消息列表 -->
<ul id="messages"></ul>
<!-- 发送消息的表单 -->
<form action="">
<input id="m" autocomplete="off" /><button>Send</button>
</form>
<!-- SocketIO 提供了一个客户端实现:socket.io.js -->
<script src="https://cdn.bootcdn.net/ajax/libs/socket.io/3.0.0-rc4/socket.io.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
<script>
// 建立连接,得到 socket 通信对象
var socket = io('ws://localhost:3000/')
socket.on('connect', () => {
console.log('建立连接成功了')
})
$('form').submit(function(e){
e.preventDefault();
// 向服务器发送消息
// chat message事件名称与谁有关?服务器的事件监听有关
socket.emit('chat message', $('#m').val());
$('#m').val('');
return false;
});
// 客户端监听服务器返回消息的事件
socket.on('chat message', function(msg){
// msg表示服务器返回的消息,通过jQuery把消息添加到页面
$('#messages').append($('<li>').text(msg));
});
</script>
</body>
</html>
总结:我们关注的是客户端代码。
//- 发消息:`socket.emit('chat message', '内容');` //- 收消息:`socket.on('chat message', function(msg){}` //--- //- 关于socket通信案例运行步骤 //1. 安装依赖包 `npm i` //2. 运行后端socket `node server.js` //3. 打开前端页面 http://localhost:3000/ //4. 输入内容可以进行socket通信
# 小智同学聊天功能
目标:实现小智同学聊天功能
- 创建socket链接
- 发送消息给服务器
- 接收消息并进行展示
- 小智头像
import avatar from '@/assets/xz.png'
// data中数据
avatar: avatar
<van-image fit="cover" round :src="avatar" />
- 自己的头像
<van-image fit="cover" round :src="photo||avatar" />
- 开始聊天
- 聊天记录
list: []
- 建立连接(初始化socket对象;监控链接成功的事件,成功链接后进行提示)
// 服务器端使用socket.io包,客户端使用socket.io-client包
import { io } from 'socket.io-client'
methods: {
initSocket () {
// 初始化Socket
this.socket = io('ws://localhost:3000/')
// 链接成功之后,后端接口会有一个默认的返回信息
this.socket.on('connect', () => {
// 链接建立成功
this.list.push({
type: 1,
name: '小智',
msg: '欢迎聊天'
})
})
// 监听服务器返回的消息
this.socket.on('chat message', (data) => {
// 把返回的消息添加到列表中
this.list.push({
type: 1,
name: '小智',
msg: data
})
})
}
},
created () {
this.initSocket()
}
- 渲染记录
<div class="chat-item" :class='[{left: item.type===1}, {right: item.type===2}]' v-for='(item, index) in list' :key='index'>
<!-- 左侧内容 -->
<template v-if='item.type===1'>
<van-image fit="cover" round src="https://img.yzcdn.cn/vant/cat.jpeg" />
<div class="chat-pao">{{item.name + ':' + item.msg}}</div>
</template>
<!-- 右侧内容 -->
<template v-else>
<div class="chat-pao">{{item.name + ':' + item.msg}}</div>
<van-image fit="cover" round :src="photo||avatar" />
</template>
</div>
- 发送消息
send () {
// 点击按钮发送消息给服务器
this.socket.emit('chat message', this.value)
// 把发送的消息添加到列表中
this.list.push({
type: 2,
name: '我',
msg: this.value
})
// 清空表单
this.value = ''
},
- 接收消息
this.socket.on('connect', () => {
// 建了链接后默认 小智给你打招呼
this.list.push({ name: 'xz', msg: '你好' })
})
// 监听服务器返回的消息
this.socket.on('chat message', (data) => {
// 把返回的消息添加到列表中
this.list.push({
type: 1,
name: '小智',
msg: data
})
})
- 关闭链接
destroyed () {
this.socket.close()
}
# 关于组件的缓存
//默认情况下,切换组件页面后,原来的组件自动销毁,再次显示组件时,组件需要重新创建(相关生命周期函数会再次触发) //那么有时,为了满足特殊场景,需要对组件进行缓存(通过 <keep-alive>标签进行组件缓存) //缓存组件后,会有如下好处 //1. keep-alive包裹组件后,那么组件会进行缓存,再次显示组件时,效率更高 //2. keep-alive缓存组件可以使组件的状态保留,从而满足维持组件当前状态的需求
- 关于缓存相关的生命周期函数
- activated 显示组件时触发
- deactivated 离开组件时触发
<template>
<div>
<div>Cachea</div>
<div>{{count}}</div>
<button @click='handleClick'>点击</button>
</div>
</template>
<script>
export default {
name: 'Cachea',
data () {
return {
count: 0
}
},
methods: {
handleClick () {
this.count += 1
}
},
created () {
console.log('a')
},
activated () {
// 显示组件时触发
console.log('a', 'activated')
this.count = 0
},
deactivated () {
// 离开组件时触发
console.log('a', 'deactivated')
}
}
</script>
- 根据特定路由组件设置缓存
{ path: 'home', component: Home, meta: { isCache: true } },
<div class="wrapper">
<keep-alive>
<router-view v-if='$route.meta.isCache'/>
</keep-alive>
<router-view v-if='!$route.meta.isCache'/>
</div>
← 文章详情模块