网站首页与布局
网站首页分为 3 个部分,顶部导航栏,底部网站信息栏,正文部分。
要求一:不同导航菜单底部网站信息栏显示与隐藏。
解决方法:路由元信息 meta 字段
新增路由元信息 meta 字段 show
{
path: '/',
name: 'Home',
component: Home,
meta:{show:true} // 添加路由元信息meta 字段
},
{
path: '/register',
name: 'Register',
component: Register,
meta:{show:false}
},
App.vue,在底部网站信息栏 通过 v-show="$route.meta.show" 控制。
<template>
<div id="app">
<Header />
<router-view />
<Footer v-show="$route.meta.show" />
</div>
</template>
要求二:点击商品分类跳转到分类页
这个组件采用的是 element ui 的导航。
商品分类组件 api => vuex => 组件中派发action => 从仓库中取数据
1、请求api
export const reqCategoryList = ()=> {
return requests.get('/product/getBaseCategoryList')
}
2、vuex 中请求(因为需要存储和公用的数据太多,因此在大仓库中建立了小仓库)
在大仓库中建立了小仓库
home 小仓库中写请求
// home 小仓库
import {reqCategoryList} from '@/api'
export default {
state: {categoryList:[], },
mutations: {
CATEGORYLIST(state,categoryList){ state.categoryList = categoryList}
},
actions: {
async categoryList({commit}){
let res = await reqCategoryList() // 因为 axios 返回的是一个promise 所以这里用 await 这样返回的结果就是对象
if (res.status == 200) { commit("CATEGORYLIST",res.data.data)}
}
},
3、根组件中派发 action,在根组件APP.vue 执行一次是为了减少请求次数。
mounted(){
this.$store.dispatch("categoryList"); // 获取三级分类 typeNav
}
4、获取到的数据在仓库,然后在组件中使用。
computed: {
...mapState({
categoryList: (state) => {
return state.home.categoryList;
},
}),
},
点击每个分类会跳转到该分类的商品页
1、不能使用声明式导航,roter-link 相当于VueComponent类的实例对象,一瞬间new VueComponent很多实例(1000+),很消耗内存,因此导致卡顿会出现卡顿(分类较多)
2、不能直接使用编程式导航,原因每一个导航都有一个回调函数,太多了
解决:在父节点上使用 事件委派 + 编程式导航
其他常见问题
描述: 编程式路由跳转到当前路由(参数不变), 会抛出NavigationDuplicated的警告错误
vue-router3.1.0之后, 引入了push()的promise的语法, 如果没有通过参数指定回调函数就返回一个promise来指定成功/失败的回调, 且内部会判断如果要跳转的路径和参数都没有变化, 会抛出一个失败的promise
解决1: 在跳转时指定成功或失败的回调函数, 通过catch处理错误
// catch()处理错误
this.$router.push(`/search/${this.keyword}`).catch(() => {})
// 指定成功的回调函数
this.$router.push(`/search/${this.keyword}`, () => {})
// 指定失败的回调函数
this.$router.push(`/search/${this.keyword}`, undefined, () => {})
解决2: 修正Vue原型上的push和replace方法
let originreplace = VueRouter.prototype.replace
VueRouter.prototype.replace = function(location,resolve,reject){
if(resolve && reject){
originreplace.call(this,location,resolve,reject)
}else{
originreplace.call(this,location,()=>{},()=>{})
}
}
如何指定params参数可传可不传?
path: '/search/:keyword?'
指定params参数时可不可以用path和params配置的组合?
不可以,用path和params配置的组合, 只能用name和params配置的组合query配置可以与path或name进行组合使用。
如果指定name与params配置, 但params中数据是一个"", 无法跳转
解决1: 不指定params
解决2: 指定params参数值为undefined
路由组件能不能传递props数据?
可以: 可以将query或且params参数映射/转换成props传递给路由组件对象
props: (route)=>({keyword1:route.params.keyword, keyword2: route.query.keyword })
params参数:路由需要占位,程序就崩了,属于URL当中一部分
query参数:路由不需要占位,写法类似于ajax当中query参数
导航守卫是什么?
导航守卫是vue-router提供的下面2个方面的功能:
a. 监视路由跳转 -->回调函数
b. 控制路由跳转
应用:
a. 在跳转到界面前, 进行用户权限检查限制(如是否已登陆)
b. 在界面离开前, 做收尾工作
导航守卫分类
全局守卫: 针对任意路由跳转;
a. 全局前置守卫;在准备跳转到某个路由组件之前 (在开发中用的比较多)
router.beforeEach((to, from, next) => {// before enter each route component
})
to: 目标route
from: 起始route
next: 函数
next(): 执行下一个守卫回调, 如果没有跳转到目标路由
next(false)/不执行: 跳转流程在当前处中断, 不会跳转到目标路由组件
next(path): 跳转到指定的另一个路由
b. 全局后置守卫;在跳转到某个路由组件之后
router.afterEach((to, from) => {
})
路由独享的守卫:前置守卫
beforeEnter: (to, from, next) => {
}
组件守卫: 只针对当前组件的路由跳转;a. 进入 b. 更新 c. 离开
在当前组件对象被创建前调用, 不能直接访问this(不是组件对象)。但可以通过next(component => {}), 在回调函数中访问组件对象;
beforeRouteEnter (to, from, next) {
next(component => {})
},
当前组件对象将要更新前调用, 可以访问this
beforeRouteUpdate (to, from, next) {
},
在当前组件离开前调用, 可以访问this
beforeRouteLeave (to, from, next) {
next()
}
只有登录了,才可以才能查看交易/支付/个人中心界面
// 所有需要检查登陆的路由路径的数组
const checkPaths = ['/trade', '/pay', '/center']
/* 注册全局前置拦截器 */
router.beforeEach((to, from, next) => {
const targetPath = to.path
if (checkPaths.some(path => targetPath.indexOf(path)===0)) {
if (!store.state.user.userInfo.token) {
return next(`/login?redirect=${targetPath}`)
}
}
next()
})
只有没有登陆,才能查看登陆界面
path: '/login',
component: Login,
beforeEnter(to, from, next) {
if (store.state.user.userInfo.token) {// 如果已登陆, 直播跳转到首页
next('/')
} else {
next()
}
}
}
只有携带的skuNum以及sessionStorage中有skuInfo数据, 才能查看添加购物车成功的界面
{
path: '/addcartsuccess',
component: AddCartSuccess,
props: route => route.query,
beforeEnter: (to, from, next) => {
const {skuId, skuNum} = to.query
const skuInfo = JSON.parse(window.sessionStorage.getItem('SKU_INFO'))
if (skuNum>0 && skuInfo && skuInfo.id) {
next()
} else {
next('/')
}
}
},
只能从购物车界面, 才能跳转到交易界面
{
path: '/trade',
component: Trade,
beforeEnter: (to, from, next) => {
if (from.path!=='/shopcart') {
next({path: '/shopcart'})
} else {
next()
}
}
}
只能从交易界面, 才能跳转到支付界面
{
path: '/pay',
component: Pay,
beforeEnter: (to, from, next) => {
if (from.path!=='/trade') {
next({path: '/trade'})
} else {
next()
}
}
},
只有从支付界面, 才能跳转到支付成功的界面
export default {
name: 'PaySuccess',
beforeRouteEnter: (to, from, next) => {
if (from.path!=='/pay') {
next({path: '/pay'})
} else {
next()
}
}
}
图片懒加载
1. 图片懒加载特点说明
(1) 还没有加载得到目标图片时, 先显示loading图片
(2) 在<img>进入可视范围才加载请求目标图片
2. 下载依赖
npm install vue-lazyload
3.引入并配置loading图片
import VueLazyload from 'vue-lazyload'
import loading from '@/assets/images/loading.gif'
// 在图片界面没有进入到可视范围前不加载, 在没有得到图片前先显示loading图片
Vue.use(VueLazyload, { // 内部自定义了一个指令lazy
loading, // 指定未加载得到图片之前的loading图片
})
4.对异步获取的图片实现懒加载
<img v-lazy="goods.defaultImg" />
路由懒加载
当打包构建应用时,JS包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了,本质就是Vue 的异步组件在路由组件上的应用,需要使用动态import语法, 也就是import()函数
const Home = () => import('@/pages/Home')
const Search = () => import('@/pages/Search')
const Detail = () => import('@/pages/Detail')
组件通信
组件通信方式1:props
场景:父子通信
父亲给子组件传递数据:
父组件将需要传递的数据通过属性传值的方式(key:{xxxx})的方式传递给子组件,子组件 通过this.props.key的方式获取参数。
props 的三种书写方法:['arry'] { type:arry } { type:arry, default: [ ] }
如果父组件给子组件传递数据是一个函数,那么它的本质其实是子组件给父组件传递数据
特殊情况:路由传递props
1:布尔值类型,把路由中params参数映射为组件props数据
2:对象,静态数据,很少用
3:函数,可以把路由中params|query参数映射为组件props数据
组件通信方式2:自定义事件 $on \ $emit
场景:子父通信
父:
<Son @parclick="handler"></Son>
子:
<button @click="$emit('parclick', '传递给父的参数')"></button>
组件通信方式3:全局事件总线 $bus
组件实例的原型的原型指向的Vue.prototype
Vue.prototype.$bus = this
组件通信方式4:vuex
组件通信方式:slot
场景:父子通信
默认插槽
具名插槽
作用域插槽
组件通信方式5:$listeners与$attrs
他们两者是组件实例的属性,可以获取到父组件给子组件传递props与自定义事件。
// parent.vue
<m-child :data-status="dataStatus" :data-message="dataMessage"></m-child>
export default {
data(){
return {
dataStatus: '123',
dataMessage: '456'
}
}
}
// m-child.vue
<m-grandron v-bind="$attrs"></m-grandron>
export default {
props:{
dataStatus: String,
},
data(){
return {}
},
created(){
console.log('dataStatus',this.dataStatus) // 123
console.log('dataMessage',this.dataStatus) // 456
}
}
// m-grandron.vue
export default {
data(){
return {}
},
created(){
console.log('dataStatus',this.$attrs) // {dataMessage:456} 因为child组件中props声名了dataStatus所以这里$attrs就不含有dataStatus
}
}
// parent.vue
<m-child @customEvent="ev_customEvent"></m-child>
export default {
data(){
return {
dataStatus: '123',
dataMessage: '456'
}
},
methods:{
ev_customEvent(){
console.log('my name is parent!')
}
}
}
// m-child.vue
<m-grandron v-on="$listeners" @customEvent="ev_customEvent"></m-grandron>
export default {
props:{
dataStatus: String,
},
data(){
return {}
},
methods:{
ev_customEvent(){
console.log('my name is child!')
}
}
}
// m-grandron.vue
<button @click="$emit('customEvent')">click me!</button>
export default {
data(){
return {}
},
}
// 控制台
// my name is child!
// my name is parent!
事件
1:原生DoM----button可以绑定系统事件---click单机事件等等
<button @click=handler>点击</button>
2:组件标签---event可以绑定系统事件(不起作用:因为属于自定义事件)需要加 .native (可以把自定义事件变为原生DOM事件) 当前原生DOMclick事件,其实是给子组件的根节点 div 绑定了点击事件---利用到事件委派。
<Event @click.native=handler></Event>
promise.all( )
该方法用于将多个Promise实例,包装成一个新的Promise实例。
let p1 = new Promise((resolve, reject) => { resolve('成功')})
let p2 = new Promise((resolve, reject) => { resolve('success')})
let p3 = Promise.reject('失败')
// 全部成功时成功
Promise.all([p1, p2]).then((result) => {
console.log(result) //['成功', 'success']
}).catch((error) => { console.log(error)})
// 有一个失败就失败
Promise.all([p1,p3,p2]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error) // 失败
})
往会话存对象(本地相同)
需要先把要存的对象转换成字符串。
sessionStorage.setItem("SKUINFO",JSON.stringify(this.skuInfo)
获取会话中存储的数据
computed:{
skuInfo(){
return JSON.parse(sessionStorage.getItem('SKUINFO'))
}
},
不同的页面获取用户信息
问题:如果已经登录则需要获取用户信息需要向服务器发送请求,服务器返回用户信息,展示在页面。如果没有登录,则不显示用户信息,显示请登录。
1、获取用户信息,api 请求方式,仓库中创建一个 action ,派发 action 获取用户信息。
2、不可能在每个组件页面 mounted 中派发 action 。
3、在 路由前置守卫中判断如果没有没有用户信息,就派发 action 获取用户信息。
不用VUEX在组件中发请求,请求接口统一管理
1.在 main.js 中引入,把所有请求 api 暴露出来挂载到 vue 原型上面,类似 $bus
2.组件中使用
要求:未登录点击 我的订单,登录之后跳转到我的订单
在 router.js 全局守卫中当中,router.beforeEach,判断没有登录时,把在未登录时想要去的页面路径存储在地址中(路由)。
然后在登录组件中。 判断路由当中是否包含 query 参数指定路由,如果没有就跳转到 home 首页