目录
一、导航守卫(导航拦截、路由拦截)
文档地址: 导航守卫 | Vue Router
路由拦截(导航守卫:前置导航守卫和后置导航守卫)
前置导航守卫有三个参数
to 表示即将进入的路由
from 表示即将离开的路由
next() 表示执行进入这个路由
1.1、全局导航守卫
在router/index.js中:
// 全局导航守卫
router.beforeEach((to, from, next)=>{
// 有token就表示已经登录
// 想要进入个人中心页面,必须有登录标识token
// console.log('to:', to)
// console.log('from:', from)
if(to.path=='/user'){
let token = localStorage.getItem('x-auth-token')
// 此时必须要有token
if(token){
next(); // next()去到to所对应的路由界面
}else{
// 提示没有登录
store.dispatch("showToast/asyncChanIsShowToast",{
msg: "你还没有登录!",
type: "danger",
})
}
return; // 需要些return,防止执行完上面的next(),还继续执行下面的next()
}
// 如果不是去往个人中心的路由,则直接通过守卫,去到to所对应的路由界面
next()
})
export default router
1.2、组件内部导航守卫
全局导航守卫是每一次改变路由都会触发的,然而,目前我们只需要到个人中心页面才触发,所以我们在组件内部书写导航守卫:
User.vue组件中
import store from "@/store"
...
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
let token = localStorage.getItem("x-auth-token");
if(token){
next()
}else{
// 提示没有登录
store.dispatch("toastStatus/asyncChanIsShowToast",{
msg:"请先登录",
type:"warning"
})
}
},
二、个人中心--购物车页面
2.1、个人中心左侧结构样式套用
把今天其他文件夹中的User.vue组件替换到项目views目录中
处理头像和用户名的展示
import {mapState} from "vuex";
...
computed:{
...mapState({
userInfo:state=>state.userInfo.userInfo
})
},
2.2、重定向到购物车(二级路由)
备注:二级路由的两种表示方式:(在以及路由的基础上)
path:'cart',
path:'/user/profiles',
<!-- 二级路由的出口 -->
<article><router-view></router-view></article>
需求:一进入到个人中心页面,就到购物车界面,
分析:购物车是个人中心众多页面下的一个,我们会把它做成子路由
先准备购物车组件:在components下新建user文件夹,新建cart.vue组件
router/index.js下配置重定向到 /user 的子路由:
{
path: '/user',
name: 'User',
component: () => import('../views/UserView.vue'),
// 二级路由的两种表示方式
children:[
{
path:'cart',
component:()=>import('../components/user/CartBar.vue')
},
{
path:'/user/profiles',
component:()=>import('../components/user/ProfileBar.vue')
},
]
},
套用购物车组件:直接把今天其他文件夹中的user文件夹替换到项目components目录中
2.3、购物车数据请求及渲染
api.js中:
// 请求购物车数据
export const CartDataApi = () => request.get(`/shop/carts`);
cart.vue组件中:
<td>
<section>
<img
width="84"
:src="imgBaseUrl+item.coverImg"
alt="列表图片"
/>
<div class="info">
<h5>{{item.title}}</h5>
<p>{{item.versionDescription}}</p>
</div>
</section>
</td>
<td>{{item.coin}}鸡腿</td>
<td>
<div class="step">
<span>-</span>
<input type="text" disabled v-model="item.total" />
<span>+</span>
</div>
</td>
<td>{{item.coin * item.total}}鸡腿</td>
<td>
<span class="del">删除</span>
</td>
<script>
import {CartDataApi} from "@/request/api";
export default {
data() {
return {
...
// 购物车数组
cartArr:[]
};
},
created(){
CartDataApi().then(res=>{
if(res.code===0){
this.cartArr = res.data
}
})
}
};
</script>
三、404处理
新建Error.vue组件:写入提示信息
在路由表中配置剩余地址对应Error组件:
,
{
path: '*',
name: 'Error',
component: () => import('../views/Error.vue')
}
四、全部商品中,滚动到底部加载更多
Goods.vue中:
<List :arr="goodsListShow" :maxLength="28" />
<p style="text-align:center;margin-top:20px">
{{isReallyBottom?"没有数据了":"正在加载..."}}
</p>
4.1、工具函数的使用
把三个函数(带兼容性的写法)写在utils文件夹的index.js作为工具函数引用: 用export暴露
在要使用的页面借助import引入,使用时在对应名字后加上()
import { getScrollTop, getClientHeight, getScrollHeight } from "../utils";
// if(窗口高度+超出窗口的页面高度>=页面高度-20)
if (getClientHeight() + getScrollTop() >= getScrollHeight() - 20) {。。。}
utils文件夹的index.js中(export暴露)
//获取滚动条当前的位置
function getScrollTop() {
var scrollTop = 0;
if(document.documentElement && document.documentElement.scrollTop) {
scrollTop = document.documentElement.scrollTop;
} else if(document.body) {
scrollTop = document.body.scrollTop;
}
return scrollTop;
}
//获取当前可视范围的高度
function getClientHeight() {
var clientHeight = 0;
if(document.body.clientHeight && document.documentElement.clientHeight) {
clientHeight = Math.min(document.body.clientHeight, document.documentElement.clientHeight);
} else {
clientHeight = Math.max(document.body.clientHeight, document.documentElement.clientHeight);
}
return clientHeight;
}
//获取文档完整的高度
function getScrollHeight() {
return Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
}
4.2、"节流"解决短时间内重复触发的问题
节流:利用一个变量,控制代码在一段时间内(setTimeout)不会重复触发执行
在此处的应用:商品列表滚动的监听。
一页8条数据,随着页面的滚动加载后面的数据,借助setTimeout设置后面页面数据出现频率
Goods.vue中:
//加载更多...(第一页:展示8条数据【下标0~7】)
//第1次滚动到底部,要加载第2页的数据,i是从下标为8~15
//第2次滚动到底部,要加载第3页的数据,i是从下标为16~23......
<script>
import {getScrollTop,getClientHeight,getScrollHeight} from "@/utils"
...
data(){
return{
// 产品列表
goodsList: [],
// 用来展示的产品列表
goodsListShow: [],
// 默认展示第一页
page:1,
// 每页8条
size:8,
// false 表示没有正在加载
isLoading: false,
//是否已经到低
isReachBottom:false,
}
},
...
getGoodList() {
GoodListAPI({
...
}).then((res) => {
...
// 在请求到数据之后,先展示前8条
this.goodsListShow=this.goodsList.filter((item,index)=>index<8)
});
},
scrollFn() {
// 滚动就执行这里的代码频繁触发事件
console.log(getClientHeight() + getScrollTop() == getScrollHeight() + 1);
// console.log("页面正在滚动");
// 如果滚动到底部的时候,
// if (到底部了) {
// if (窗口高度+scrollTop>=页面文档高度-20) {
if (getClientHeight() + getScrollTop() >= getScrollHeight() - 20) {
// 需要this.isLoading为false才能进行加载
if (!this.isLoading) {
// this.isLoading避免了重复触发这个到底了加载数据事件
this.page++;
this.isLoading = true;
setTimeout(() => {
// 往goodsListShow这个数组去push下一页的数据
// 从goodsList数组中去 this.page页的数据 push到goodsListShow
for (var i = this.size * (this.page - 1);i < this.size * this.page;i++) {
//this.goodsList[i]必须有这个值,才能push到展示的数组里面去
this.goodsList[i]
? this.goodsListShow.push(this.goodsList[i])
: "";
}
this.isLoading = false;
}, 500);
}
}
},
},
mounted() {
// 监听滚动
window.addEventListener("scroll", this.scrollFn);
},
beforeDestroy() {
// 取消监听
window.removeEventListener("scroll", this.scrollFn);
},
</script>
4.3、是否已经到底部
结构完善:isReachBottom
<p style="text-align: center; margin-top: 20px">
{{ isReachBottom ? "已经没有数据了" : "正在加载... ..." }}
</p>
<script>
...
// 定义是不是已经没有数据了
isReachBottom: false,
...
if (getClientHeight() + getScrollTop() >= getScrollHeight() - 20) {
if (this.goodsListShow.length >= this.goodsList.length) {
// 没有数据了
this.isReachBottom = true;
return;
}
</script>
最后解决选项切换时候的bug
async goodsSearch(){
... ...
// 初始化数据
this.isReachBottom = false;
this.page = 1;
// 判断是不是已经没有数据了
if (this.goodsListShow.length >= this.goodsList.length) {
// 每次请求到数据,把数据把页数和是否到底部初始化一下
this.isReachBottom = true;
}
}
五、跨域配置
我们对 vue.config.js
进行配置:
module.exports = {
devServer: {
proxy: {
'/api': {
target: "http://kumanxuan1.f3322.net:8881/cms",
pathRewrite: {
'^/api': ''
}
}
}
}
}
request.js中:
const instance = axios.create({
baseURL: "/api",
timeout: 5000
})
记得配置完需要重启服务器!!
六、项目环境变量配置
项目目录下新建两个文件,分别是开发环境和生产环境下的两个不同配置
.env.dev
NODE_ENV=development
VUE_APP_BASE_URL=http://192.168.113.249:8081/cms
VUE_APP_STATE_URL=http://127.0.0.1:8080
.env.prod
NODE_ENV = production
VUE_APP_BASE_URL = http://kumanxuan1.f3322.net:8881/cms
VUE_APP_STATE_URL="后端给的地址"
在package.json中修改启动命令:
"serve": "vue-cli-service serve --open --mode dev",
"servepro": "vue-cli-service serve --open --mode prod",
在vue.config.js中换成:
'/api': {
target: process.env.VUE_APP_BASE_URL,
pathRewrite: {
'^/api': ''
}
}
七、项目总结
7.1、项目介绍
这是一个由vue-cli搭建的PC端SPA商城,该商城主要涉及登录注册、商品列表、商品详情、个人中心、购物车及商品检索等主体功能。
7.2、项目技术点
-
使用vue-cli搭建项目,并结合蓝湖+PS进行页面切图,实现对设计稿的高保真还原;
-
使用axios进行数据请求,并对其进行请求拦截器响应拦截器封装;
-
封装所有请求的api,统一管理项目所有的请求路径
-
鉴权,认证机制采用手机+验证码、手机+密码及微信扫码登录认证,其中微信扫码登录结合环境变量,调用后端接口实现平台切换验证;
-
使用localStorage对token进行存储;
-
使用原生JS在组件mounted中监听滚动,并实现向下滚动加载更多;
-
使用组内导航守卫对每个进入个人中心页的路由进行拦截,判断路径后保证有token方能进入该路由;
-
使用路由监听解决路由跳转而页面不跳转的问题;
-
给组件绑定key属性,通过修改key值来进行组件重载