尚品汇项目后续部分

224 阅读10分钟

注册组件

注意:

assets文件夹---放置全部组件的共用静态资源

在样式当中也可以使用@符号【src】别名,切记要在前面加上~

由于登录组件需要先注册,所以我们先完善注册组件

第一步:注册组件(路由组件)之前已经写好了的。

最后一步:表单验证(暂时不做,最后再处理)

  1. 静态页面

    静态页面已经写好

  2. 绑定参数

    因为用户输入的数据都是我们需要的,因此分别绑定参数v-model

        data() {
          return {
            //收集表达数据--手机号
            phone:'',
            //验证码
            code:''  ,
            //密码
            password:'',
            //确认密码
            password1:'',
            //是否同意
            agree:true
          };
        },
    
  3. 获取验证码

    正常来说是将验证码发送到用户手机上,但是为了省钱。我们将用户手机号传递给服务器后,服务器把用户验证码当成数据发送给我们。

    1. 绑定点击函数

    2. 点击函数,发送请求

           getCode(){
                //解构复制
                const {phone} = this
                phone && this.$store.dispatch('user/getCode',phone)
                。。。。。。
            },
      
    3. api

      //获取验证码
      // URL:/api/user/passport/sendCode/{phone} method GET
      export const reqGetCode = (phone) =>requests({
          url:`/user/passport/sendCode/${phone}`,
          method:"get"
      })
      
    4. Vuex

      //获取验证码 action
          async getCode({commit},phone){
              //获取验证码的接口:把验证码返回,但是正常情况,后台把验证码发到用户手机上【省钱】
              let result = await reqGetCode(phone)
              if(result.code==200){
                  commit('GETCODE',result.data)
              }else{
                  return Promise.reject(new Error('faile'))
              }
          },
      

       

  4. 组件获取验证码

         //获取验证码
          async getCode(){
            //简单判断---至少有数据
            try {
              //如果获取验证码
              const {phone} = this
              phone && await this.$store.dispatch('user/getCode',phone)
              //将仓库中的code复制给组件的code[验证码自己填写]
              this.code = this.$store.state.user.code
            } catch (error) {
              console.log(error.message);
            }
          },
    
  5. 完成注册

    完成注册分为几步:收集数据信息,编写API,vuex发送请求,收到vuex注册成功后组件跳转到登录页面

    1. 收集数据,绑定点击函数

            //用户注册
            userRegister(){
              const {phone,code,password,password1} = this
              if(phone&&code&&password==password1)
              {
                this.$store.dispatch('user/userRegister',{phone,password,code})
                ......
              }
            },
      
    2. API

      //注册接口
      // URL: /api/user/passport/register  method :post  phone code password
      export const reqUserRegister = (data) =>requests({
          url:`/user/passport/register`,
          data,
          method:"post",
      
      })
      
    3. Vuex

      注册成功无需返回数据,所以只需要actions

         //用户注册
          async userRegister({commit},user){
              let result = await reqUserRegister(user)
              if(result.code==200){
                  return 'ok'
              }else{
                  return Promise.reject(new Error('fail'))
              }
          }
      
    4. 组件实例

            //用户注册
            async userRegister(){
              const {phone,code,password,password1} = this
              if(phone&&code&&password==password1)
              {
                try {
                //如果成功,路由跳转
                await this.$store.dispatch('user/userRegister',{phone,password,code})
                alert('账号注册成功')    
                //路由跳转
                this.$router.push("/login")
              } catch (error) {
                console.log(error.message);
              }
              }
            },
      

登录组件

登录成功的时候,后台为了区分你这个用户是谁-服务器下发token【令牌:唯一标识符】

