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、图片懒加载
1、安装依赖
yarn add vue-lazyload
2、在main.js中进行配置
import VueLazyLoad from 'vue-lazyload'
Vue.use(VueLazyLoad,{
preLoad:1.3,
loading:require('@/assets/loading.gif')
})
3、使用(将: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的加载效果
1、在vuex中定义一条isShowLoading --> 设置加载条是否显示
export default new Vuex.Store({
state: {
isSowLoading:true
},
})
2、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;
});
3、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)
}