关于VueRouter导入的全过程
目录
- router
- nanoid的使用
- 引入
- 使用
- 路由
- 1-1 安装依赖
- 1-2 引入
- 1-3 在main.js中使用
- 1-4 App.vue
- 全局过滤器
- element-ui
- 全局组件
- Vuc-cli中的视配
- 1-1 安装依赖
- 1-2 配置文件
- 1-3 main.js
- 1-4 public/index.html
- 1-5 在pc端视配
- slot封装动画
- 项目初始化
- 1-1 .router-link-active
- 1-2 动态显示tabbar
- 1-3 跳转回前一个页面
- 1-4轮播
- vant ui的按需导入
- 1-1 安装依赖
- 1-2 配置babel.config.js
- 1-3 配置main.js
- 嵌套路由
- 1-1 router.js
- 1-2 index.vue
- 异步路由
- 页面跳转和生命周期
- 页面跳转
- DOM和生命周期
- 缓存的进一步封装
- axios
- 跨域
- 地址值的放置
- Vuex
- 1、state
- 2、mutation
- 3、actions
- keep-alive
- 路由守卫
- Login_guard(Vue&koa)
- 一、登录页面
- 二、后台代码
- 三、配置cookie跨域访问
- 懒加载
- 1、图片懒加载
- 2、axios拦截器
- 3、上拉刷新
- 4、路由滚动记录当前滚动条位置问题
- Vue路由跳转的bug
router
nanoid的使用
--生成随机id
引入
yarn add nanoid
使用
import {nanoid} from 'nanoid' var id = nanoid()
路由
1-1 安装依赖
yarn add vue-router
1-2 引入
router/index.js
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) import Movie from '../pages/Movie/index.vue' import Music from '../pages/Music/index.vue' const routes = [ { path:"/music", component:Music }, { path:"/movie", component:Movie } ] const router = new VueRouter({ routes, mode:"history" }) export default router;
1-3 在main.js中使用
import Vue from 'vue' import App from './App.vue' import router from './router' Vue.config.productionTip = false new Vue({ router, render: h => h(App), }).$mount('#app')
1-4 App.vue
<template> <div> <router-view></router-view> </div> </template>
全局过滤器
在main.js中挂载在Vue原型上
Vue.filter("handleStr",function(val){ if(val.length > 3){ val = val.slice(0,3) + '...' } return val })
element-ui
安装依赖
yarn add element-ui
main.js
.... import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI); ...
全局组件
import Loading from '../components/Loading.vue' Vue.component("Loading",Loading)
Vuc-cli中的视配
只在手机端
lib-flexible 阿里
1-1 安装依赖
yarn add lib-flexible postcss-pxtorem@5.1.1
1-2 配置文件
新建postcss.config.js
module.exports = { plugins: { 'postcss-pxtorem': { rootValue: 75, propList: ['*'], }, }, };
1-3 main.js
导入lib-flexible
import 'lib-flexible/flexible.js'
1-4 public/index.html
将此行注释,关闭视口
<meta name="viewport" content="width=device-width,initial-scale=1.0">
1-5 在pc端视配
<template> <div id="app"> ... </div> </template>
<script> ... </script>
<style> *{ margin: 0; padding: 0; } #app{ width: 10rem; margin: 0 auto; background-color: red; } </style>
slot封装动画
// #1 定义一个组件 <template> <transition> <slot name="fade"></slot> </transition> </template>
<script> export default { } </script>
<style> .v-enter,.v-leave-to{ opacity: 0; } .v-enter-active,.v-leave-active{ transition: opacity 4s; } </style>
// #2 使用 <template> <div class="about"> <Fade> <h1 slot="fade" v-show="isShow">This is an about page</h1> </Fade> </div> </template>
<script> import Fade from '../components/Fade.vue' export default { data() { return { isShow:true } }, components:{ Fade } } </script>
项目初始化
1、rem 2、asssreset.css
1-1 .router-link-active
被选中的路由样式
.router-link-active{ color: #ff2d51; }
1-2 动态显示tabbar
– 在路由配置中增加一条meta属性
const routes = [ { path: '/films', name: 'Films', component:Films, meta:{ isNav:true } }, { path: '/article', name: 'Article', component:Article, meta:{ isNav:true } }, { path: '/center', name: 'Center', component:Center, meta:{ isNav:true } }, { path:"/movie/:id", name:'MovieDetail', component:MovieDetail } ]
通过v-if动态显示
<tab-bar v-if="this.$route.meta.isNav"></tab-bar>
1-3 跳转回前一个页面
this.$router.back()
1-4轮播
yarn add vue-preview
import VuePreview from 'vue-preview' Vue.use(VuePreview)
vant ui的按需导入
1-1 安装依赖
yarn add vant babel-plugin-import
1-2 配置babel.config.js
module.exports = { presets: [ '@vue/cli-plugin-babel/preset' ], plugins: [ ["import", { "libraryName": "vant", "libraryDirectory": "es", "style": true }] ] }
1-3 配置main.js
import {Button} from 'vant' Vue.use(Button)
router-view实现动画
<template> <div> <div class="cover" v-if="isShow"></div> <transition @before-enter="handleBeforeEnter" @enter="handleEnter" > <slot></slot> </transition> </div> </template>
<script> /* .v-enter @before-enter .v-ernter-active @enter .v-enter-to @after-enter */ export default { data() { return { isShow:false } }, methods:{ handleBeforeEnter(){ this.isShow = true }, handleEnter(){ setTimeout(() => { this.isShow = false }, 200); } } } </script>
<style> .v-enter-active{ animation: animate 2s linear; } @keyframes animate { 0%{ opacity: 0; transform: translateY(0px); } 50%{ opacity: .5; transform: translateY(20px); } 100%{ opacity: 1; transform: translateY(0px); } } .cover{ width: 100%; height: 100%; background-color: #fff; position: fixed; z-index: 10; } </style>
嵌套路由
1-1 router.js
{ path: '/films', name: 'Films', component:Films, meta:{ isNav:true }, children:[ { path:"nowPlaying", component:NowPlaying } ] },
1-2 index.vue
需要加入router-view
<template> ... <div class="container"> <!-- 装载父路由下的子路由的对应 --> <router-view></router-view> </div> </template>
异步路由
--又称路由懒加载
{ path: '/about', name: 'About', // 异步路由 component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') },
怎么减少首屏渲染时间
1、使用异步路由
页面跳转和生命周期
--面试常客
页面跳转
1-1 A页面初次加载
beforeCreate(){ console.log('beforeCreate'); }, created(){ console.log('created'); }, beforeMount(){ console.log('beforeMount'); }, mounted(){ console.log('mounted'); },
1-2 A->B
从A页面跳转到B页面
A页面触发以下生命周期
beforeDestroy(){ console.log('beforeDestroy'); }, destroyed(){ console.log('destroyed'); }
1-3 B–>A
从B页面回到A页面
A页面触发以下生命周期
beforeCreate(){ console.log('beforeCreate'); }, created(){ console.log('created'); }, beforeMount(){ console.log('beforeMount'); }, mounted(){ console.log('mounted'); },
upDate
beforeUpdate , beforeUpdate执行需要满足以下两个条件
1、data中的数据更新的时候
2、模板中要使用data中的数据
destroyed
# A页面 --> b页面
b页面执行以下生命周期:
- 1.beforeCreate B
- 2.created B
- 3.beforeMount B
- 4.beforeDestroy A
- 5.destroyed A
- 6.mounted B
DOM和生命周期
只能在mounted生命周期中获取DOM
缓存的进一步封装
localStorage.setItem() 函数会将对象或者数组全部转换成字符串的形式
所以可以对缓存进行判断,使用 JSON.stringify 和 JSON.parse 分别处理数据
const setLocalStorage = (key , value) => { if(value instanceof Array || value instanceof Object){ value = JSON.stringify(value) } localStorage.setItem(key , value) } const getLocalStorage = (key) =>{ var val = localStorage.getItem(key) var reg = /^[[{].*[\]}]/ if(reg.test(val)){ val = JSON.parse(val) } return val }
axios
跨域
安装依赖
yarn add axios-jsonp
axios格式
import axios from 'axios' import jsonpAdapter from 'axios-jsonp' axios({ url:"", adapter: jsonpAdapter, }).then( res => console.log(res) )
腾讯地图api需要在最后加上 &output=jsonp
https://apis.map.qq.com/ws/location/v1/ip?key=L6UBZ-JSLCU-FRAVA-4DBQG-V5WC5-2RBJ4&output=jsonp
地址值的放置
http://47.108.197.28:3000/top/playlist?limit=1&offset=1
在小程序中
wx.request({ })
axios中
import axios from 'axios' axios({ url:"http://47.108.197.28:3000/top/playlist", method:"get", params:{ limit:1 } }).then(res=>{ console.log(res) })
Vuex
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { }, mutations: { }, actions: { }, modules: { } })
1、state
--state中存放的是数据
state: { num:10 }
this.$store.state
2、mutation
--mutation中的函数第一个默认参数是state
--连接方式 commit
mutations: { add(state){ console.log(state); state.num++; } },
this.$store.commit('add')
3、actions
--actions中的函数第一个默认值是上下文
--连接方式 dispatch
actions: { addNum(ctx){ console.log(ctx); ctx.commit("add") } },
this.$store.dispatch('a')
keep-alive
- 使用keep-alive之后,路由切换的时候,生命周期函数不会重复的触发
- 组件不会被销毁,而是被缓存起来。当加载到对应的路由页面,缓存的组件会被加载
路由组件的两个生命周期函数
/* 路由组件被激活时触发。 */ activated(){ console.log('activated') } /* 路由组件失活时触发。 */ deactivated(){ console.log('deactivated') }
路由守卫
全局路由守卫
router.beforeEach((to , from ,next)=>{ console.log(to); // 要跳转的路由 console.log(from); // 起点路由 next(); })
Login_guard(Vue&koa)
一、登录页面
<template> <div> <el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm" > <!-- props为了规则校验 rules --> <el-form-item label="用户名" prop="username"> <el-input type="text" v-model.number="ruleForm.username"></el-input> </el-form-item> <el-form-item label="密码" prop="pass"> <el-input type="password" v-model="ruleForm.pass" autocomplete="off" ></el-input> </el-form-item> <el-form-item label="确认密码" prop="checkPass"> <el-input type="password" v-model="ruleForm.checkPass" autocomplete="off" ></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="submitForm('ruleForm')" >提交</el-button > <el-button @click="resetForm('ruleForm')">重置</el-button> </el-form-item> </el-form> </div> </template> <script> export default { // beforeRouteEnter () { // /* 在局部守卫中获取不到this */ // console.log(1); // }, data() { var checkUsername = (rule, value, callback) => { if (!value) { return callback(new Error("用户名不能为空")); }else{ callback(); } }; var validatePass = (rule, value, callback) => { if (value === "") { callback(new Error("请输入密码")); } else { if (this.ruleForm.checkPass !== "") { this.$refs.ruleForm.validateField("checkPass"); } callback(); } }; var validatePass2 = (rule, value, callback) => { if (value === "") { callback(new Error("请再次输入密码")); } else if (value !== this.ruleForm.pass) { callback(new Error("两次输入密码不一致!")); } else { callback(); } }; return { ruleForm: { pass: "", checkPass: "", username: "", }, rules: { pass: [{ validator: validatePass, trigger: "blur" }], checkPass: [{ validator: validatePass2, trigger: "blur" }], username: [{ validator: checkUsername, trigger: "blur" }], }, }; }, methods: { submitForm(formName) { this.$refs[formName].validate((valid) => { if (valid) { // alert("submit!"); // console.log(this.ruleForm); // console.log(username,pass); /* 发送http请求 */ this.loginHttp() } else { console.log("error submit!!"); return false; } }); }, resetForm(formName) { this.$refs[formName].resetFields(); }, loginHttp(){ var {username , pass} = this.ruleForm this.$http({ method: 'post', url:'http://localhost:8000/login', data:{ username, pass } }).then(res=>{ console.log(res.data); if(res.data.code == 200){ this.$message({ message:res.data.msg, type:"success", duration:1000 }) this.$router.push('/home') }else{ this.$message({ message:res.data.msg, type:"warning", duration:1000 }) } }) } } }; </script>
二、后台代码
const koa = require("koa"); const app = new koa(); const koaBody = require("koa-body"); const router = require("koa-router")(); const cors = require("koa2-cors"); /* username=cheng pass=123456 */ console.log(ctx.request.body); var {username , pass } = ctx.request.body if(username == "cheng" && pass == "123456"){ ctx.cookies.set("loginAuth",true,{ /* httpOnly:false 设置前端可读 */ httpOnly:false }) ctx.body = { code:200, msg:"登录成功" } }else{ ctx.body = { code:400, msg:"登录失败,用户名或密码错误" } } }) /* 后端配置cookie可以实现跨域访问 */ app.use(cors({ origin:ctx =>{ return ctx.headers.origin }, credentials:true })); app.use(koaBody()); app.use(router.routes()); app.listen(8000);
三、配置cookie跨域访问
3-1 配置后端cookie可以访问
/* 后端配置cookie可以实现跨域访问 */ app.use(cors({ origin:ctx =>{ return ctx.headers.origin }, credentials:true }));
3-2 配置前端跨域访问cookie
import axios from 'axios' /* 设置前端跨域访问cookie */ axios.defaults.withCredentials = true axios.defaults.crossDomain = true
3-3 vue上获取cookie
安装依赖
yarn add vue-cookie
配置main.js
import VueCookie from 'vue-cookie' Vue.use(VueCookie)
在页面中获取cookie
mounted() { console.log(this.$cookie.get('loginAuth')); }
3-4 路由守卫
--没有登录的情况下,不能进入其它页面
--已经登录的情况下,直接进入首页
var vm = new Vue(); router.beforeEach((to,from,next)=>{ console.log(vm.$cookie.get('loginAuth')); var isLogin = vm.$cookie.get('loginAuth') if(to.path == "/login"){ /* 1、登录页面,如果cookie显示登录了直接进入home页面,如果没有登录,正产执行login页的逻辑 */ if(isLogin){ router.push('/home') }else{ next() } }else{ /* 2、在其他页面,如果登录正常显示,没有登录则停留在login页面 */ if(isLogin){ next() }else{ router.push('/login') } } })
懒加载
1、图片懒加载
安装依赖
yarn add vue-lazyload
在main.js中进行配置
import VueLazyLoad from 'vue-lazyload' Vue.use(VueLazyLoad,{ preLoad:1.3, loading:require('@/assets/loading.gif') })
使用(将:src替换成v-lazy)
<template> <div class="home"> <div v-for="item of playlists" :key="item.id"> <img class="item" v-lazy="item.coverImgUrl" alt=""> <p>{{item.name}}</p> </div> </div> </template>
2、axios拦截器
--实现loading的加载效果
在vuex中定义一条isShowLoading --> 设置加载条是否显示
export default new Vuex.Store({ state: { isSowLoading:true }, })
main.js
// 配置 请求拦截 和 响应拦截 // 添加请求拦截器 axios.interceptors.request.use(function (config) { // 在发送请求之前做些什么 store.state.isSowLoading = true return config; }); // 添加响应拦截器 axios.interceptors.response.use(function (response) { // 2xx 范围内的状态码都会触发该函数。 // 对响应数据做点什么 store.state.isSowLoading = false return response; });
App.vue设置loading
<template> <div id="app"> <Loading v-if="this.$store.state.isShowLoading"/> <router-view/> </div> </template>
3、上拉刷新
vant-ui 中整合了小程序中的 onBottom 和 onLoad
<template> <div class="home"> <van-list class="home" v-model="loading" :finished="finished" finished-text="没有更多了" @load="onLoad" > <van-cell @click="handleClick(item.id)" v-for="item of playlists" :key="item.id"> <img class="item" v-lazy="item.coverImgUrl" alt=""> <p>{{item.name}}</p> </van-cell> </van-list> </div> </template>
<script> export default { name: 'Home', data() { return { playlists:[], loading:false, finished:false } }, mounted() { }, methods: { onLoad(){ setTimeout(()=>{ var offset = this.playlists.length console.log(1); this.axios.get(`http://47.108.197.28:3000/top/playlist?offset=${offset}&limit=20`).then(res =>{ var playlists = this.playlists.concat(res.data.playlists) this.playlists = playlists this.loading = false }) },500) }, handleClick(id){ this.$router.push(`detail?id=${id}`) } }, } </script>
<style scoped> .item{ width: 150px; height: 150px; } .home{ display: flex; justify-content: space-between; flex-wrap: wrap; } .van-cell{ width: 150px; } .home >>> .van-list__loading{ position: fixed; bottom: 0; left: 50%; transform:translateX(-50%); } </style>
4、路由滚动记录当前滚动条位置问题
在路由设置中,重置滚动条的x,y
const router = new VueRouter({ ... scrollBehavior (to , from , savedPosition) { if( to.path == "/detail"){ return {x:0,y:0} // 让页面出于顶部 }else{ return savedPosition // 让页面出于记录点 } } })
Vue路由跳转的bug
项目中遇到如下报错内容:
Uncaught (in promise) Error: Redirected when going from “/XXX” to “/XXX” via a navigation guard.
原因:vue-路由版本更新产生的问题,导致路由跳转失败抛出该错误,但并不影响程序功能
在main.js中改变push原型
import Router from 'vue-router' const originalPush = Router.prototype.push Router.prototype.push = function push(location, onResolve, onReject) { if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject) return originalPush.call(this, location).catch(err => err) }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。