注意:vuex存储数据是非持久化的,刷新就没

  1. 静态组件

    静态组件已经给出

  2. 绑定函数、传递参数

          async userLogin(){
             const {phone,password} = this
             this.$store.dispatch('user/userLogin',{phone,password})
    
          }
    
  3. API

    //登录接口
    // URL:/api/user/passport/login  method:post  phone password
    export const reqUserLogin = (data) =>requests({
        url:`/user/passport/login`,
        data,
        method:"post",
    
    })
    
  4. Vuex(发送请求、保存token)

    const actions ={
        ......
        //登录业务
        async userLogin({commit},data){
            let result = await reqUserLogin(data)
            //服务器下发token,用户唯一标识符(UUID)
            //将来通过带token找服务器要用户信息展示
            if(result.code==200){
                commit("USERLOGIN",result.data.token)
                return 'ok'
            }else{
                return Promise.reject(new Error('fail'))
            }
        }
    }
    
    const mutations ={
        ......
        USERLOGIN(state,token){
            state.token=token
        }
    
    }
    const state ={
        ......
        token:''
    }
    
  5. 组件使用、路由跳转

        //登录业务
        async userLogin({commit},data){
            let result = await reqUserLogin(data)
            //服务器下发token,用户唯一标识符(UUID)
            //将来通过带token找服务器要用户信息展示
            if(result.code==200){
                commit("USERLOGIN",result.data.token)
                return 'ok'
            }else{
                return Promise.reject(new Error('fail'))
            }
        }
    

token问题

上一节中我们拿到了用户的token。我们需要根据token拿到用户信息

跳转到home组件之后,需要修改header组件的登录|注册 部分。

也就是说,跳转到home组件时,我们需要拿到用户的信息

  1. home组件发起请求(也就是跳转到home组件的时候,vuex会拿到用户信息)

    src\pages\Home\index.vue

        mounted() {
            ......
            //获取用户信息在首页展示
            this.$store.dispatch('user/getUserInfo')
        },
    
  2. API

    //获取用户信息【需要带着用户token找服务器要信息】
    // URL:api/user/passport/auth/getUserInfo GET
    export const reqUserInfo = () =>requests({
        url:`user/passport/auth/getUserInfo`,
        method:"get",
    })
    

    需要在请求头添加TOKEN信息

    //请求拦截器:在请求发出去之前可以做一些事情
    requests.interceptors.request.use((config)=>{
        //config:配置对象,对象里面有一个属性很重要,headers请求头
        ......
        //需要token带给服务器
        if(store.state.user.token){
            config.headers.token = store.state.user.token
        }
        return config
    });
    
  3. Vuex

        //获取用户信息 actions
        async getUserInfo({commit}){
            let result = await reqUserInfo()
            if(result.code==200){
                //提交用户信息
                commit("GETUSERINFO",result.data)
                return 'ok'
            }else{
                return Promise.reject(new Error('fail'))
            }
        }
        //mutation
        const mutations ={
            GETUSERINFO(state,userInfo){
            state.userInfo = userInfo
        }
    }
        //state
        const state ={
        code:'',
        token:'',
        userInfo:{}
    }
    
  4. header组件根据用户信息改变

    userName根据计算属性得到

      computed: {
        //用户名信息
        userName(){
          return this.$store.state.user.userInfo.name
        }
      },
    

刷新问题

此时会出现一个问题,那就是当你刷新页面的时候,由于vuex存储数据是非持久化的,刷新就没。又因为你的vuex中的token是通过登录按钮得到。所以刷新后home页面会因为发送给服务器的token是空的,所以会变成没有登陆的状态。

解决方式:将登录时得到的token保存在本地存储中。

  1. 重新包装一下(localStorage)非常的简单,也可以不包装

    src\utils\token.js

    //对外暴露一个函数
    export const setToken = (token)=>{
        localStorage.setItem('TOKEN',token)
    }
    
  2. 登陆的时候保存

        //引入
        import { setToken } from "@/utils/token";
        //登录业务
        async userLogin({commit},data){
            let result = await reqUserLogin(data)
            //服务器下发token,用户唯一标识符(UUID)
            //将来通过带token找服务器要用户信息展示
            if(result.code==200){
                commit("USERLOGIN",result.data.token)
                //持久化存储token
                setToken(result.data.token)
                return 'ok'
            }else{
                return Promise.reject(new Error('fail'))
            }
        },    
        //state
        const state ={
        code:'',
        token:localStorage.getItem('TOKEN'),
        userInfo:{}
    }
    

退出登录

退出登录要做的事情:

1:需要发请求,通知服务器退出登陆【清楚一些数据:token】

