vue前台(十一)

一,我的订单获取数据进行展示

在center订单中心组建中,创建两个子路由组件,myorder组件, 我的订单,  grouporder组件, 团购组件

 <dd>
              <router-link to="/center/myorder">我的订单</router-link>
            </dd>
            <dd>
              <router-link to="/center/grouporder">团购订单</router-link>
            </dd>
  </div>
        <!-- 右侧内容 -->
        <router-view></router-view>
      </div>

配置路由

 { 
    path:'/center',
    component:Center,
    children:[
      {
        path:'myorder',
        component:Myorder
      },
      {
        path:'grouporder',
        component:Grouporder
      },
      {
        path:'',
        redirect:'myorder'
      }
    ]

  },

点击路由连接router-link,自带的类,配置颜色

   //左边
      .order-left {
        float: left;
         16.67%;

        .router-link-active {
          color: hotpink;
          /* background-color: hotpink; */
        }

1.封装请求获取我的订单分页信息

//请求获取我的订单分页信息   /api/order/auth/{page}/{limit}   get

export const reqMyOrder = (page,limit) => Ajax.get(`/order/auth/${page}/${limit}`)

 2.myorder组件获取数据进行展示,需要传入page和limit参数,可在data中初始化,因为myorder中有分页器,需要用到该参数

data() {
    return {
      page: 1,
      limit: 5,

      myOrderInfo: {},
    };
  },
  mounted() {
    this.getMyOrder();
  },
  methods: {
    //这个MyOrder组件是一个路由组件
    //路由组件点击切换才会创建组件对象,父组件传递才有可能
    //可以选择路由传参,但是非常复杂并且不适合(数据有可能很复杂)
    //所以请求数据只能在子路由组件
    async getMyOrder(page = 1) {
      this.page = page;
      const result = await this.$API.reqMyOrder(this.page, this.limit);
      if (result.code === 200) {
        this.myOrderInfo = result.data;
      }
    },

3.返回的响应数据

{
    "code": 200,
    "message": "成功",
    "data": {
        "records": [
            {
                "id": 70,
                "consignee": "admin",
                "consigneeTel": "15011111111",
                "totalAmount": 29495,
                "orderStatus": "UNPAID",
                "userId": 2,
                "paymentWay": "ONLINE",
                "deliveryAddress": "北京市昌平区2",
                "orderComment": "",
                "outTradeNo": "ATGUIGU1584247289311481",
                "tradeBody": "Apple iPhone 11 (A2223) 128GB手机 双卡双待 A",
                "createTime": "2020-03-15 12:41:29",
                "expireTime": "2020-03-16 12:41:29",
                "processStatus": "UNPAID",
                "trackingNo": null,
                "parentOrderId": null,
                "imgUrl": null,
                "orderDetailList": [
                    {
                        "id": 81,
                        "orderId": 70,
                        "skuId": 2,
                        "skuName": "Apple iPhone 11 (A2223) 64GB 红色",
                        "imgUrl": "http://192.168.200.128:8080/xxx.jpg",
                        "orderPrice": 5499,
                        "skuNum": 1,
                        "hasStock": null
                    },
                    …
                ],
                "orderStatusName": "未支付",
                "wareId": null
            },
            …
        ],
        "total": 41,
        "size": 2,
        "current": 1,
        "pages": 21
    },
    "ok": true
}

4.在html中填充数据

<tr v-for="(goods, index) in order.orderDetailList" :key="goods.id">
              <td width="60%">
                <div class="typographic">
                  <!-- hasStock:null
                    id:4252
                    imgUrl:"http://182.92.128.115:8080/group1/M00/00/0D/rBFUDF7G-ZKADQhWAAJsvyuFaiE144.jpg"
                    orderId:1939
                    orderPrice:4500
                    skuId:118
                    skuName:"华为P40--22"
                    skuNum:2
                  -->
                  <img :src="goods.imgUrl" style="80px;height:80px" />
                  <a href="#" class="block-text">{{goods.skuName}}</a>
                  <span>x{{goods.skuNum}}</span>
                  <a href="#" class="service">售后申请</a>
                </div>
              </td>
              <!--template是一个内置的标签,这个标签不会影响样式,相当于一个包裹器和div类似,但是div影响样式  -->
              <template v-if="index === 0">
                <td
                  :rowspan="order.orderDetailList.length"
                  width="8%"
                  class="center"
                >{{order.consignee}}</td>
                <td :rowspan="order.orderDetailList.length" width="13%" class="center">
                  <ul class="unstyled">
                    <li>总金额¥{{order.totalAmount}}</li>
                    <li>{{ order.paymentWay === "ONLINE" ? '在线支付': '货到付款'}}</li>
                  </ul>
                </td>
                <td :rowspan="order.orderDetailList.length" width="8%" class="center">
                  <a href="#" class="btn">{{order.orderStatus === "UNPAID"?"未支付":"已完成"}}</a>
                </td>
                <td :rowspan="order.orderDetailList.length" width="13%" class="center">
                  <ul class="unstyled">
                    <li>
                      <a href="mycomment.html" target="_blank">评价|晒单</a>
                    </li>
                  </ul>
                </td>
              </template>
            </tr>

注;1.此时需要对单元格进行合并,首先,只对第一行数据展示,然后对td单元格标签中的 :rowspan属性进行行数占据,可计算order.orderDetailList.length长度

 
 将单元格合并后

二,使用element-ui的分页器

1.在html中使用分页器paginaton

在入口文件main.js中导入分液器pagainaton

import { MessageBox, Message, Pagination } from 'element-ui';
Vue.use(Pagination)
 <!--  @size-change="changeSize" 修改每页的数量回调函数 选择了新条数,就会触发这个事件,把选择的条数传给这个事件 -->
      <!-- @current-change="getMyOrder" 修改当前页  点击了哪一页,就会触发这个事件 把点击的页码传给这个事件 -->
      <el-pagination
        background
        :current-page="page"
       
        :page-size="limit"
        layout=" prev, pager, next, jumper,->,total"
        :total="myOrderInfo.total"
        
        :pager-count="5"  
        @current-change="getMyOrder($event)"
       @size-change="changeSize" 
        
      ></el-pagination>

2.@current-change="getMyOrder" 和  @size-change="changeSize" 两个事件函数

 methods: {
    //这个MyOrder组件是一个路由组件
    //路由组件点击切换才会创建组件对象,父组件传递才有可能
    //可以选择路由传参,但是非常复杂并且不适合(数据有可能很复杂)
    //所以请求数据只能在子路由组件
    async getMyOrder(page = 1) {  //默认当前页为第一页
      this.page = page;   //修改当前页
      const result = await this.$API.reqMyOrder(this.page, this.limit);
      if (result.code === 200) {
        this.myOrderInfo = result.data;
      }
    },
  
  //改变当前页的条数
    changeSize(size){
      this.limit = size
      this.getMyOrder()
    },

注,对分页器的属性详解

 <el-pagination
    //当前页的颜色
    background
    //修改每页的条数回调函数 选择了新条数,就会触发这个事件,把选择的条数传给这个事件
      @size-change="handleSizeChange"
    //修改当前页  点击了哪一页,就会触发这个事件 把点击的页码传给这个事件
      @current-change="handleCurrentChange"
    //当前页
      :current-page="currentPage4" 首尾项
    //连续页码数,包括首
    :pager-count="5" 
      :page-sizes="[100, 200, 300, 400]"
    //每页显示条目个数
      :page-size="100"
    //total放在最后面
      layout=" prev, pager, next, jumper,->,total"
    //总条目数
      :total="400">
    </el-pagination>

三,全局前置路由守卫的使用

1.在router-index.js,  

2.引入vuex

import store from '@/store'
import routes from '@/router/routes'
const router = new VueRouter({
  routes,
  scrollBehavior (to, from, savedPosition) {
    return { x: 0, y: 0 }
  }
})

//添加全局前置路由导航守卫
// 必须登录后才能访问的多个界面使用全局守卫(交易相关、支付相关、用户中心相关) 
// 自动跳转前面想而没到的页面

router.beforeEach((to, from, next) => {
  //to:代表路由对象,目标(想去哪)
  //from: 代表路由对象,起始(从哪来)
  //netx:是一个函数,选择放行或者不放行的意思还可以去重定向到一个新的地方  
  //next()就是放行
  //next(false)不放行
  //next(路径)重定向

  let targerPath = to.path
  if(targerPath.startsWith('/pay') || targerPath.startsWith('/trade') || targerPath.startsWith('/center')){
    //看看用户是否登录了
    if(store.state.user.userInfo.name){
      next()
    }else{
      //在登录的路径后面添加上之前想要去的路径
      //配合登录逻辑可以让我们去到之前想去而没有去的地方
      next('/login?redirect='+targerPath)
    }
  }else{
    next()
  }

})

export default router

注; 1.如果用户需要去交易页面,订单页面,需要判断一下

2.判断用户是否登录了,从vuex中的user.js找到用户名信息,判断是否登录了

3.

如果用户没有登录,点击我的订单,跳转到登录页面,然后登录后,应该直接跳转到我的订单页面
此时需要配置一个重定向的路径,在登录路径后添加一个query参数(去哪里的路径)
此时在登录组件需要判断一下是否有query参数,有的话,去需要去的路径,没有的话,去home路径

  //点击登录按钮,发送请求
  methods: {
    async login() {
      let { mobile, password } = this;
      if (mobile && password) {
        let userInfo = { mobile, password };
        try {
          await this.$store.dispatch("userLogin", userInfo);
          let redirectPath = this.$route.query.redirect;
          //如果存在需要去的路径
          if (redirectPath) {
            //代表是从导航守卫进来的登录逻辑
            this.$router.push(redirectPath);
          } else {
            //代表不是从导航守卫来的登录逻辑
            this.$router.push("/home");
          }
        } catch (error) {
          alert(error.message);
        }
      }
    },
  },
 四,路由独享的守卫
 
1.

有个bug, 如果用户登录了,进入登录页面输入http://localhost:8080/#/login, 还是会跳转到登录页面,应该到home页面
此时需要设置路由独享守卫,没有登录时,放行,有登录时,跳转到home页面

2.引入vuex,    import store from '@/store'

在路由对象中 router--routers.js, 设置login路由对象中设置路由独享守卫

  {
    path:'/login',
    component:Login,
    // 用来判定底部是否隐藏
    meta:{
      isHide:true
    },
    //路由独享守卫
    beforeEnter: (to, from, next) => {
      //to:代表路由对象,目标(想去哪),此时代表login路由对象
      if(!store.state.user.userInfo.name){
        //没登录,放行
        next()
      }else{
        //登录了,跳转到home页面  
        next('/home')
      }
    }
  },

 五,组件内的守卫,一般不用,一般用路由独享守卫

和路由独享守卫的第二种方法,组件内的守卫,在login组件中设置

引入vuex

import store from '@/store'
  beforeRouteEnter(to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
    // 如果内部需要用到this,那么就得用下面的那个写法
    if (!store.state.user.userInfo.name) {
      //没有登录,放行
      next();
    } else {
      //登录了,跳转到home页面
      next("/home");
    }
  },

  // beforeRouteEnter(to, from, next) {
  //   next((vm) => {
  //     // 通过 `vm` 访问组件实例 vm就是你之前想要的this
  //   });
  // },

 六,只有携带了skuNum和sessionStorage内部有skuInfo数据  才能看到添加购物车成功的界面,路由独享守卫

 
 {
    path:'/addCartSuccess',
    component:AddCartSuccess,
    name:"addcartsuccess",
    //只有携带了skuNum和sessionStorage内部有skuInfo数据  才能看到添加购物车成功的界面
    beforeEnter: (to, from, next) => {
      let skuNum= to.query.skuNum
      let skuInfo = JSON.parse(sessionStorage.getItem('SKUINFO'))
      //判断
      if(skuNum && skuInfo){
        //携带了skuNum 和skuInfo,放行
        next()

      }else{
        next('/')

      }
    }
  },

只有从购物车界面/shopcart才能跳转到交易页面(创建订单)/trade

  {
    path:'/trade',
    component:Trade,
    beforeEnter: (to, from, next) => {
      if(from.path === '/shopcart'){
        next()
      }else{
        next('/')
      }
    }
  },

只有从交易页面(创建订单)页面/trade才能跳转到支付页面/pay

{
    path:'/pay',
    component:Pay,
    beforeEnter: (to, from, next) => {
      if(from.path === '/trade'){
        next()
      }else{
        next('/')
      }
    }
  },

只有从支付页面/pay才能跳转到支付成功页面/paysuccess

 {
    path:'/paysuccess',
    component:PaySuccess,
    beforeEnter: (to, from, next) => {
      if(from.path === '/pay'){
        next()
      }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" />

 1.在入口文件main.js中配置

// 引入图片懒加载插件
import VueLazyload from 'vue-lazyload'
import loading from '@/assets/images/loading.gif'
Vue.use(VueLazyload, { // 内部自定义了一个指令lazy
  loading,  // 指定未加载得到图片之前的loading图片
})

2.在search组件的img标签中实行v-lazy指令

 <router-link :to="`/detail/${goods.id}`" target="_blank">
                        <img v-lazy="goods.defaultImg" />
                      </router-link>

八,路由懒加载

路由懒加载  
    调用import函数把一次性打包的所有路由组件分开去打包
    然后访问哪一个再去加载哪一个

    (1)    当打包构建应用时,JS包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,
        然后当路由被访问的时候才加载对应组件,这样就更加高效了
    (2)    本质就是Vue 的异步组件在路由组件上的应用
    (3)    需要使用动态import语法, 也就是import()函数
    (4)    import('模块路径'): webpack会对被引入的模块单独打包
    (5)     当第一次访问某个路径对应的组件时,此时才会调用import函数去加载对应的js打包文件
1. 理解
(1)    当打包构建应用时,JS包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了
(2)    本质就是Vue 的异步组件在路由组件上的应用
(3)    需要使用动态import语法, 也就是import()函数
2. 编码
// import Home from '@/pages/Home'
// import Search from '@/pages/Search'
// import Detail from '@/pages/Detail'

/* 
1. import('模块路径'): webpack会对被引入的模块单独打包
2. 路由函数只在第一次请求时才执行, 也就是第一次请求访问对应路由路径时才会请求后台加载对应的js打包文件
*/
const Home = () => import('@/pages/Home')
const Search = () => import('@/pages/Search')
const Detail = () => import('@/pages/Detail')

没有使用路由懒加载时,是整体的组件打包

使用路由懒加载后,加载一个路由组件,打包一个路由组件, 0.js,是组件打包的文件,路由的懒加载组件按照顺序加载组件

 vee-validate表单验证

注;
required: true,是必须的验证,

name="phone" 是每项的输入框的名称标识,

invalid类,非法的类名,不匹配这个验证规则,提示报错

class="error-msg"  报错的类,局部验证

协议选项需要打钩,需要自定义一个规则agree

# 1. 说明

    vee-validate是专门用来做表单验证的vue插件
    我们当前用的是2.x的版本, 最新的3.x版本使用比较麻烦
    github地址: https://github.com/logaretm/vee-validate
    内置校验规则: https://github.com/logaretm/vee-validate/tree/v2/src/rules
    中文messages: https://github.com/logaretm/vee-validate/blob/v2/locale/zh_CN.js

# 2. 使用

## 1). 引入

    下载: npm install -S vee-validate@2.2.15   
    引入插件:
        import Vue from 'vue'
        import VeeValidate from 'vee-validate'
        
        Vue.use(VeeValidate)

## 2). 基本使用

     <input v-model="mobile" name="phone" v-validate="{required: true,regex: /^1d{10}$/}" 
          :class="{invalid: errors.has('phone')}">
     <span class="error-msg">{{ errors.first('phone') }}</span>
     
     const success = await this.$validator.validateAll() // 对所有表单项进行验证
     
     问题: 提示文本默认都是英文的

## 3). 提示文本信息本地化

    import VeeValidate from 'vee-validate'
    import zh_CN from 'vee-validate/dist/locale/zh_CN' // 引入中文message
    
    VeeValidate.Validator.localize('zh_CN', {
      messages: {
        ...zh_CN.messages,
        is: (field) => `${field}必须与密码相同`  // 修改内置规则的message
      },
      attributes: { // 给校验的field属性名映射中文名称
        phone: '手机号',
        code: '验证码',
      }
    })
    
    完整中文message源码: https://github.com/logaretm/vee-validate/blob/v2/locale/zh_CN.js

## 4). 自定义验证规则

    VeeValidate.Validator.extend('agree', {
      validate: value => {
        return value
      },
      getMessage: field => field + '必须同意'
    })

 1.在src新建一个文件,validate.js,

import Vue from 'vue'
import VeeValidate from 'vee-validate'
import zh_CN from 'vee-validate/dist/locale/zh_CN' // 引入中文message

Vue.use(VeeValidate)



VeeValidate.Validator.localize('zh_CN', {
  messages: {
    ...zh_CN.messages,
    is: (field) => `${field}必须与密码相同`  // 修改内置规则的message
  },
  attributes: { // 给校验的field属性名映射中文名称
    phone: '手机号',
    code: '验证码',
    password:'密码',
    password2:'确认密码',
    isCheck:'协议'
  }
})


VeeValidate.Validator.extend('agree', {
  validate: value => {
    return value
  },
  getMessage: field => field + '必须同意'
})

在入口文件main.js引入该模块

// 引入表单验证插件
import "../validate";

在Register注册组件中,使用规则

<div class="content">
        <label>手机号:</label>
        <input
          placeholder="请输入你的手机号"
          v-model="mobile"
          name="phone"
          v-validate="{ required: true, regex: /^1d{10}$/ }"
          :class="{ invalid: errors.has('phone') }"
        />
        <span class="error-msg">{{ errors.first("phone") }}</span>
        <!-- <input type="text" placeholder="请输入你的手机号" v-model="mobile" />
        <span class="error-msg">错误提示信息</span> -->
      </div>
<div class="content">
        <label>验证码:</label>
        <input
          placeholder="请输入验证码"
          v-model="code"
          name="code"
          v-validate="{ required: true, regex: /^d{4}$/ }"
          :class="{ invalid: errors.has('code') }"
        />

        <img
          ref="code"
          src="/api/user/passport/code"
          @click="changecode"
          alt="code"
        />

        <span class="error-msg">{{ errors.first("code") }}</span>
        <!-- <input type="text" placeholder="请输入验证码" v-model="code" />
        <img
          ref="code"
          src="/api/user/passport/code"
          @click="changecode"
          alt="code"
        />
        <span class="error-msg">错误提示信息</span> -->
      </div>
 <div class="content">
        <label>登录密码:</label>

        <input
          type="text"
          placeholder="请输入你的登录密码"
          v-model="password"
          name="password"
          v-validate="{ required: true, regex: /^1d{10}$/ }"
          :class="{ invalid: errors.has('password') }"
        />
        <span class="error-msg">{{ errors.first("password") }}</span>
        <!-- <input
          type="text"
          placeholder="请输入你的登录密码"
          v-model="password"
        />
        <span class="error-msg">错误提示信息</span> -->
      </div>

 确认密码要和密码一致

   <div class="content">
        <label>确认密码:</label>
        <input
          placeholder="请输入确认密码"
          v-model="password2"
          name="password2"
          v-validate="{ required: true, is: (password) }"
          :class="{ invalid: errors.has('password2') }"
        />
        <span class="error-msg">{{ errors.first("password2") }}</span>

        <!-- <input type="text" placeholder="请输入确认密码" v-model="password2" />
        <span class="error-msg">错误提示信息</span>   v-model="isCheck"   type="checkbox"-->
      </div>
<div class="controls">
        <input
        type="checkbox"
          v-model="isCheck"
          name="isCheck"
      //自定义的agree规则
v-validate="{ agree: true }" :class="{ invalid: errors.has('isCheck') }" /> <span>同意协议并注册《尚品汇用户协议》</span> <span class="error-msg">{{ errors.first("isCheck") }}</span> <!-- <input name="m1" type="checkbox" /> <span>同意协议并注册《尚品汇用户协议》</span> <span class="error-msg">错误提示信息</span> --> </div>

,此时,点击注册按钮,需要统一验证

//点击完成注册,发送请求
    async wczc() {
      const success = await this.$validator.validateAll(); // 对所有表单项进行验证
      if (success) {
        let { mobile, password, code, password2 } = this;
        try {
          // if (mobile && code && password && password === password2) {
          let userInfo = {
            mobile,
            password,
            code,
          };

          await this.$store.dispatch("getreqRegister", userInfo);
          alert("注册成功,跳转到登录");
          this.$router.push("/login");
          // }
        } catch (error) {
          alert(error);
        }
      }
    },
原文地址:https://www.cnblogs.com/fsg6/p/13492672.html