目录
小爱同学模块
一、WebSocket介绍
WebSocket是一种数据通信协议,也是用于客户端和服务端数据通信,类似于常见的http。
但是http通信是单向的,即请求+响应,没有请求就没有响应,并且通信只能由客户端发起
,服务端返回查询结果,http做不到服务器向客户端推送信息。
如果服务器有了连续的状态变化,客户端要获知比较麻烦,只能使用“轮询”,每隔一段时间就发出一个询问去了解服务器有没有新的消息。“轮询”效率低,浪费资源,因为需要不停连接或者http连接始终打开。
WebSocket:
服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话
ws://exmaple.com:80/some/path
二、使用原生WebSocket(了解)
三、Socket.IO(了解)
3.1 介绍
原生的WebSocket使用比较麻烦,推荐使用封装好的解决方案:socket.io
socket.io提供了服务端+客户端的实现
- 客户端:浏览器
- 服务端:JAVA PYTHON PHP .... NODE.JS
对于前端开发者来说只需要关心它的客户端即可
注意:socket.io必须前后端配套使用
3.2 基本使用
安装: npm i socket.io-client
加载:
import io from 'socket.io-client'
const socket = io('http://localhost')
3.3 总结
四、小爱同学
4.1 准备
新建user-chat/index.vue
配置路由
// 小爱同学
{
path: '/user/chat',
name: 'user-chat',
component: () => import('@/views/user-chat/')
}
4.2 布局
<template>
<div class="user-chat">
<!-- 导航栏 -->
<van-nav-bar class="page-nav-bar" title="小爱同学"> </van-nav-bar>
<!-- 消息列表 -->
<van-cell-group class="message-list">
<van-cell title="单元格"> </van-cell>
<van-cell title="单元格"> </van-cell>
<van-cell title="单元格"> </van-cell>
<van-cell title="单元格"> </van-cell>
<van-cell title="单元格"> </van-cell>
<van-cell title="单元格"> </van-cell>
<van-cell title="单元格"> </van-cell>
<van-cell title="单元格"> </van-cell>
<van-cell title="单元格"> </van-cell>
<van-cell title="单元格"> </van-cell>
<van-cell title="单元格"> </van-cell>
<van-cell title="单元格"> </van-cell>
<van-cell title="单元格"> </van-cell>
<van-cell title="单元格"> </van-cell>
<van-cell title="单元格"> </van-cell>
<van-cell title="单元格"> </van-cell>
<van-cell title="单元格"> </van-cell>
<van-cell title="单元格"> </van-cell>
</van-cell-group>
<!-- 发送消息 -->
<van-cell-group class="send-message-wrap">
<van-field v-model="message" placeholder="请输入消息" :border="false">
</van-field>
<van-button type="primary" size="small">发送</van-button>
</van-cell-group>
</div>
</template>
<script>
export default {
name: "index",
components: {},
props: {},
data() {
return {
message: ""
};
},
computed: {},
watch: {},
created() {},
mounted() {},
methods: {}
};
</script>
<style scoped lang="less">
.message-list {
position: fixed;
left: 0;
right: 0;
top: 1.3rem;
bottom: 1.2rem;
overflow-y: auto;
}
.send-message-wrap {
position: fixed;
left: 0;
right: 0;
bottom: 0;
display: flex;
padding: 0 14px;
align-items: center;
}
</style>
4.3 建立连接
created() {
// 建立连接的通讯地址
const socket = io("http://ttapi.research.itcast.cn");
this.socket = socket;
socket.on("connect", () => {
console.log(连接建立成功);
});
// 断开连接
socket.on("disconnect", () => {
console.log(断开连接);
});
},
4.4 收发消息并展示消息列表
把用户发出去的消息存储到数组中并清空输入框
this.messages.push(data);
this.message = "";
把对方发给我的消息放到数组中 this.messages.push(data);
数据在页面上展示遍历
<van-cell
:title="item.msg"
v-for="(item, index) in messages"
:key="index"
>
</van-cell>
数据存储到本地
import { getItem, setItem } from "@/utils/storage";
data初始化数据
messages: getItem("chat-message") || [] // 消息列表
监听数据是否有变化
watch: {
// 监听到改变的时候存储到本地存储
messages() {
setItem("chat-message", this.messages);
}
},
4.5 消息列表自动滚动到底部
给标签绑定一个ref属性
ref="message-list"
在监听到数据有变化时调用scrollTopBottom方法,如果要在操作数据之后立即操作数据影响的视图DOM,最好把代码放到nextTick函数中,因为数据改变影响视图更新这件事并不是立即发生的
messages() {
setItem("chat-message", this.messages);
// 如果要在操作数据之后立即操作数据影响的视图DOM
// 最好把代码放到nextTick函数中
// 数据改变影响视图更新这件事并不是立即发生的
this.$nextTick(() => {
this.scrollTopBottom();
});
}
列表滚动到询问的方法,涉及到scrollTop和scrollHeight属性
scrollTopBottom() {
// 消息列表自动滚动到底部
const list = this.$refs["message-list"];
list.scrollTop = list.scrollHeight;
}
效果如下:
功能优化补充:
1.组件缓存
1.1 介绍
从首页切换到我的,再从我的回到首页,发现首页重新渲染原来的状态没有了,首先这是正常的状态,并非问题,路由在切换时候会销毁切出去的页面组件,然后渲染匹配到的页面组件,但是我想要某些页面保持状态,而不会随着路由切换导致重新渲染。
这里可以在App.vue里用keep-alive标签包裹住路由的出口标签<router-view />,router-view实际是动态组件,但这是只对一级路由有效。但keep-alive也会带来一些问题,如:
1.部分页面缓存后再次点击进去查看内容时,内容仍是上次的,但不同的列表项是有不同的内容的,这会导致一些生命周期无法重新渲染,如created;
2.部分页面缓存后点击进去查看内容时,再回退上一级时页面会自动定位到头部位置;实际上我想要的效果是从哪个列表项点击进去查看内容的,再退回时应该显示当前列表项位置,而不是直接显示顶部位置
1.2 使用keep-alive缓存组件
1. 组件有条件缓存
若只缓存一级路由Layout组件,只需要给router-view加Inclue属性,值为数组,数组中的值是一个组件的name属性
<keep-alive>
<router-view :include="['LayoutIndex']" />
</keep-alive>
2. 控制缓存及在项目中的缓存配置
用户在使用另一个账号进行登录页面时,登录成功后仍会显示上一个用户的个人信息,只有刷新过后才会显示新的账号的个人信息。而我们又想只缓存Layout组件中的数据,新用户登录后Layout组件里的数据也是上一个用户的。
因此我们先要在App.vue里进行有 条件缓存Layout组件中的数据,并引入store/index.js的vuex文件
<template>
<div id="app">
<!-- 路由的出口,一级路由,router-view是动态组件 -->
<keep-alive>
<router-view :include="['cachePages']" />
</keep-alive>
</div>
</template>
import { mapState } from "vuex";
computed: {
...mapState(["cachePages"])
}
在store/index.js中设置添加缓存页面和移出缓存页面,state定义一个cachePages变量设为LayoutIndex组件,mutations中去修改state对象的值,addCachePage和removeCachePage方法
import Vue from 'vue'
import Vuex from 'vuex'
import { getItem, setItem } from '@/utils/storage'
Vue.use(Vuex)
const USER_KEY = 'toutiao-user'
export default new Vuex.Store({
state: {
// user: null,
// 还原为对象
// user: JSON.parse(window.localStorage.getItem('user')) // 当前登录用户的登录状态(token等数据)
// 优化为:
user: getItem(USER_KEY),
cachePages: ['LayoutIndex']
},
// 修改state对象的值
mutations: {
// 参数:state对象,data所传入的参数
setUser (state, data) {
state.user = data
// 容器中的数据不是持久化的,刷新后就不存在了
// 为了防止页面刷新数据丢失,还需要把数据存储到本地存储中,这里仅仅是为了持久化数据
// 对象state.user不能直接存入本地存储中,要转换为JSON字符串
// window.localStorage.setItem('user', JSON.stringify(state.user))
// 优化为:
console.log(data)
setItem(USER_KEY, state.user)
},
// 添加缓存页面
addCachePage (state, pageName) {
// 如果不包含缓存页面
if (!state.cachePages.includes(pageName)) {
state.cachePages.push(pageName)
}
},
// 移出缓存页面
removeCachePage (state, pageName) {
// 如果包含缓存页面
const index = state.cachePages.indexOf(pageName)
if (index !== -1) {
state.cachePages.splice(index, 1)
}
}
},
actions: {
},
modules: {}
})
在登录组件login/index.vue组件中,在用户登录成功后未跳转原来的页面之前需要清除layout组件的缓存,让它重新渲染
this.$store.commit("removeCachePage", "LayoutIndex");
但是上方清除缓存后,新用户成功登录页面后LayoutIndex组件中的数据就不能缓存了,但是与我们原先想要的功能不一样了:每个用户登录账号后LayoutIndex组件都会有缓存,所以在layout/index.vue组件的mounted生命周期里添加缓存
mounted() {
this.$store.commit("addCachePage", "LayoutIndex");
}
1.3 解决缓存带来的滚动问题
在article-list.vue中添加一个 ref="article-list"属性,页面加载mounted时记录到顶部的位置
mounted() {
const articleList = this.$refs["article-list"];
articleList.onscroll = debounce(() => {
this.scrollTop = articleList.scrollTop;
}, 50);
},
keep-alive缓存时activated是激活状态,把记录的到顶部的距离重新设置回去
activated() {
// 把记录的到顶部的距离重新设置回去
this.$refs["article-list"].scrollTop = this.scrollTop;
},
钩子函数:
2.处理token过期 ***
token用于访问需要身份认证的普通接口,有效期2个小时,超过2小时就无效了,如果继续请求数据的话会报错 401,可以让用户重新登录
3.登录成功跳转回原来的位置
用户从哪个页面进来,再登录成功之后就回到哪个页面,原先用的 this.$router.back(); 实现的,但这个方式存在一个问题,若用户第一次访问时是从tab地址栏进来的,登录成功之后会回到原先的tab地址栏,如未登录状态下,当前我是从“我的”页面点击登录的,在登录成功之后仍然会跳转到“我的”页面,不像原先登录完后直接跳转到“首页”页面上了,此时tab地址栏会有路径记录
实现:
修改utils/request.js文件
function redirectLogin () {
router.replace({
name: '/login',
// 传递查询参数,查询参数会以 ? 作为分隔符放到 url后面
query: {
// router.currentRoute 当前路由路径
// fullPath 属性
// 数据名是自己起的
redirect: router.currentRoute.fullPath
}
})
}
将原先login/index.vue中this.$router.back();替换为this.$router.push(this.$route.query.redirect || "/");
上述我是以“我的”页面为例的,所以将my/index.vue里的@click="$router.push('./login')"修改为
@click="
$router.push({
name: 'login',
query: {
redirect: '/my'
}
})
"
4.统一处理页面访问权限
需求:有的页面需要登录才能访问,有的不需要登录就可以访问
1.给每个路由配置meta属性,用requiresAuth控制页面是否需要登录才能访问,requiresAuth是自定义的属性名,后面要做路由导航守卫的配置。这里让“小爱同学”在登录状态下才能访问为例
2.配置路由导航守卫
// 路由导航守卫
// to 要访问的页面路由信息
// from 来自哪个页面的路由信息
// next 旅行的标记
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth) {
// 需要登录状态,提示用户
// 如果已登录,则直接放行
if (store.state.user) {
return next()
}
// 没有登录则提示是否登录
Dialog.confirm({
title: '访问提示',
message: '该功能需要登录才能访问,确认登录吗?'
}).then(() => {
// 确认执行这里
router.replace({
name: 'login',
query: {
redirect: router.currentRoute.fullPath
}
})
}).catch(() => {
// 取消执行这里,中断路由导航
next(false)
})
} else {
// 不需要登录则直接过去
next()
}
})
5.打包移动APP
1)下载安装
2)创建项目
3)真机调试支行
Android:
1.在手机的设置中找到并打开开发者选项
2.打开USB调试
3.使用数据线连接手机和电脑
4.在HBuildX菜单中:运行-运行到手机或模拟器-你的手机设备
备注:
笔记链接 一、课程介绍 · 语雀