2:清除项目当中的数据【userInfo,token】

  1. 绑定点击函数、发送请求

        //退出登陆
        logout(){
          //退出登录需要做的事
          // 1:需要发请求,通知服务器退出登陆【清楚一些数据:token】
          //2:清除项目当中的数据【userInfo,token】
            this.$store.dispatch("user/userLogout")
        }
    
  2. API

    //退出登录
    // URL:/api/user/passport/logout  GET
    export const reqLogout = () =>requests({
        url:`/user/passport/logout`,
        method:"get",
    })
    
  3. Vuex

        //退出登录action
        async userLogout({commit}){
            //向服务器发起请求,通知服务器清除token
            let result = await reqLogout()
            if(result.code==200){
                commit('CLEAR')
                return 'ok'
            }else{
                return Promise.reject(new Error('fail'))
            }
        }
    
        //mutations
        //清除本地数据
        CLEAR(){
            //把仓库中的相关的用户信息清空,
            state.token=''
            state.userInfo=''
            //本地存储数据清空
            removeToken()
        }
    
  4. mutation使用到的函数:removeToken(包装的函数)。

    src\utils\token.js

    //清除本地存储的token
    export const removeToken = ()=>{
        localStorage.removeItem('TOKEN')
    }
    
  5. 跳转页面(跳转到首页)

        //退出登陆
        logout(){
          //退出登录需要做的事
          // 1:需要发请求,通知服务器退出登陆【清楚一些数据:token】
          //2:清除项目当中的数据【userInfo,token】
          try {
            //如果退出成功
            this.$store.dispatch("user/userLogout")
            //回到首页
            this.$router.push('/home')
          } catch (error) {
            console.log(error.message);
          }
        }
      },
    

导航守卫

在此主要是解决2个问题。

1:当登录之后再地址栏输入login不能返回登陆界面

2:除home组件外,其他组件登陆后再刷新(导致仓库的用户数据没了)会导致header显示问题。

对于问题1:增加一个判断,如果登陆后路要跳转到登陆界面,此时组织这个行为。

对于问题2:主要问题在于发起用户信息的请求在home组件中,我们把它拿出来,发到路由跳转之前。也就是说,如果你登陆了,然后你在刷新之前,再发起一次请求(注意,判断是否保留用户信息通过看能不能拿到name)

src\router\index.js

......
//引入store
import store from "@/store";

......

//全局路由守卫
router.beforeEach(async (to, from, next) => {
  //to:可以获取你要跳转到哪个路由信息
  //from:可以获取你从哪个路由而来的信息
  //next:放行函数
  //1:next()直接放行
  //2:next('path') 放行到指定路由
  //3:next(false)  中断此次导航。

  //用户登陆了,才会有token
  let token = store.state.user.token;
  //用户信息
  let name = store.state.user.userInfo.name;
  //用户已经登陆了
  if (token) {
    //用户已经登陆了就别想去login[给它停在首页]
    if (to.path == "/login") {
      next("/home");
    } else {
      //未登录或者登录刷新
      //登录刷新去的是【home|search|detail|shopcart】
      //如果用户名已有
      if (name) {
        next();
      } else {
        //没有用户信息,派发actions让仓库存储用户信息
        try {
          //获取用户信息成功
          await store.dispatch("user/getUserInfo");
          next();
        } catch (error) {
          //token失效了 获取不到用户信息
          //清除token
          await store.dispatch('user/userLogout')
          next('login')
        }
      }
    }
  } else {
    //未登录暂时未处理
    next();
  }
});

export default router;

Trade组件

trade组件就是购物车之后的结算页面。

  1. 静态页面

    已经给出

  2. shopcart兄弟组件点击跳转

  3. 编写路由

       import AppTrade from '@/pages/Trade'
       {
            path:"/trade",
            component:AppTrade,
            meta:{show:true}
        },
    
  4. 发起actions请求

    因为在组件一开始就需要用户地址信息商品清单俩个数据,因此需要想服务器发起请求

        mounted() {
          this.$store.dispatch('trade/getUserAddress')
          this.$store.dispatch('trade/getOrderInfo')
        },
    
  5. API

    //获取用户地址信息
    // URL:/api/user/userAddress/auth/findUserAddressList   get
    export const reqAddress = () =>requests({
        url:`/user/userAddress/auth/findUserAddressList`,
        method:"get",
    })
    
    //获取商品清单
    // URL:/api/order/auth/trade  GET
    export const reqOrderInfo = () =>requests({
        url:`/order/auth/trade`,
        method:"get",
    })
    
  6. Vuex

    注意:先在大仓库中引入和注册一下,此处省略

    const state ={
        address:[],
        orderInfo:{}
    }
    const mutations ={
        GETUSERADDRESS(state,address){
            state.address = address
        },
        GETORDERINFO(state,orderInfo){
            state.orderInfo= orderInfo
        }
    
    }
    const actions ={
        //获取用户地址信息
        async getUserAddress({commit}){
            let result = await reqAddress()
            if(result.code==200){
                commit('GETUSERADDRESS',result.data)
            }else{
                return Promise.reject(new Error('fail'))
            }
        },
        //获取商品清单数据
        async getOrderInfo({commit}){
            let result = await reqOrderInfo()
            if(result.code==200){
                commit('GETORDERINFO',result.data)
            }else{
                return Promise.reject(new Error('fail'))
            }
        }
    }
    
  7. 组件计算属性得到数据

    ...mapState({
            addressInfo:state=>state.trade.address,
            orderInfo:state=>state.trade.orderInfo
    
          }),
    
  8. 将数据填入模板中

  9. 修改默认地址(排他性)

    注意:绑定点击事件要给父组件绑定

        methods: {
          //修改默认地址
          changeDefault(address,addressInfo){
            //全部的isDefault变为0
            addressInfo.forEach(item => {
              item.isDefault=0
            });
            address.isDefault=1
          }
        },
    
  10. 买家留言部分

    因为此部分是要收集的之后提交订单的,因此单独给一个数据

        data() {
          return {
            //收集买家留言信息
            msg: '',
          };
        },
    
  11. 将来提交订单最终选中地址(数组find方法

    注意:赋值一个空对象,放置假报错(页面刷新时数据还没得到)

        computed: {
            ......
          //将来提交订单最终选中地址
          userDefaultAddress(){
            //find查找数组当中符合条件的第一个结果返回。
            return this.addressInfo.find(item=>{
              return item.isDefault==1
            })||{}
          }
        },
    

提交订单

提交订单这个部分我们要做一些其他的事情。对Vuex的使用要变更:为后面做准备,这里我们不再使用Vuex,因此要对API进行一些改动

API:

  1. 将API定义为全局属性

    src\main.js

    类似于全局总线$bus,放在vue原型对象上面

    //统一接口API文件夹里面全部请求函数
    //统一引入
    import * as API from "@/api"
    new Vue({
      render: h => h(App),
      //全局事件总线$bus配置
      beforeCreate(){
        Vue.prototype.$bus = this
        Vue.prototype.$API = API
      },
      //注册路由
      //注册路由信息:当这里书写router的时候,组件身上都拥有$route,$router属性
      router,
      //注册仓库:组件实例的身上会多一个属性:&store
      store
    }).$mount('#app')
    
  2. 使用

    this.$API.reqSubmitOrder(tradeNo,data);

提交订单按钮功能

  1. 绑定点击函数

  2. 函数功能实现

    在这里要注意,因为我们不使用Vuex,所以我们将返回的数据(orderId)存储于data中

     data() {
        return {
          //收集买家留言信息
          msg: "",
          orderId:''
        };
      },
    
        //提交订单
        async submitOrder() {
          //交易编码
          let { tradeNo } = this.orderInfo;
          //其余的六个参数
          let data = {
            consignee:this.userDefaultAddress.consignee,//名字
            consigneeTel: this.userDefaultAddress.phoneNum,//手机号
            deliveryAddress: this.userDefaultAddress.fullAddress,//地址
            paymentWay: "ONLINE",//支付方式
            orderComment: this.msg,//留言信息
            orderDetailList: this.orderInfo.detailArrayList//商品清单
          };
          //需要参数:tradeNo,
          let result =  await this.$API.reqSubmitOrder(tradeNo,data);
          //提交订单成功
          if(result.code==200){
            this.orderId = result.data
            //路由跳转 + 路由传参
            this.$router.push('/pay?orderId='+this.orderId)
          }else{
            //提交订单失败
            console.log(result.data);
          }
          console.log(result);
        },
    

立即支付组件(pay)

  1. 静态组件

  2. 配置路由

    src\router\routes.js

       import AppPay from '@/pages/Pay'
         {
            path:"/pay",
            component:AppPay,
            meta:{show:true}
        },
    
  3. 获取订单编号以及支付信息

    获取订单编号(路由传参)

        computed: { 
          orderId(){
            return this.$route.query.orderId
          }
        },
    

    获取支付信息:

    API:

    //获取支付信息
    // URL:/api/payment/weixin/createNative/{orderId}  GET
    export const reqPayInfo = (orderId) =>requests({
        url:`/payment/weixin/createNative/${orderId}`,
        method:"get",
    })
    

    组件内发送请求及获取数据

        //不建议在生命周期函数中async|await
        mounted() {
          this.getPayInfo()
         },   
        methods:{
          //获取支付信息
          async getPayInfo(){
            let result =  await this.$API.reqPayInfo(this.orderId)
            //如果成功:组件当中存储支付信息
            if(result.code==200){
              this.payInfo=result.data
            }
          },
        }
    
  4. 填写模板

使用elmentUI生成支付弹窗

  1. 安装elmentUI

    npm i element-ui -S

  2. 按需引入以及注册

    注意:主要使用MessageBox 弹框

    src\main.js

    import { Button,MessageBox} from 'element-ui';
    //注册按钮的全局组件
    Vue.component(Button.name,Button)
    //ElmentUI注册组件的另外一种写法:挂在原型上
    Vue.prototype.$msgbox = MessageBox;
    Vue.prototype.$alert = MessageBox.alert;
    
  3. 绑定点击函数

    src\pages\Pay\index.vue

  4. 使用MessageBox 弹框

    this.$alert(`输入文本`, '标题', {
              confirmButtonText: '已支付成功',
              dangerouslyUseHTMLString:true,
              //取消按钮的文本内容
              cancelButtonText:'支付遇见问题',
              //显示取消按钮
              showCancelButton:true,
              //居中
              center:true,
              //右上角的X
              showClose:false,
            });
    
  5. 使用QRCODE生成二维码地址

    1. 安装

      npm i qrcode

    2. 引入

      import QRCode from 'qrcode'

    3. 生成二维码地址

      payInfo的数据中有codeUrl,可以使用这个连接生成二维码

          //生成二维码(地址)
          let url = await QRCode.toDataURL(this.payInfo.codeUrl)
      
  6. 展示二维码界面

    this.$alert(`<img src=${url} />`, '微信支付', {
              confirmButtonText: '已支付成功',
              dangerouslyUseHTMLString:true,
              //取消按钮的文本内容
              cancelButtonText:'支付遇见问题',
              //显示取消按钮
              showCancelButton:true,
              //居中
              center:true,
              //右上角的X
              showClose:false,
            });
    
  7. 支付成功的操作

    注意:要不断向后台发请求来确认是否成功,因此使用定时器。data数据timer保存定时器

          data() {
          return {
            payInfo: {},
            timer:null,
            //支付成功的状态码
            code:''
          };
        },
    
        //弹出框
      async open(){
        //生成二维码(地址)
        let url = await QRCode.toDataURL(this.payInfo.codeUrl)
    
        this.$alert(`<img src=${url} />`, '微信支付', {
          confirmButtonText: '已支付成功',
          dangerouslyUseHTMLString:true,
          //取消按钮的文本内容
          cancelButtonText:'支付遇见问题',
          //显示取消按钮
          showCancelButton:true,
          //居中
          center:true,
          //右上角的X
          showClose:false,
          //关闭弹出框前的回调
        });
        //需要知道支付成功|失败
        //支付成功,路由跳转。支付失败,提示信息
        //定时器
        if(!this.timer){
          this.timer = setInterval(async ()=>{
            //发请求获取用户支付状态
            let result =  await this.$API.reqPayStatus(this.orderId)
            //如果支付成功
            if(result.code==200){
              //第一步:清除定时器
              clearInterval(this.timer)
              this.time=null
              //第二步:保存支付成功返回的code
              this.code =result.code
              //第三步:关闭弹出框
              this.$msgbox.close()
              //第四步:跳转到下一路由
              this.$router.push('/paysuccess')
    
            }
          },1000)
        }
      }
    
8. 按钮的回调(关闭弹出框前)

```js
     //弹出框
      async open(){
        //生成二维码(地址)
        let url = await QRCode.toDataURL(this.payInfo.codeUrl)

        this.$alert(`<img src=${url} />`, '微信支付', {
          confirmButtonText: '已支付成功',
          dangerouslyUseHTMLString:true,
          //取消按钮的文本内容
          cancelButtonText:'支付遇见问题',
          //显示取消按钮
          showCancelButton:true,
          //居中
          center:true,
          //右上角的X
          showClose:false,
          //关闭弹出框前的回调
          beforeClose:(type,instance,down)=>{
            //type:区分取消|确定按钮
            //instance 为 MessageBox 实例,可以通过它访问实例上的属性和方法
            //done 用于关闭 MessageBox 实例
            if(type=='cancel'){
              alert('请联系飞大帅')
              //清除定时器
              clearInterval(this.timer)
              this.time=null
              //关闭弹出框
              down()
            }else{
              //判断是否真的支付成功
              //为了方便
              /* if(this.code==200){ */
                //清除定时器
                clearInterval(this.timer)
                this.time=null
                //关闭弹出框
                down()
                this.$router.push('/paysuccess')
              /* } */
            }
          }
        });
        //需要知道支付成功|失败
        //支付成功,路由跳转。支付失败,提示信息
        //定时器
        if(!this.timer){
          this.timer = setInterval(async ()=>{
            //发请求获取用户支付状态
            let result =  await this.$API.reqPayStatus(this.orderId)
            //如果支付成功
            if(result.code==200){
              //第一步:清除定时器
              clearInterval(this.timer)
              this.time=null
              //第二步:保存支付成功返回的code
              this.code =result.code
              //第三步:关闭弹出框
              this.$msgbox.close()
              //第四步:跳转到下一路由
              this.$router.push('/paysuccess')

            }
          },1000)
        }
      }

个人中心组件(center)

知识点:二级路由,行合并

基础功能实现

  1. 静态页面,已经给出

    我们将我的订单以及团购订单的模板拆出来,作为子组件。分别放到src\pages\Center\myOrder\index.vue

    src\pages\Center\groupOrder\index.vue

  2. 组件内容

二级路由搭建

注意:二级路由路径写法以及重定向

src\router\routes.js

    import Center from '@/pages/Center'
    //引入二级路由组件
    import myOrder from '@/pages/Center/myOrder'
    import groupOrder from '@/pages/Center/groupOrder'  
    {
        path:"/center",
        component:Center,
        meta:{show:true},
        //二级路由组件
        children:[
            {
                path:'myorder',
                component:myOrder
            },
            {
                path:'grouporder',
                component:groupOrder
            },
            /* 重定向 */
            {
                path:'/center',
                redirect:'/center/myorder'
            }
        ]
    },

子组件(myOrder)

由于另一个子组件没有详情页面,主要介绍myOrder子组件

  1. 获取我的订单数据

    同样的,mouted钩子不建议放promise函数,因此将getData()函数放在method里面,然后通过调用实现。数据放在data中

      data() {
          return {
            //初始化参数
            //当前第几页
            page: 1,
            //每页展示数据个数
            limit:3,
            //存储我的订单的数据
            myOrder:{}
          };
        },
        mounted() {
          //获取我的订单数据的方法
          this.getData()
        },
        methods: {
          async getData(){
            //解构出参数
            const {page,limit} = this
            let result = await this.$API.reqMyOrderList(page,limit)
            if(result.code==200){
              this.myOrder = result.data
            }
          },
        },
    
  2. API

    //获取个人中心的数据
    // URL:/api/order/auth/{page}/{limit}   get
    export const reqMyOrderList = (page,limit) =>requests({
        url:`/order/auth/${page}/${limit}`,
        method:"get",
    })
    
  3. 模板呈现数据

    注意查看数据结构

  4. 行合并

    :rowspan="order.orderDetailList.length"

    根据元素个数合并行

    v-if="index==0"

    只显示一行就够

分页器

我们之前写过分页器,并且将它注册为全局组件,因此在这里使用的时候只需要将它引入且传递参数就可

<!-- 分页器 -->
          <AppPagination
           :pageNo="page"
           :pageSize="limit"
           :total="myOrder.total" 
           :continues="5"
           @getPageNo="getPageNo"
           ></AppPagination>

未登录的导航守卫判断

在之前的全局路由守卫中,我们对于用户未登录就进行路由跳转的行为没有做任何处理,直接放行next(),在这部分,要对未登录的路由跳转做处理

未登录应该有的限制

未登录:不能去交易相关、不能去支付相关【pay|paysuccess】、不能去个人中心

  //用户已经登陆了
  if (token) {
    。。。。。。
  } else {
    //未登录:不能去交易相关、不能去支付相关【pay|paysuccess】、不能去个人中心
    //未登录去上面这些路由-----登陆页面
    if(to.path =='/trade'|| to.path.indexOf('/pay')!=-1 ||to.path.indexOf('/center')!=-1){
      next(`/login`)
    }
    //去的不是上面这个
    next();
  }

存储路由信息

把未登录的时候想去而没有去成的信息,存储于地址栏中【路由】(也就是先点击例如“我的订单”,会跳转到登陆页面,但是登陆后应该立即跳转到“我的订单”)

处理方法:把点击的路由信息放在query参数中,传递给登录组件。登录组件点击登录按钮时对query参数进行判断

  1. 全局路由守卫

      if (token) {
        。。。。。。
      } else {
        //未登录:不能去交易相关、不能去支付相关【pay|paysuccess】、不能去个人中心
        //未登录去上面这些路由-----登陆页面
        if(to.path =='/trade'|| to.path.indexOf('/pay')!=-1 ||to.path.indexOf('/center')!=-1){
          //把未登录的时候想去而没有去成的信息,存储于地址栏中【路由】
          next(`/login?redirect=`+to.path)
        }
        //去的不是上面这个
        next();
      }
    
  2. 登陆组件

    登录的路由:看路由当中是否包含query参数,由:跳到query参数指定路由,没有:跳转到home首页

          async userLogin(){
            try {
              const {phone,password} = this
              if(phone&&password){
                await this.$store.dispatch('user/userLogin',{phone,password})
                //登录的路由:看路由当中是否包含query参数,由:跳到query参数指定路由,没有:跳转到home首页
                let toPath = this.$route.query.redirect||'/home'
                this.$router.push(toPath)
              }
            } catch (error) {
              console.log(error.message);
            }
          }
    

路由守卫的应用

全局路由守卫

src\router\index.js

//全局路由守卫
router.beforeEach(async (to, from, next) => {
  //to:可以获取你要跳转到哪个路由信息
  //from:可以获取你从哪个路由而来的信息
  //next:放行函数
  //1:next()直接放行
  //2:next('path') 放行到指定路由
  //3:next(false)  中断此次导航。

  //用户登陆了,才会有token
  let token = store.state.user.token;
  //用户信息
  let name = store.state.user.userInfo.name;
  //用户已经登陆了
  if (token) {
    //用户已经登陆了就别想去login[给它停在首页]
    if (to.path == "/login"||to.path == "/register") {
      next("/home");
    } else {
      //未登录或者登录刷新
      //登录刷新去的是【home|search|detail|shopcart】
      //如果用户名已有
      if (name) {
        next();
      } else {
        //没有用户信息,派发actions让仓库存储用户信息
        try {
          //获取用户信息成功
          await store.dispatch("user/getUserInfo");
          next();
        } catch (error) {
          //token失效了 获取不到用户信息
          //清除token
          await store.dispatch('user/userLogout')
          next('login')
        }
      }
    }
  } else {
    //未登录:不能去交易相关、不能去支付相关【pay|paysuccess】、不能去个人中心
    //未登录去上面这些路由-----登陆页面
    if(to.path =='/trade'|| to.path.indexOf('/pay')!=-1 ||to.path.indexOf('/center')!=-1){
      //把未登录的时候想去而没有去成的信息,存储于地址栏中【路由】
      next(`/login?redirect=`+to.path)
    }
    //去的不是上面这个
    next();
  }
});

路由独享守卫

src\router\routes.js

export default [
    ......
    {
        path:"/paysuccess",
        component:PaySuccess,
        meta:{show:true}
    },
    {
        path:"/pay",
        component:AppPay,
        meta:{show:true},
        beforeEnter: (to, from, next) => {
            if(from.path=='/trade'){
                next()
            }else{
            next(false)
            }
        }
    },
    {
        path:"/trade",
        component:AppTrade,
        meta:{show:true},
        //路由独享守卫
        beforeEnter: (to, from, next) => {
            //去交易页,必须是从购物车而来
           if(from.path=='/shopcart'){
                next()
           }else{
            next(false)
           }
        }
    },
    ......
]

路由内守卫

src\pages\PaySuccess\index.vue

  export default {
    name: 'PaySuccess',
    //组件内守卫
    beforeRouteEnter (to, from, next) {
      // 在渲染该组件的对应路由被验证前调用
      // 不能获取组件实例 `this` !
      // 因为当守卫执行时,组件实例还没被创建!
      if(from.path=='/pay'){
        next()
      }
      else{
        next(false)
      }
    },

   /*  beforeRouteUpdate(to, from) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
    // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
    }, */

    /* beforeRouteLeave(to, from) {
    // 在导航离开渲染该组件的对应路由时调用
    // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
    },
 */


  }

图片懒加载

图片懒加载:在我们的图片加载出来之前先展示一个其他的图片(gif)

方法:使用插件Vue-Lazyload

  1. 安装Vue-Lazyload

    使用这个版本不会bug

    npm i vue-lazyload@1.3.3 -S

  2. 注册插件

    src\main.js

    注:atm是我们放在assets文件夹下的gif

    //引入插件
    import VueLazyload from 'vue-lazyload'
    import atm from '@/assets/1.gif'
    //注册插件
    Vue.use(VueLazyload,{
      //懒加载默认图片
      loading: atm
    })
    
  3. 使用

    src\pages\Search\index.vue

    放在搜索页面下的图片

    原来是:img :src="good.defaultImg"

自定义插件

我们定义一个自定义插件,里面用到了[自定义指令](Vue.js 自定义指令 | 菜鸟教程)

  1. 创建文件并且定义插件

    src\plugins\maPlugins.js

    注意:传递的参数

    Vue:Vue实例,方便我们在这上面进行操作,比如注册自定义指令。或者将一些东西挂载到vue原型上去

    options:一个对象,在我们注册的时候传入

    //Vue插件一定暴露一个对象
    let myPlugins = {}
    
    myPlugins.install = function(Vue,options){
        //Vue.prototype.$bus:任何组件都可以使用
        //Vue.directive:注册自定义指令
    }
    
    export default myPlugins
    
  2. 引入注册插件

    注意:Vue.use执行的时候,其实就是调用myPlugins的install方法

    //引入自定义插件
    import myPlugins from '@/plugins/maPlugins'
    //注册插件
    Vue.use(myPlugins,{
      name:'upper'
    })
    
  3. vue自定义指令

    我们要使用自定义插件,为了掩饰可以通过vue实例做一些事情,通过调用Vue自定义指令来做一个示例。

    myPlugins.install = function(Vue,options){
        //Vue.prototype.$bus:任何组件都可以使用
        //Vue.directive:注册自定义指令
        Vue.directive(options.name,(element,params)=>{
            element.innerHTML = params.value.toUpperCase()
        }
    
        );
    }
    

    注解:此时的optings是我们在main.js注册时传递过来的{name:'upper'}。

    调用的Vue.directive传递的参数可参照:[Vue.js 自定义指令](Vue.js 自定义指令 | 菜鸟教程)

  4. 使用

      <h1 v-upper="msg"></h1>
    
       data() {
        return {
          msg: 'ABC',
        };
      },
    

总结:其实我们使用VUE插件,就是调用插件的install方法,这个方法可以接受Vue实例以及注册时传递的参数。我们可以做一些操作。

上述的使用过程可以总结为:引入注册插件(调用)==>插件调用函数(编写了一个自定义指令)===》使用自定义指令

路由懒加载

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效。

src\router\routes.js

  1. 开头不用引入

  2. 直接写在路由配置里面

    箭头函数简写形式

        {
            path:"/home",
            component:()=>import('@/pages/Home'),
            meta:{show:true}
        },
    

打包上限

打包:npm run build

打包后js文件夹中会出现.map文件,它是我们源文件代码的映射,体积大,打包上线后作用不大,可通过配置来选择打包不生成.map文件

vue.config.js

module.exports = defineConfig({
  //打包不生成map文件
  productionSourceMap:false,
  //新创建vue项目后,会出现第一行代码爆红的现象,解决它的方法
  transpileDependencies: true,
  //关闭ESLINT校验工具
  lintOnSave:false,
  //代理跨域
  devServer: {
    proxy: {
      '/api': {
        target:'http://gmall-h5-api.atguigu.cn',
      }
    },
  },
})