文章目录

  • 八、加入购物车
    • 路由跳转之前发请求
      • api—请求接口
      • 将数据存储到服务器里
      • 判断加入成功 或 失败
    • 成功后进行路由跳转
      • 开始进行路由跳转,并进行路由传参
      • 开始渲染页面
      • 购物车静态组件与修改
  • 九、完成ShopCart购物车模块业务
    • 向服务器发起请求,获取购物车数据
      • 向服务器发请求
      • 操作vuex 将数据存储到仓库中
      • 组件获取数据展示数据
    • UUID临时游客身份
    • 动态展示购物车
      • 修改购物车产品的数量
      • 删除购物车产品
      • 修改产品的勾选状态
      • 删除选中全部产品的操作
      • 修改全部商品的勾选状态
  • 十、完成登录与注册页面
    • 登录与注册的静态组件完成
    • 注册业务的完成
      • 向手机发送验证码
      • 登录和确认密码
      • 点击 完成注册
      • 表单验证
    • 登录业务的完成
  • 十一、登录过后
    • 首页用户信息的展示
      • Header组件显示用户名与退出登录
      • 持久化存储token
    • 退出登录
    • 导航守卫
      • 用户登录后的页面
      • 用户未登录后的页面
  • 十二、完成trade交易业务
    • 完成交易页面的静态组件
    • 获取交易页数据
    • 动态渲染数据
      • 渲染用户地址信息
      • 渲染商品清单信息
      • 收集买家的留言信息
      • 渲染底部总信息
  • 十三、完成提交订单业务
    • 完成支付页面的静态组件
    • 提交订单发起请求
    • 组件存储数据
    • 在支付页面展示数据
      • 展示订单号
      • 展示应付金额
    • 点击立即支付-elementUI
    • 微信支付业务
      • 二维码生成 qrcode
      • 支付后
        • 支付成功
        • 没有支付
      • 完整代码
  • 十四、个人中心
    • 完成静态组件
    • 个人中心二级路由搭建
    • 我的订单
      • 发请求存储数据
      • 动态渲染数据
      • 分页器
  • 十五、鉴权判断
    • 未登录的导航守卫判断
    • 路由独享守卫
    • 组件内守卫
  • 十六、图片懒加载
  • 十七、路由懒加载
  • 十八、项目上线
    • 打包上线
    • 购买云服务器
      • 安全组设置
      • xshell链接服务器与linux指令
      • nginx反向代理

八、加入购物车

点击 加入购物车 的时候,先发请求,把数据存储到服务器,然后再进行路由跳转

  • 当点击 加入购物车 的时候,会跳转到 加入购物车成功的页面,到时候需要配置路由,进行路由跳转

路由跳转之前发请求

api—请求接口

//     src/api/index.js// 将产品添加到购物车中(或者 更新某一个产品个数)//   /api/cart/addToCart/{ skuId }/{ skuNum }  POST 带参数export const reqAddOrUpdateShopCart = (skuId,skuNum)=>{    return requests({url:`/cart/addToCart/${skuId}/${skuNum}`,method:'POST'})}

将数据存储到服务器里

  1. 派发actions,发请求

    //       src / pages / detail /index.vue<div class="add">        <a @click="addShopcar">加入购物车</a></div>
        // 加入购物车的回调函数addShopcar() {      // 1. 发请求--将产品加入到数据库(通知服务器)      //    服务器存储成功----进行路由跳转      //    失败,给用户提示     this.$store.dispatch( "detail/addOrUpdateShopCart",{skuId:this.$route.params.skuId,skuNum:this.skuNum});},

加入购物车以后(发请求),前台将参数带给服务器

服务器写入数据成功,并没有返回其他的数据,只是返回code=200,代表这次成功

⚠ !!!因为服务器没有返回其余的数据,所以我们不需要vuex来存储数据

  1. 将参数带给服务器

    const actions = {    // 将产品添加到购物车中    async addOrUpdateShopCart({commit},{skuId,skuNum}){        // 加入购物车返回的结果        let result = await reqAddOrUpdateShopCart(skuId,skuNum);        console.log('购物车',result);        // 加入购物车以后(发请求),前台将参数带给服务器        // 服务器写入数据成功,并没有返回其他的数据,只是返回code=200,代表这次成功        // 因为服务器没有返回其余的数据,所以我们不需要vuex来存储数据    },};

判断加入成功 或 失败

async:只要有async返回的就是Promise,Promise返回的不是成功就是失败

  1. 发请求–将产品加入到数据库(通知服务器)

  2. 服务器存储成功—-进行路由跳转

  3. 服务器存储失败——给用户提示

//      src/pages/detail/index.vue// 加入购物车的回调函数    async addShopcar() {      // 1. 发请求--将产品加入到数据库(通知服务器)      //    服务器存储成功----进行路由跳转      //    失败------给用户提示      try {        await this.$store.dispatch("detail/addOrUpdateShopCart", {skuId: this.$route.params.skuId,skuNum: this.skuNum,});        // 成功了进行路由跳转          .....      } catch (error) {        alert(error.message);      }    },

this.$store.dispatch("detail/addOrUpdateShopCart", {skuId: this.$route.params.skuId,skuNum: this.skuNum,});表示调用了addOrUpdateShopCart这个函数,调用这个函数,这个函数有async,说明这个函数的返回值是Promise函数

await返回的是promise成功的值,但是我们要有成功做什么以及失败做什么…如果await的promise失败了,就会抛出异常,我们需要通过try…catch捕获处理

所以在detail.vue中,给 加入购物车的回调函数加上 async

//      src/store/detail/index.js// 将产品添加到购物车中    async addOrUpdateShopCart({ commit }, { skuId, skuNum }) {        // 加入购物车返回的结果        // 加入购物车以后(发请求),前台将参数带给服务器        // 服务器写入数据成功,并没有返回其他的数据,只是返回code=200,代表这次成功        // 因为服务器没有返回其余的数据,所以我们不需要vuex来存储数据        let result = await reqAddOrUpdateShopCart(skuId, skuNum);        // 当前的这个函数,如果执行,返回promise        // 代表服务器加入购物车成功        if(result.code==200){            return 'ok'; // 返回的只要是非空字符串就是成功        }else{            // 代表加入购物车失败            return Promise.reject(new Error('false'));        }    },

成功后进行路由跳转

注意:路由不是组件!!!??????❓

路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。

使用组件的步骤:创建-注册-引用-使用

  1. 创建路由,写到专门放路由的文件夹下

  2. 编写路由配置项:src/router/index.js

    // 配置路由的地方import Vue from 'vue';import VueRouter from 'vue-router';// 使用插件Vue.use(VueRouter);// 引入路由组件import AddCartSuccess from '../pages/AddCartSuccess'// 先把VueRouter原型对象的push,先保存一份const originalPush = VueRouter.prototype.pushconst originalReplace = VueRouter.prototype.replace// 重写push | replace// 参数:告诉原来的push方法,你往哪里跳转(传递哪些参数)VueRouter.prototype.push = function push(location) {    return originalPush.call(this, location).catch(err => err)}VueRouter.prototype.replace = function replace(location) {    return originalReplace.call(this, location).catch(err => err)}// 配置路由export default new VueRouter({    // 配置路由    routes: [        ...        {            name: 'addcartsuccess',            path: '/addcartsuccess',            component: AddCartSuccess,            meta: { showFooter: true }        },        // 重定向:在项目跑起来的时候,访问/,立马让他定向到首页!!!        {            path: '*',            redirect: '/home',        }    ],    // 控制滚动条的滚动行为    scrollBehavior(to, from, savedPosition) {        // return 期望滚动到哪个的位置        return { y: 0 }; // 始终滚动到顶部    }})

开始进行路由跳转,并进行路由传参

路由传参的话数据过多,在skuInfo里面,是个对象,以及还要带参数skuNum,地址栏会有点乱

所以这里我们只带skuNum参数传过去,其余复杂数据用会话存储—不持久化,会话结束数据消失

浏览器存储功能:HTML5中新增的,分为本地存储和会话存储

本地存储:持久化的,具有上限—–5M

会话存储:不是持久化的(浏览器关闭等),

    // 加入购物车的回调函数    async addShopcar() {      // 1. 发请求--将产品加入到数据库(通知服务器)      //    服务器存储成功----进行路由跳转      //    失败------给用户提示      try {        await this.$store.dispatch("detail/addOrUpdateShopCart", {          skuId: this.$route.params.skuId,          skuNum: this.skuNum,        });        // 成功了进行路由跳转,并将产品的信息带给下一级的路由组件        // 会话存储 | 本地存储,一般存储的是字符串,所以将对象转换为字符串        sessionStorage.setItem('SKUINFO',JSON.stringify(this.skuInfo))        this.$router.push({          name: "addcartsuccess",          query:{skuNum:this.skuNum},        });      } catch (error) {        alert(error.message);      }    },

本地存储 里面只能存储字符串格式 ,因此需要把对象转换为字符串JSON.stringify()

获取本地存储数据,需要把里面的字符串转换为对象格式JSON.parse() 我们才能使用里面的数据。

获取本地存储数据

开始渲染页面

   <div class="left-good">         <div class="left-pic">            <img :src="skuInfo.skuDefaultImg" />         </div>         <div class="right-info">            <p class="title">{{ skuInfo.skuName }}</p>            <p class="attr">              <span v-for="attrName in skuInfo.skuSaleAttrValueList" :key="attrName.id">{{ attrName.saleAttrName }} {{attrName.saleAttrValueName}}  </span>              <span>数量:{{$route.query.skuNum}}</span>            </p>         </div>       </div>   <div class="right-gocart">

购物车静态组件与修改

  1. 跳转到detail商品详情页,携带参数

     <div class="right-gocart">          <router-link :to="`/detail/${skuInfo.id}`" class="sui-btn btn-xlarge">查看商品详情</router-link>          <a href="javascript:">去购物车结算 > </a> </div>
  2. 跳转到购物车页面ShopCart

九、完成ShopCart购物车模块业务

  1. 引入和配置路由

    // 配置路由的地方import Vue from 'vue';import VueRouter from 'vue-router';// 使用插件Vue.use(VueRouter);// 引入路由组件import ShopCart from '../pages/ShopCart'// 先把VueRouter原型对象的push,先保存一份const originalPush = VueRouter.prototype.pushconst originalReplace = VueRouter.prototype.replace// 重写push | replace// 参数:告诉原来的push方法,你往哪里跳转(传递哪些参数)VueRouter.prototype.push = function push(location) {    return originalPush.call(this, location).catch(err => err)}VueRouter.prototype.replace = function replace(location) {    return originalReplace.call(this, location).catch(err => err)}// 配置路由export default new VueRouter({    // 配置路由    routes: [        {            name: 'shopcart',            path: '/shopcart',            component: ShopCart,            meta: { showFooter: true },        },        // 重定向:在项目跑起来的时候,访问/,立马让他定向到首页!!!        {            path: '*',            redirect: '/home',        }    ],    // 控制滚动条的滚动行为    scrollBehavior(to, from, savedPosition) {        // return 期望滚动到哪个的位置        return { y: 0 }; // 始终滚动到顶部    }})
  2. 路由跳转

      <div class="right-gocart">          <router-link :to="`/detail/${skuInfo.id}`" class="sui-btn btn-xlarge">查看商品详情</router-link>          <router-link to="/shopcart">去购物车结算 > </router-link>  </div>

向服务器发起请求,获取购物车数据

向服务器发请求

/api/cart/cartList GET 无参数

//    src/api/index.js// 获取购物车列表数据   /api/cart/cartList  GET 无参数export const reqCartList = ()=>{    return requests({url:'/cart/cartList',method:'GET'})}

操作vuex 将数据存储到仓库中

  1. 新建一个仓库,用来存储购物车的数据
//      src/store/shopcart/index.jsimport { reqCartList } from '@/api/index'const state = {};const mutations = {};const actions = {};const getters = {};export default {    namespaced: true,    state,    mutations,    actions,    getters}
  1. 在大仓库中引用小仓库
//      src/store/index.jsimport Vue from 'vue';import Vuex from 'vuex';// 需要使用插件Vue.use(Vuex);// 引入小仓库import home from './home'import search from './search'import detail from './detail'import shopcart from './shopcart';// 对外暴露Store类的一个实例export default new Vuex.Store({    // 实现vuex仓库模块式开发存储数据    modules:{        home,        search,        detail,        shopcart    }})
  1. vuex三连环

    const actions = {    // 获取购物车列表的数据    async getCartList({ commit }) {        let result = await reqCartList();        console.log('购物车列表',result);    },};
      export default {    name: 'ShopCart',    mounted(){      // 获取服务器数据      this.getData();    },    methods:{      // 获取个人购物车数据      getData(){        this.$store.dispatch('shopcart/getCartList');      }    }  }

    注意:发请求的时候,获取不到你购物车里的数据,因为这里不知道购物车获取谁的数据,需要给用户加一个身份!所以需要 UUID临时游客身份

  2. 开始真正的vuex三连环!!

    这个是shopcart仓库里面的数据,有点复杂,所以我们要简化

    import { reqCartList } from '@/api/index'const state = {    cartList:[],};const mutations = {    GETCARTLIST(state, cartList) {        state.cartList = cartList;    }};const actions = {    // 获取购物车列表的数据    async getCartList({ commit }) {        let result = await reqCartList();        if (result.code == 200) {            commit('GETCARTLIST', result.data); // result.data 是个数组        }    },};const getters = {    cartList(state){        // state.cartList[0] 如果没有返回,至少是个数组        return state.cartList[0]|| [];    },};export default {    namespaced: true,    state,    mutations,    actions,    getters}

    现在仓库里面有了数据

组件获取数据展示数据

...mapGetters("shopcart", ["cartList"]), 组件开始获取数据

遍历用every

<script>import { mapGetters } from "vuex";export default {  name: "ShopCart",  mounted() {    // 获取服务器数据    this.getData();  },  methods: {    // 获取个人购物车数据    getData() {      this.$store.dispatch("shopcart/getCartList");    },  },  computed: {    ...mapGetters("shopcart", ["cartList"]),// 并不是真正的购物车列表数据    // 真正的购物车列表数据    cartInfoList() {      // 至少是个空数组      return this.cartList.cartInfoList || [];    },    // 计算购买产品的总价    totalPrice() {      let sum = 0;      this.cartInfoList.forEach((item) => {        // item是购物车列表的每一行数据        sum += item.skuNum * item.cartPrice;      });      return sum;    },    // 判断底部的复选框是否勾选    isAllChecked(){      // 遍历每一个产品的isChecked,只要全部元素isChecked属性都为1,返回为真      return this.cartInfoList.every(item=>item.isChecked==1)    },  },};</script>

开始渲染数据

UUID临时游客身份

在点击 加入购物车 的时候,告诉服务器你是谁

utils :放一些常用的功能模块,比如正则,uuid

  1. 将游客身份用会话存储(sessionStorage)保存,放到detail仓库里

    uuid是一个随机的字符串,且每次执行不能发生变化,且持久存储,

    所以每次调用getUUID函数的时候,先从本地存储获取uuid,看会话存储是否有,

    如果有的话就返回会话存储里面的uuid,

    如果没有的话,就生成uuid

//      src/utils/uuid_token.jsimport {v4 as uuidv4} from 'uuid'// 要生成一个随机的字符串,且每次执行不能发生变化,游客身份持久存储export const getUUID = ()=>{    // 先从会话存储获取uuid,看一下本地存储是否有    let uuid_token = sessionStorage.getItem('UUIDTOKEN');    // 如果没有,我就生成UUID    if(!uuid_token){        uuid_token=uuidv4();        // 会话存储 存储一次        sessionStorage.setItem('UUIDTOKEN',uuid_token);    }    // 切记要有返回值!    return uuid_token;}

在detail仓库里面存储uuid

//     src/store/detail/index.js// 封装游客身份模块uuid---生成一个随机的字符串(不能再变了)import {getUUID} from '@/utils/uuid_token'const state = {    goodsInfo: {}, // 看api文档,result.data返回的是一个对象    // 游客临时身份    uuid_token:getUUID(),};

现在游客身份在仓库里,我们要把数据带给服务器,可以利用请求头把数据带给服务器

找到请求拦截器,在请求拦截器捞到仓库里的数据

//   src/api/request.js// 在当前模块引入store仓库import store from '@/store'.....// 请求拦截器:在发请求之前,请求拦截器可以检测到,可以在请求发出去之前做一些事情requests.interceptors.request.use((config) => {    // config:配置对象,对象里面有一个属性很重要:headers请求头    if (store.state.detail.uuid_token) {        // 给请求头添加一个字段(userTempId):和后台老师商量好!        config.headers.userTempId = store.state.detail.uuid_token    }    // 进度条开始动    nprogress.start();    return config;})

动态展示购物车

修改购物车产品的数量

点击 + 或 – 或 修改input框里的数字,input框里发生变化

:这个时候向服务器发请求吗?

发请求,如果不发请求的话,服务器里的数据还是原来的,那么该页面的关于数量的数据和原来一样,

———–所以我们每当修改的时候,就要发请求给服务器,然后再从服务器捞到数据,进行渲染页面

这个之前写过api(在将产品添加到购物车那里,detail详情页,也有仓库了)所以我们可以把数据存到detail仓库,就现在直接派发action就行了,然后再重新捞数据渲染

这里的skuNum是 现在状态的数字 与 起始状态的数字 的差值。比如:现在商品数量是5,我们要买12,这个skuNum是12-5 = 7

  1. 找到产品数量的结构位置

     <li class="cart-list-con5">            <a href="javascript:void(0)" class="mins">-</a>                        <input              autocomplete="off"              type="text"              minnum="1"              class="itxt"              :value="cart.skuNum"            />            <a href="javascript:void(0)" class="plus">+</a> </li>

    这三个节点 要派发同一个action,也就是说这三个节点调用同一个回调函数

    :但是如何判断这三个节点?

    通过传参。三个不同的参数,用形参接收,来区分这三个节点

  2. 要传三个参数

    第一个参数type 是用来区分它们三个节点

    第二个参数是disNum,他们的变化量,+号是1,-号是-1,input框暂定不是变化量,是修改后的值

    第三个参数是cart 当前他们点击的产品的信息cart,然后得知他们的id,因为要发请求需要skuID

      <li class="cart-list-con5">            <a href="javascript:void(0)" class="mins" @click="handler('mins',-1,cart)">-</a>                        <input              autocomplete="off"              type="text"              minnum="1"              class="itxt"              :value="cart.skuNum"              @change="handler('change',$event.target.value*1,cart)"            />            <a href="javascript:void(0)" class="plus" @click="handler('plus',1,cart)">+</a>  </li>
  3. 先写 + 号 和 – 号

    加号:直接带给服务器变化的量

    减号:判断产品的当前数量是否大于1,大于1才能减,传递给服务器-1

    然后就是派发action发请求,发给服务器后再 捞数据 渲染页面

        // 购物车里 修改某一个产品的个数    handler(type, disNum, cart) {      //disNum 代表 现在状态的数字 与 起始状态的数字 的差值      // 目前disNum 形参: + 号变化量(1),- 号 变化量 (-1),input 最终的量(并不是变化量)      // 向服务器发请求,修改数量      switch (type) {        // 加号        case "plus": {          // 带给服务器变化的量          disNum = 1;          break;        }        // 减号        case "mins": {          // 判断产品的数量是否大于1,大于1才能减,传递给服务器-1          if (cart.skuNum > 1) {            disNum = -1;          } else {            // 产品的个数小于等于1,传递给服务器的个数为 0            disNum = 0;          }          break;        }      }              // 派发action 发请求      try {        // 代表的是修改购物车中产品数量修改成功        await this.$store.dispatch("detail/addOrUpdateShopCart", {          skuId: cart.skuId,          skuNum: disNum,        });        // 再一次获取服务器的最新数据进行展示        this.getData();      } catch (error) {        // 修改失败        alert(error.message)      }    },
  4. 解决 input 框里面输入数字

    如果input框里面输入的不是数字,或者小于1,则带给服务器的数字是0

    如果输入的是小数,将你最终输入的取整,然后减去最初的数量,就是你要传的变化量disNum

      // input框  case "change": {          // 如果input框里面输入的不是数字,或者小于1,则带给服务器的数字是0          if (isNaN(disNum) || disNum < 1) {            disNum = 0;          } else {            // 如果输入的是小数,将你最终输入的取整,然后减去最初的数量,就是你要传的变化量disNum            disNum = parseInt(disNum) - cart.skuNum;          }          break;  }
  5. 对修改购物车产品的数量 进行节流操作

    当你刚到购物车页面的时候,快速点击 – 号,商品的数量会变成负数、

    这是因为 用户操作太频繁,请求服务器跟不上————–所以我们用到【节流】,限制用户的操作

    //   原来写法async handler(type, disNum, cart) {      //disNum 代表 现在状态的数字 与 起始状态的数字 的差值      // 目前disNum 形参: + 号变化量(1),- 号 变化量 (-1),input 最终的量(并不是变化量)      // 向服务器发请求,修改数量      switch (type) {        // 加号        case "plus": {          // 带给服务器变化的量          disNum = 1;          break;        }        // 减号        case "mins": {          // 判断产品的数量是否大于1,大于1才能减,传递给服务器-1          if (cart.skuNum > 1) {            disNum = -1;          } else {            // 产品的个数小于等于1,传递给服务器的个数为 0            disNum = 0;          }          break;        }        // input框        case "change": {          // 如果input框里面输入的不是数字,或者小于1,则带给服务器的数字是0          if (isNaN(disNum) || disNum < 1) {            disNum = 0;          } else {            // 如果输入的是小数,将你最终输入的取整,然后减去最初的数量,就是你要传的变化量disNum            disNum = parseInt(disNum) - cart.skuNum;          }          break;        }      }      // 派发action 发请求      try {        // 代表的是修改购物车中产品数量修改成功        await this.$store.dispatch("detail/addOrUpdateShopCart", {          skuId: cart.skuId,          skuNum: disNum,        });        // 再一次获取服务器的最新数据进行展示        this.getData();      } catch (error) {        // 修改失败        alert(error.message);      }    },
    1. 引入节流

      // 最好的引入方式:按需加载import throttle from "lodash/throttle";
    2. 整理代码

          // 购物车里 修改某一个产品的个数,使用【节流】    handler: throttle(async function (type, disNum, cart) {//  原来代码    }, 500),

删除购物车产品

  1. 写api接口

    //     src/api/index.js// 删除购物车商品数据    /api/cart/deleteCart/{skuId}   DELETE  带参数skuIdexport const reqDeleteCartById=(skuId)=>{    return requests({url:`/cart/deleteCart/${skuId}`,method:'DELETE'})}  
  2. 写仓库vuex,去购物车的仓库里写

    这里就不用拿数据了,因为是删除数据!!!

    const actions = {    // 删除购物车商品的数据    async deleteCartListBySkuId({ commit }, skuId) {        let result = await reqDeleteCartById(skuId);        if (result.code == 200) {            return 'ok'        } else {            return Promise.reject(new Error('删除购物车商品失败'))        }    }};

    点击 删除 的时候 派发action,需要传参,当前商品的skuId

      <li class="cart-list-con7">            <a class="sindelet" @click="deleteCartById(cart)">删除</a>            <br />            <a href="#none">移到收藏</a>  </li>

    删除购物车商品的操作

    如果删数据成功,则发请求显示新的数据,如果失败弹窗失败信息

        // 删除购物车商品的操作    async deleteCartById(cart) {      // 如果删数据成功,则发请求显示新的数据,如果失败弹窗失败信息      try {        await this.$store.dispatch(          "shopcart/deleteCartListBySkuId",          cart.skuId        );        //  再次发请求显示新的数据        this.getData();      } catch (error) {        alert(error.message);      }    },

修改产品的勾选状态

每次修改完状态也是要向服务器发请求的

  1. 写api接口

    // 切换商品的选中状态    /api/cart/checkCart/{skuId}/{isChecked}  GET请求 带参数export const reqUpdateCheckedById = (skuId, isChecked) => {    return requests({ url: `/cart/checkCart/${skuId}/${isChecked}`, method: 'GET' })}
  2. vuex—-派发actions 与 仓库捞到数据 (不返回数据)

    多个参数的话,多个参数用{}对象包裹!!

    注意!不能只传个参数 cart ,因为isChecked不变的,它获取的是后台的数据,一直是1

    可以用event.target.checked获取当前元素的checked属性

       // src/pages/ShopCart/index.vue <li class="cart-list-con1">            <input        type="checkbox"        name="chk_list"        :checked="cart.isChecked == 1"        @change="updateChecked(cart, $event)"      /> </li>.....<script>  // 修改购物车是否勾选状态  async updateChecked(cart, event) {      try {        let checked = event.target.checked " />

    • 我们现在解决了 当上面的勾选框全都勾选上了,下面的全选按钮全都勾选上

    • 但是没有解决 当下面全选按钮勾选上的时候,上面的也都要勾选上

      ​ 1. 修改产品的勾选状态 这个请求 一次只能勾选一个,而我们是要全部产品勾选上,所以我们可以多发几次请求。
      2. 当点击 【全选】的时候,派发一个actions,去调用一个函数【这个函数可以多次发请求修改产品的勾选状态】,这个函数 我们要执行【遍历上面所有产品,如果勾选框勾上了–不动,如果勾选框没勾上—发请求修改产品的勾选状态】

    1. 给【全选】绑定事件

      <div class="select-all">        <input class="chooseAll" type="checkbox" :checked="isAllChecked" @change="updateAllCartChecked" />        <span>全选</span></div>
    2. 派发actions

      问:需要传参数吗

      答:需要,因为我们发请求的时候需要商品的ID和isChecked,而我们对商品的isChecked修改是根据 【全选】的checked来修改。如果全选的checked为1,那么商品的isChecked为1,否则就是0。所以我们需要拿到当前状态该【全选】的checked值

       // 修改全部产品的勾选状态 updateAllCartChecked(event){      // console.log(event.target.checked); // 返回的是布尔值       let isChecked = event.target.checked ? "1" : "0"; // 我们发请求需要的是布尔值      // 派发actions       this.$store.dispatch('shopcart/updateAllCartIsChecked',isChecked) }
    3. vuex,多调用几次修改产品勾选状态的函数

      我们拿state里面的数据state.cartList.cartInfoList 里面才是我们购物车里商品的数据

       // 修改全部商品的勾选状态 updateAllCartIsChecked({ dispatch, getters }, isChecked) {        let promiseAll = [];        // 遍历购物车里面的所有商品        getters.cartList.cartInfoList.forEach(item => {            // 向修改产品的勾选状态发请求,带两个参数,第二个参数是【全选】的勾选状态            // 目的:让购物车里面的商品勾选状态与全选一致            let promise = dispatch('updateCheckedById', { skuId: item.skuId, isChecked: isChecked });            promiseAll.push(promise); // 将每一次返回的promise添加到数组promiseAll当中        });        // 最终返回的结果  Promise.all只要全部的商品都修改成功,返回结果就是成功        return Promise.all(promiseAll); },
    4. 最后我们用try...catch,如果成功,则再次发请求获取购物车列表,如果失败,报错

       // 修改全部商品的勾选状态 updateAllCartIsChecked({ dispatch, getters }, isChecked) {        let promiseAll = [];        // 遍历购物车里面的所有商品        getters.cartList.cartInfoList.forEach(item => {            // 向修改产品的勾选状态发请求,带两个参数,第二个参数是【全选】的勾选状态            // 目的:让购物车里面的商品勾选状态与全选一致            let promise = dispatch('updateCheckedById', { skuId: item.skuId, isChecked: isChecked });            promiseAll.push(promise); // 将每一次返回的promise添加到数组promiseAll当中        });        // 最终返回的结果  Promise.all只要全部的商品都修改成功,返回结果就是成功        return Promise.all(promiseAll); },
    5. 解决小问题:当购物车里面商品没有的时候,全选框不可点击且不勾选上

      cartInfoList.length > 0当购物车商品数组长度>0才勾选上

      :disabled="cartInfoList == 0"当购物车商品数组长度=0,不能被点击

      <div class="select-all">        <input          class="chooseAll"          type="checkbox"          :checked="isAllChecked && cartInfoList.length > 0"          :disabled="cartInfoList == 0"          @change="updateAllCartChecked"        />        <span>全选</span></div>

    十、完成登录与注册页面

    1. 登录与注册的静态组件完成
    2. 注册业务的完成
    3. 登录业务的完成

    登录与注册的静态组件完成

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

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

    注册业务的完成

    • 主页业务 | 登录业务中表单验证先不处理【最后一天统一处理】

    业务步骤:

    1. 看到注册页面,你会 【输入手机号】,然后点击【获取验证码】,验证码会发到你的手机上,然后你【输入验证码】---------向手机发送验证码
      1. 需要知道你输入的手机号,所以给手机号输入框双向绑定v-model
      2. 点击【获取验证码】,会向服务器发请求,给你的手机发验证码,需要写api接口发请求
      3. 需要知道你输入的验证码是否正确,所以给验证码输入框双向绑定

    向手机发送验证码

    /api/user/passport/sendCode/{phone} 获取验证码接口,GET

    1. 写api接口—获取验证码

      // 获取验证码接口    /api/user/passport/sendCode/{phone} GET export const reqGetCode = (phone) =>{    return requests({url:`/user/passport/sendCode/${phone}`,method:'GET'})}
    2. vuex 写仓库 --------我们把登录和注册的仓库写一起

      1. 写仓库

      2. 引入仓库与注册仓库

    3. 派发action 来获取验证码

      //   src/pages/Register/index.vue <input type="text" placeholder="请输入验证码" v-model="code" /> <button style="width: 100px; height: 38px" @click="getCode">获取验证码</button>... methods: {    // 获取验证码    async getCode() {      try {        // 简单判断一下---至少有数据        this.phone && (await this.$store.dispatch("user/getCode", this.phone));        // 如果获取到验证码,让验证码自动填写,将组件中的code变为我们的验证码        this.code = this.$store.state.user.code;      } catch (error) {        alert(error.message);      }    },  },
    4. VUEX来存储验证码

      业务:获取验证码的接口,把验证码返回存储到state中,然后将【接收到的验证码】返回到组件里的验证码,这样点击【获取验证码】的时候,就可以自动返回并填写验证码了

      ------------正常情况是,后台把验证码发到用户手机上【省钱】,但我们后台弄不了

      const state = {    // 验证码    code:'', // 验证码起始状态是字符串}const mutations = {    GETCODE(state,code){        state.code = code;    }}const actions = {    // 获取验证码    async getCode({ commit }, phone) {        // 获取验证码的接口,把验证码返回,但是正常情况,后台把验证码发到用户手机上【省钱】        let result = await reqGetCode(phone) // result.data是验证码        if(result.code==200){            // 存储验证码,因为后台不能把验证码发到用户手机上            commit('GETCODE',result.data)            return 'ok'        }else{            return Promise.reject(new Error('发送验证码失败咯'))        }    },}

    登录和确认密码

    业务步骤

    1. 写登录密码和确认密码,这两个密码要一致,然后勾选同意协议,才能进行注册
      1. 要知道密码,所以我们给密码的input框进行【双向绑定】
      2. 要确保勾选框是勾上的,所以data里面新加agree:true
      <div class="content">        <label>登录密码:</label>        <input type="password" placeholder="请输入你的登录密码" v-model="password"/>        <span class="error-msg">错误提示信息</span>  </div>  <div class="content">        <label>确认密码:</label>        <input type="password" placeholder="请输入确认密码" v-model="password1"/>        <span class="error-msg">错误提示信息</span>  </div>  <div class="controls">        <input name="m1" type="checkbox" :checked="agree"/>        <span>同意协议并注册《尚品汇用户协议》</span>        <span class="error-msg">错误提示信息</span>  </div>
    data() {    return {      // 收集表单数据---手机号      phone: "",      // 验证码      code: "",      // 密码      password:'',      // 确认密码      password1:'',      // 是否同意协议      agree:true  };

    点击 完成注册

    点击 完成注册,发请求进行注册,注册成功后 跳转到登录页面

    1. 写api请求 /api/user/passport/register POST 带参数

      // 注册用户接口   /api/user/passport/register POST 带参数export const reqUserRegister = (data) => {    return requests({ url: '/user/passport/register', method: 'POST', data })}
    2. vuex写仓库

      变量的解构赋值

      const {phone} = this;解构赋值后面必须有个;

      这样就不用每次this.phone,可以直接phone

      1. 派发action

         <div class="btn">        <button @click="userRegister">完成注册</button> </div>
            // 用户注册--发请求async userRegister() {    try {        // 如果成功----路由跳转          // 变量的解构赋值        const { phone, code, password, password1 } = this;        // 判断phone,code存不存在,以及密码和确认密码是否相等        //前面都为真的话就开始派发action        phone && code && password == password1 &&          (await this.$store.dispatch("user/userRegister", {            phone,            code,            password,          }));        // 如果成功就跳转路由---登陆页面        this.$router.push("/login");    } catch (error) {        alert(error.message);    }},
      2. VUEX

        // 完成用户注册async userRegister({ commit }, user) {        let result = await reqUserRegister(user);        if(result.code==200){            return 'ok'        }else{            return Promise.reject(new Error('用户注册失败咯'))        }},

    表单验证

    拿【登陆密码】为例子

     <div class="content">   <label>登录密码:</label>   <input       type="password"       placeholder="请输入你的登录密码"       v-model="password"       @change="testPassword"   />   <span class="error-msg" style="display: none" ref="passwordError">错误提示信息</span></div>

    登陆密码是 6-16位的数字或字母或 - 或 _

    用正则reg.test(value) reg是规则,value 是我们要验证的

    如果验证成功返回的true,就让错误提示信息隐藏

    验证错误,就要错误提示信息显示

     // 表单验证登陆密码 testPassword(){      let regPassword = /^[a-zA-Z0-9-_]{6,16}$/;  //6-16位的数字字母-和_      if(!regPassword.test(this.password)){        // 表单验证失败        this.$refs.passwordError.style.display = "block";        this.$refs.passwordError.innerHTML = "密码格式不正确,请输入6-16位的数字字母-和_";      }else{        this.$refs.passwordError.style.display = "none";      } },

    手机号码: /^1[3|4|5|7|8][0-9]{9}$/
    QQ: [1-9][0-9]{4,} (腾讯QQ号从10000开始)
    昵称是中文: ^[\u4e00-\u9fa5]{2,8}$ 从第一个汉字到第二个汉字 2-8位
    短信验证码:^\d{6}$ 六位数字
    密码:^[a-zA-Z0-9-_]{6,16}$ 6-16位的数字字母-和_

    具体的正则

    1. 写api /api/user/passport/login POST 带参数

      // 登录接口   /api/user/passport/login  POST  带参数export const reqUserLogin = (data) =>{    return requests({url:'/user/passport/login',method:'POST',data})}
    2. VUEX

      const state = {    code: '', // 验证码起始状态是字符串    token:'', // 用户的唯一标识}const mutations = {    USERLOGIN(state,token){        state.token = token;    }}const actions = {...    // 完成用户登录    async userLogin({ commit }, data) {       let result = await reqUserLogin(data);        // 服务器下发token,用户唯一标识(相当于uuid)        // 将来经常带着token找服务器要用户的信息 进行展示        if(result.code==200){            // 存储token            commit('USERLOGIN',result.data.token);            return 'ok';        }else{            return Promise.reject(new Error('用户登录失败咯'))        }    },}
    3. 派发action

       // 登录的回调函数 async userLogin() {      try {        const { phone, password } = this;        // 派发action        phone && password && await this.$store.dispatch("user/userLogin", { phone, password });        // 进行路由跳转---home主页        phone && password && this.$router.push('/home')      } catch (error) {        alert(error.message)      } },

      token -------- 是用户的唯一标识

      注册-----通过数据库存储用户信息(名字,密码)

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

      前台会持久化 存储token【带者token找服务器要用户信息进行展示】

      vuex仓库存储数据------不是持久化

    十一、登录过后

    首页用户信息的展示

    1. 用户注册完成-----用户点击登录【用户名+密码】向服务器发请求(组件派发action:userLogin),登陆成功获取到token,存储到仓库当中(非持久化),然后路由跳转到home首页
    2. 跳转到home首页,home首页在挂载的时候就向服务器发请求(组件派发action:getUserInfo),用token校验获取用户登录信息,然后动态的将用户信息展示到Header中
    3. 一刷新home首页,首页获取不到用户信息(token:vuex非持久化存储)

    Header组件显示用户名与退出登录

    1. 写api

      添加了token校验获取用户登录信息,用户登录只保存用户的token

      token校验 http://182.92.128.115/api/user/passport/auth/getUserInfo

      // 获取用户的信息【需要带着用户的token向服务器要用户信息】api/user/passport/auth/getUserInfo// 用请求头带过去参数export const reqUserInfo = () =>{    return requests({url:'/user/passport/auth/getUserInfo',method:'GET'})}
    2. vuex三连环,存储数据

       //   src/store/user/index.jsconst state = {    code: '', // 验证码起始状态是字符串    token: '', // 用户的唯一标识    userInfo:{}, // 用户的信息}const mutations = {    GETUSERINFO(start,userInfo){        state.userInfo = userInfo;    }}const actions = {    // 获取用户信息【token】    async getUserInfo({ commit }) {        let result = await reqUserInfo();        // 开始存储用户信息        if(result.code==200){            commit('GETUSERINFO',result.data)            return 'ok'        }    }}
    3. 请求拦截器—需要携带token带给服务器

      //    src/api/requests.js// 请求拦截器:在发请求之前,请求拦截器可以检测到,可以在请求发出去之前做一些事情requests.interceptors.request.use((config) => {    // config:配置对象,对象里面有一个属性很重要:headers请求头    if (store.state.detail.uuid_token) {        // 给请求头添加一个字段(userTempId):和后台老师商量好        config.headers.userTempId = store.state.detail.uuid_token    }    // 需要携带token携带给服务器    if(store.state.user.token){        config.headers.token = store.state.user.token;    }    // 进度条开始动    nprogress.start();    return config;})
    4. 将仓库里的数据进行展示

      Header组件

      如果登陆成功,有用户信息的话,将用户信息展示this.$store.state.user.userInfo.name

      //   原形式 <div class="loginList">     <p>尚品汇欢迎您!</p>     <p>          <span></span>                    <router-link to="/login">登录</router-link>          <router-link to="/register" class="register">免费注册</router-link>     </p> </div>

      现在形式

       <div class="loginList">          <p>尚品汇欢迎您!</p>                    <p v-if="!userName">            <span></span>                        <router-link to="/login">登录</router-link>            <router-link to="/register" class="register">免费注册</router-link>          </p>                    <p v-else>            <a>{{userName}}</a>            <a class="register">退出登录</a>          </p> </div>....<script>computed:{    // 用户名信息    userName(){     return this.$store.state.user.userInfo.name;    }}</script>

      注意!一刷新的话vuex存储的信息就没了,vuex非持久化存储,一刷新仓库里的 token就没有了

    持久化存储token

    在用户完成登录的时候,服务器发给我们token,我们将token本地存储起来,然后将state里面token,读取本地存储里的token

    //  src/utils/token.js// 本地存储token// 存储tokenexport const setToken = (token) => {    localStorage.setItem('TOKEN', token)}// 获取tokenexport const getToken = () =>{    return localStorage.getItem('TOKEN')}
    //   src/store/user/index.jsimport { setToken,getToken } from '@/utils/token'const state = {    code: '', // 验证码起始状态是字符串    token: getToken(), // 用户的唯一标识    userInfo: {}, // 用户的信息}...// 完成用户登录    async userLogin({ commit }, data) {        let result = await reqUserLogin(data);        // 服务器下发token,用户唯一标识(相当于uuid)        // 将来经常带着token找服务器要用户的信息 进行展示        if (result.code == 200) {            // 存储token            commit('USERLOGIN', result.data.token);            // 持久化存储token            setToken(result.data.token);            return 'ok';        } else {            return Promise.reject(new Error('用户登录失败咯'))        }    },

    这样就持久化存储token了

    但是存在问题:

    1. 多个组件要展示用户信息,需要在每一个组件的mounted中 触发获取用户信息进行展示 this.$store.dispatch('user/getUserInfo'),要不然进入到其他组件刷新会不展示用户信息
    2. 用户已经登陆了,用户再点击 登录 不能跳转到登陆页面

    退出登录

    ​ 向服务器发请求清空用户信息,清除后台token和前台的一些关于用户的数据,然后退出成功后跳转到首页

    1. 点击【退出登录】

       <p v-else>       <a>{{userName}}</a>       <a class="register" @click="logout">退出登录</a> </p>
    2. 发请求通知服务器要退出登录【清除一些数据:token】

      /api/user/passport/logout GET 无参数

      1. 写api

        // 退出登录 /api/user/passport/logout   GET  无参数export const reqLogout = () => {    return requests({ url: '/user/passport/logout', method: 'GET' })}
      2. vuex

        //   src/store/user/index.jsconst state = {    code: '', // 验证码起始状态是字符串    token: getToken(), // 用户的唯一标识    userInfo: {}, // 用户的信息}const mutations = {    // 清除本地的数据    CLEAR(state) {        // 把仓库中相关用户信息清空 以及 本地存储数据清空        state.token = '';        state.userInfo = {};        removeToken();    }}const actions = {    // 退出登录,向服务器发请求,通知服务器清除服务器的token    async userLogout({ commit }) {        let result = await reqLogout();        // 发请求成功,清除前台的数据        if (result.code == 200) {            commit('CLEAR');            return 'ok'        }else{            return Promise.reject(new Error('退出登录失败'))        }    }}
        //  src/utils/token.js// 本地存储token// 存储tokenexport const setToken = (token) => {    localStorage.setItem('TOKEN', token)}// 获取tokenexport const getToken = () =>{    return localStorage.getItem('TOKEN')}// 清除本地存储的tokenexport const removeToken = ()=>{    localStorage.removeItem('TOKEN')}
      3. 退出登录后 跳转到home首页

        //   src/components/Header/index.vuemethods:{    // 退出登录    async logout() {      try {        // 退出登录 清空数据 派发action        await this.$store.dispatch("user/userLogout");        // 退出成功,回到首页        this.$router.push('/home')      } catch (error) {        alert(error.message)      }    },}

    导航守卫

    现在的问题:

    1. 用户已经登录,用户不应该可以去到login登录页面
    2. 用户没有登陆,用户不能去购物车页面
  3. 配置路由

    //      src/router/index.js//    引入路由组件import Trade from '../pages/Trade'...// 配置路由let router = new VueRouter({    // 配置路由    routes: [        ....        {            name: 'trade',            path: '/trade',            component: Trade,            meta: { showFooter: true },        },        // 重定向:在项目跑起来的时候,访问/,立马让他定向到首页!!!        {            path: '*',            redirect: '/home',        }    ],    // 控制滚动条的滚动行为    scrollBehavior(to, from, savedPosition) {        // return 期望滚动到哪个的位置        return { y: 0 }; // 始终滚动到顶部    }})

获取交易页数据

  1. 获取用户地址信息–写api

    /api/user/userAddress/auth/findUserAddressList 获取用户地址信息 GET

    // 获取用户地址信息   /api/user/userAddress/auth/findUserAddressList  GETexport const reqAddressInfo = () =>{    return requests({url:'/user/userAddress/auth/findUserAddressList',method:'GET'})}

    获取商品清单信息–写api

    /api/order/auth/trade 获取订单交易页【商品清单信息】 GET

    // 获取订单交易页 商品清单信息 /api/order/auth/trade GET 无参数export const reqOrderInfo = () =>{    return requests({url:'/order/auth/trade',method:'GET'})}
  2. 建仓库trade

    //   src/store/trade/index.js// 交易页面的仓库import { reqAddressInfo } from '@/api/index'const state = {};const mutations = {};const actions = {};const getters = {};export default {    // 开启命名空间    namespaced: true,    state,    mutations,    actions,    getters,}

    在大仓库引入小仓库

  3. vuex存储数据

    // 交易页面的仓库import { reqAddressInfo, reqOrderInfo } from '@/api/index'const state = {    address: [], // address至少是个空数组    orderInfo: {}, // 至少是个空对象};const mutations = {    GETUSERADDRESS(state, address) {        state.address = address;    },    GETORDERINFO(state, orderInfo) {        state.orderInfo = orderInfo;    }};const actions = {    // 获取用户地址信息    async getUserAddress({ commit }) {        let result = await reqAddressInfo();        if (result.code == 200) {            commit('GETUSERADDRESS', result.data)     }    }, // 获取商品清单信息    async getOrderInfo({ commit }) {        let result = await reqOrderInfo();        console.log(result);        if (result.code == 200) {            commit('GETORDERINFO', result.data)        }    }};

    当交易页面挂载完毕的时候就开始派发action请求用户地址信息

    <script>  export default {    name: 'Trade',    mounted(){      // 组件挂载完毕,就要获取用户的地址信息      this.$store.dispatch('trade/getUserAddress')      // 获取商品清单列表      this.$store.dispatch('trade/getOrderInfo')    },  }</script>

动态渲染数据

渲染用户地址信息

当数据中的 isDefault为1 是,才是默认地址,所以收件人会有边框以及地址后面跟着【默认地址】

动态绑定【类名】以及【默认地址的出现】

:class="{ selected: addressInfo.isDefault == 1 }"

<div class="address clearFix" v-for="addressInfo in address" :key="addressInfo.id">        <span class="username" :class="{ selected: addressInfo.isDefault == 1 }" >{{ addressInfo.consignee }}</span>        <p @click="changeDefault(addressInfo,address)">          <span class="s1">{{ addressInfo.fullAddress }}</span>          <span class="s2">{{ addressInfo.phoneNum }}</span>          <span class="s3" v-show="addressInfo.isDefault == 1">默认地址</span>        </p></div>

当你点击谁 谁就是默认地址------【排他思想】先让全部的地址的isDefault为0,然后让你点击的为1

@click="changeDefault(addressInfo,address)"

要传两个参数,一个是当前点击的地址,一个是所有的地址列表

 // 修改默认地址 changeDefault(addressInfo,address){ // addressInfo你点击的--address全部地址列表      // 排他思想----先让全部的地址的isDefault为0,然后让你点击的为1      address.forEach(item => {        item.isDefault = 0;      });      addressInfo.isDefault = 1 },

当上面修改了默认地址后,将来提交订单最终选中的地址也要改变—所以我们计算出最终选中的地址

这里计算最终地址我们用find方法,可以直接将符合条件的元素返回

  import { mapState } from "vuex";...  computed: {    ...mapState("trade", ["address"]), // state仓库下的 trade小仓库里面的address数据    // 计算出将来最终要提交的选中的地址    userDefaultAddress() {      // find:查找数组当中符合条件的元素返回      return this.address.find(item => item.isDefault==1) || {};// 至少是个空对象    },  },

将计算出的地址进行渲染

    <div class="trade">      <div class="price">应付金额: <span>¥5399.00</span></div>      <div class="receiveInfo">        寄送至:        <span>{{userDefaultAddress.fullAddress}}</span>        收货人:<span>{{userDefaultAddress.consignee}}</span>        <span>{{userDefaultAddress.phoneNum}}</span>      </div>    </div>

渲染商品清单信息

将仓库里的数据捞到组件里面

// src/pages/Trade/index.vue  import { mapState } from "vuex";  computed: {    ...mapState("trade", ["address","orderInfo"]),    // state仓库下的 trade小仓库里面的address和orderInfo数据  }

开始渲染数据

  <ul class="list clearFix" v-for="order in orderInfo.detailArrayList" :key=order.skuId>       <li>            <img :src="order.imgUrl" style="width:100px;height:100px"/>       </li>       <li>            <p>{{order.skuName}}</p>            <h4>7天无理由退货</h4>       </li>       <li>            <h3>¥{{order.orderPrice}}</h3>       </li>       <li>X{{order.skuNum}}</li>       <li>有货</li> </ul>

收集买家的留言信息

买家留言是要收集然后并上传的,所以动态绑定 买家留言

  <div class="bbs">     <h5>买家留言:</h5>     <textarea placeholder="建议留言前先与商家沟通确认" class="remarks-cont" v-model="msg"></textarea> </div>...<script>data(){    return {      // 收集买家留言的信息      msg:'',    }},</script>

渲染底部总信息

购物车里的总商品数量和金额

<li>     <b><i>{{orderInfo.totalNum}}</i>件商品,总商品金额</b>     <span>¥{{orderInfo.totalAmount}}</span></li>...<div class="price">应付金额: <span>¥{{orderInfo.totalAmount}}</span></div>

十三、完成提交订单业务

点击 【交易页面】中的【提交订单】,向服务器发请求【把支付的一些信息传递给服务器】,然后跳转到【支付页面】

    <div class="sub clearFix">      <a class="subBtn" to="/pay" @click="submitOrder">提交订单</a>    </div>

完成支付页面的静态组件

  1. 引入静态组件

  2. 配置路由

    // src/router/index.jsimport Pay from '../pages/Pay' ...// 配置路由let router = new VueRouter({    // 配置路由    routes: [        {            name: 'pay',            path: '/pay',            component: Pay,            meta: { showFooter: true },        },        // 重定向:在项目跑起来的时候,访问/,立马让他定向到首页!!!        {            path: '*',            redirect: '/home',        }    ],    // 控制滚动条的滚动行为    scrollBehavior(to, from, savedPosition) {        // return 期望滚动到哪个的位置        return { y: 0 }; // 始终滚动到顶部    }})

提交订单发起请求

当你点击【提交订单】的时候,向服务器发送你的一些交易页面的数据,获取到你的订单号

提交订单发请求—写api

这里tradeNo 当query参数拼接在路径中,其余参数都写在data对象里面

//  src/api/index.js// 提交订单的接口  /api/order/auth/submitOrder" />

//  src/main.js // 统一接收api文件夹里面全部请求函数import *as API from '@/api'new Vue({  render: h => h(App),  // 注册路由信息:当这里书写router的时候,组件身上都拥有$route,$router属性  router,  // 注册仓库:组件实例的身上会多一个属性$store属性  store,  // 配置全局事件总线$bus  beforeCreate() {    Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm    Vue.prototype.$API = API; // 以后你使用请求接口的时候可以 this.$API.xxx  },}).$mount('#app')

当点击【提交订单】开始发请求

  <div class="sub clearFix">      <a class="subBtn" to="/pay" @click="submitOrder">提交订单</a> </div>

开始发请求

返回 成功后的

{  // 返回result  "code": 200, "message": "成功", "data": 71,   // orderId 订单号 "ok": true}
 // 提交订单发请求 async submitOrder() {      // 发请求,需要带参数tradeNo      let { tradeNo } = this.orderInfo; // 解构赋值!!!可以直接用this.orderInfo的tradeNo      // 其余的六个参数      let data = {        consignee: this.userDefaultAddress.consignee, // 收件人姓名        consigneeTel: this.userDefaultAddress.phoneNum, // 收件人电话        deliveryAddress: this.userDefaultAddress.fullAddress, // 收件人地址        paymentWay: "ONLINE", // 支付方式        orderComment: this.msg, // 订单备注        // 存储多个商品对象的数组        orderDetailList: this.orderInfo.detailArrayList,      };      // 开始发请求      let result = await this.$API.reqSubmitOrder(tradeNo, data);      console.log(result);       },

请求成功后 将数据存储到组件的data里面

      // 开始发请求      let result = await this.$API.reqSubmitOrder(tradeNo, data);      console.log("提交订单", result);      if (result.code == 200) {        // 提交订单成功        // 存储数据        this.orderId = result.data;        // 进行跳转,把订单号传参过去        this.$router.push("/pay?orderId=" + this.orderId);      } else {        alert(result.data);      }
data() {    return {      // 收集买家留言的信息      msg: "",      // 订单号      orderId: "", };

在支付页面展示数据

展示订单号

<span class="fl">请您在提交订单<em class="orange time">4小时</em>之内完成支付,超时订单会自动取消。订单号:<em>{{orderId}}</em></span>
 computed:{      // 订单号      orderId(){        return this.$route.query.orderId;      } },

展示应付金额

要拿到应付金额,要拿着订单号向服务器发请求,捞到你这次支付的信息

//  写api 发请求// 获取订单支付信息 /api/payment/weixin/createNative/{orderId} 带参数 GETexport const reqPayInfo = (orderId) => {    return requests({ url: `/payment/weixin/createNative/${orderId}`, method: 'GET' })}

当你路由跳转到【支付页面】的时候,你会拿到【订单号】,然后你就要拿着【订单号】去发请求捞到【支付金额】,所以是你在【支付页面挂载】的时候就要开始发请求获取订单支付信息

在组件里发请求完后 就要等待你的数据await,正常情况是async

但是!生命周期函数中不要使用async!!!!!!!!!!!!!!!!!!!!!!!!

//   src/pages/Pay/index.vue  data() {    return {      payInfo: {}, // 至少是个对象    };  mounted() {    // 发请求 获取订单支付信息    this.getPayInfo();  },  methods: {    async getPayInfo() {      let result = await this.$API.reqPayInfo(this.orderId);      if (result.code == 200) {        // 存储支付信息        this.payInfo = result.data;      }    },  },
//   开始展示数据 <span class="fr">   <em class="lead">应付金额:</em>   <em class="orange money">¥{{payInfo.totalFee}}</em> </span>

点击立即支付-elementUI

点击【立即支付】会弹出一个【遮罩层】

  • 我们这里用elementUI
  1. 安装elementUI【我们按需引入】

    npm install babel-plugin-component -D

  2. 然后,将 .babelrc 修改为:

    module.exports = {  presets: [    '@vue/cli-plugin-babel/preset'  ],  "plugins": [    [      "component",      {        "libraryName": "element-ui",        "styleLibraryName": "theme-chalk"      }    ]  ]}
  3. 我们这里用这个

  4. 注册组件

    //  main.js// 引入elementUI--按需引入import { MessageBox,xxxx } from 'element-ui'// 引入样式import 'element-ui/lib/theme-chalk/index.css';// 注册组件的时候---可以挂载在原型上// Vue.use(xxxxx)Vue.prototype.$msgbox = MessageBox;Vue.prototype.$alert = MessageBox.alert;
  5. 点击【立即支付】以后

      <div class="submit">    <a class="btn" @click="open">立即支付</a>  </div>
     // 遮罩层/弹出框 open() {      this.$alert("二维码", "HTML 片段", {        dangerouslyUseHTMLString: true, // 是否将 message 属性作为 HTML 片段处理        center:true, // 中间布局        showCancelButton:true, // 是否显示取消按钮        cancelButtonText:'支付遇见问题', // 取消按钮的文本内容        confirmButtonText:'已支付成功', // 确定按钮的文本内容        showClose:false, // MessageBox 是否显示右上角关闭按钮      }); },

现在样式是这样

微信支付业务

二维码生成 qrcode

qrcode - npm npm官网

安装QRCODE

npm i qrcode

 // 遮罩层/弹出框 async open() {      // 生成二维码地址      let url = await QRCode.toDataURL(this.payInfo.codeUrl);      this.$alert(`

支付后

我们要知道他是支付成功 | 失败

  1. 支付成功,进行路由的跳转
  2. 支付失败,提示信息

怎么知道他现在处于什么支付状态呢?

​ 当 弹出框一弹出来,就开始一直发请求 知道你的订单状态

查询支付订单的状态 /api/payment/weixin/queryPayStatus/{orderId} GET 带参数

//   写api// 查询支付订单的状态  /api/payment/weixin/queryPayStatus/{orderId}  GET  带参数export const reqPayStatus = (orderId) => {    return requests({ url: `/payment/weixin/queryPayStatus/${orderId}`, method: 'GET' })}

支付成功

支付成功—code==200

  1. 要清除定时器
  2. 保存支付成功返回的code,之后点击【支付成功】按钮时 要判断你的code==200,然后进行路由跳转
  3. 关闭支付弹出框
  4. 如果不点击【支付成功】按钮的话,你支付成功了,可以直接跳转到下一页路由
      // 你需要知道支付成功 | 失败      // 支付成功,路由的跳转;如果支付失败,提示信息      // 定时器没有,开启一个新的的定时器      if (!this.timer) {        this.timer = setInterval(async () => {          // 发请求获取 用户支付状态          let result = await this.$API.reqPayStatus(this.orderId);          // 支付成功          if(result.code == 200){            // 清除定时器            clearInterval(this.timer);            this.timer = null;            // 保存支付成功返回的code ,然后之后 点击支付成功 判断你的code==200,然后进行路由跳转            this.code = result.code;            // 关闭弹出框            this.$msgbox.close();            // 跳转到下一页路由            this.$router.push('/paysuccess');          }        }, 1000);      }

下一页路由-----支付成功页面

  1. 静态组件
  2. 配置路由

没有支付

没有支付,点击了【已支付成功】,弹出框不要消失,

beforeClose MessageBox 关闭前的回调,会暂停实例的关闭,有三个参数

​ // type:可以区分 取消 | 确定 按钮

​ // instance:当前组件实例

​ // done 关闭弹出框的方法

  1. 如果你点击了【支付遇见问题】,会弹框 让你联系管理员,清除定时器,关闭弹出框
  2. 如果你点击【已支付成功】,要判断是否真的支付成功了
    1. code==200,支付成功,关闭定时器和弹出框,跳转到下一路由
    2. code !==200,未支付成功,弹出 【未支付成功】,关闭定时器和弹出框
        beforeClose: (type, instance, done) => {          // type:可以区分 取消 | 确定 按钮          // instance:当前组件实例          // done 关闭弹出框的方法          // 关闭弹出框之前的配置          // 如果你点击了[支付遇见问题]          if (type == "cancel") {            alert("请联系管理员东方青苍");            // 清除定时器            clearInterval(this.timer);            this.timer = null;            // 关闭弹出框            done();          } else {            // 判断是否真的支付了            if (this.code == 200) {              // 关闭定时器              clearInterval(this.timer);              this.timer = null;              // 关闭弹出框              done();              // 跳转到下一页路由              this.$router.push("/paysuccess");            } else {              alert("未支付成功");              // 清除定时器              clearInterval(this.timer);              this.timer = null;              // 关闭弹出框              done();            }          }        },

完整代码

src/pages/Pay/index.vue

<script>import QRCode from "qrcode";export default {  name: "Pay",  data() {    return {      payInfo: {}, // 至少是个对象      timer: null,      code: "", // 支付的状态码    };  },  computed: {    // 订单号    orderId() {      return this.$route.query.orderId;    },  },  mounted() {    // 发请求 获取订单支付信息    this.getPayInfo();  },  methods: {    // 获取支付的信息    async getPayInfo() {      let result = await this.$API.reqPayInfo(this.orderId);      if (result.code == 200) {        // 存储支付信息        this.payInfo = result.data;      }    },    // 遮罩层/弹出框    async open() {      // 生成二维码地址      let url = await QRCode.toDataURL(this.payInfo.codeUrl);      this.$alert(`

完成静态组件

  1. 将组件引入到文件夹下

  2. 配置路由

    //   src/router/index.js// 引入组件import Center from '../pages/Center'// 配置路由组件let router = new VueRouter({    // 配置路由    routes: [        {            name:'center',            path:'/center',            component:Center,            meta: { showFooter: true },        },    ]})

个人中心二级路由搭建

想给Center组件加上二级路由 :

  1. 当你点击 【我的订单】显示我的订单相关内容,.

  2. 当你点击【团购订单】 显示团购订单相关内容

步骤:

  1. 将子组件【myOrder】和【groupOrder】的静态组件放到【Center】文件夹下

  2. 配置二级路由组件

    //  src/router/index.js// 引入二级路由组件import MyOrder from '../pages/Center/myOrder'import GroupOrder from '../pages/Center/groupOrder'{            name:'center',            path:'/center',            component:Center,            meta: { showFooter: true },            // 二级路由组件            children:[                {                    name:'myorder',                    path:'myorder',                    component:MyOrder,                },                {                    name:'grouporder',                    path:'grouporder',                    component:GroupOrder                },                // 重定向                {                    path:'/center',                    redirect:'/center/myorder'                }            ]        },
  3. 在父组件中引入–注册子组件

    //   src/pages/Center/index.vue<script>  // 引入子组件  import myOrder from './myOrder'  import groupOrder from './groupOrder'  export default {    name: 'Center',    // 注册局部组件    components:{      myOrder,      groupOrder    }  }</script>
  4. 展示子路由组件 出口的位置

    //   src/pages/Center/index.vue <dd><router-link to="/center/myorder">我的订单</router-link></dd> <dd><router-link to="/center/grouporder">团购订单</router-link></dd><router-view></router-view>

我的订单

我们要展示动态数据

发请求-捞数据-动态展示数据

发请求存储数据

/api/order/auth/{page}/{limit} GET 带参数

// 获取我的订单列表   /api/order/auth/{page}/{limit}   GET 带参数export const reqMyOrderList = (page, limit) => {    return requests({ url: `/order/auth/${page}/${limit}`, method: 'GET' })}

当【我的订单】开始挂载的时候,就要发次请求捞到数据,然后每点击下面的页数也要发次请求,所以封装成一个函数

<script>export default {  name: "myOrder",  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;      }    },  },};</script>

动态渲染数据

<div class="orders">      <table class="order-item" v-for="order in myOrder.records" :key=order.id>      <thead>        <tr>          <th colspan="5">            <span class="ordertitle">{{order.createTime}} 订单编号:{{order.outTradeNo}}              <span class="pull-right delete"><img src="../images/delete.png" /></span>             </span>          </th>        </tr>       </thead>       <tbody>                  <tr v-for="(cart,index) in order.orderDetailList" :key="cart.id">            <td width="60%">                <div class="typographic">                  <img :src="cart.imgUrl" style="width:100px;height:100px"/>                  <a href="#" class="block-text">{{cart.skuName}}</a>                  <span>x{{cart.skuNum}}</span>                  <a href="#" class="service">售后申请</a>                </div>            </td>                          <td :rowspan="order.orderDetailList.length" v-if="index==0" width="8%" class="center">{{order.consignee}}</td>            <td :rowspan="order.orderDetailList.length" v-if="index==0" width="13%" class="center">                <ul class="unstyled">                  <li>总金额¥{{order.totalAmount}}</li>                  <li>在线支付</li>                </ul>              </td>            <td :rowspan="order.orderDetailList.length" v-if="index==0" width="8%" class="center">                <a href="#" class="btn">{{order.orderStatusName}} </a>            </td>            <td :rowspan="order.orderDetailList.length" v-if="index==0" width="13%" class="center">                <ul class="unstyled">                  <li>                    <a href="mycomment.html" target="_blank">评价|晒单</a>                  </li>                </ul>            </td>         </tr>       </tbody>   </table></div>

  1. tbody里面的tr是每一行,每一行就是一个产品,td是每个单元格,我们要合并单元格,是根据该订单的数量来合并的

  2. 每一个tr是一个产品,但是一个订单可能会有多个产品,我们只需要遍历左边的,右边的只显示第一个就可以了

最后的结果:

分页器

咱们之前写过分页器组件,作为了全局组件,我们可以直接使用

            <div class="choose-order">        <Pagination          :pageNo="page"          :pageSize="limit"          :total="myOrder.total"          :continues="5"          @getPageNo="getPageNo"        ></Pagination>      </div>

pageNo :是当前在第几页

pageSize:是一页有几条数据

total:是一共有多少条数据

continues:是连续页数是几页

@getPageNo 是自定义事件,当你点击的时候,就跳转到第几页,把你点击的页数传给该函数

  // 获取当前点击的那一页  getPageNo(page){      // 修改组件响应式数据      this.page = page;      // 再次发请求      this.getData();  },

十五、鉴权判断

未登录的导航守卫判断

跳转到登陆过后—>导航守卫---->用户未登录后的页面

路由独享守卫

想要去交易trade页面,不能去从地址栏进行跳转,只能从【购物车页面】才能跳转到【交易页面】

同理,只能从【交易页面】跳转到【支付页面】

只能从【支付页面】跳转到【支付成功页面】

路由独享守卫,要写在路由配置项里面,写在你要【鉴权】的路由里面

next(false)中断当前路由,停留在当前页面

只能从【购物车页面】才能跳转到【交易页面】,就要看他来的路径是不是购物车,是的话就放行,不是的话就停留在当前页面

只能从【交易页面】跳转到【支付页面】 同理

        {            name: 'trade',            path: '/trade',            component: Trade,            meta: { showFooter: true },            // 路由独享守卫            beforeEnter: (to, from, next) => {                if (from.path == '/shopcart') {                    next();                } else {                    next(false); // 停留在当前页面                }            }        },        {            name: 'pay',            path: '/pay',            component: Pay,            meta: { showFooter: true },            // 路由独享守卫            beforeEnter: (to, from, next) => {                if (from.path == '/trade') {                    next();                } else {                    next(false); // 停留在当前页面                }            }        },        {            name: 'paysuccess',            path: '/paysuccess',            component: PaySuccess,            meta: { showFooter: true },            beforeEnter: (to, from, next) => {                if (from.path == '/pay') {                    next();                } else {                    next(false); // 停留在当前页面                }            }        },

组件内守卫

在组件里面写该守卫【.vue】

你可以为路由组件添加以下配置:

  • beforeRouteEnter
  • beforeRouteUpdate
  • beforeRouteLeave
const UserDetails = {  template: `...`,  beforeRouteEnter(to, from) {    // 在渲染该组件的对应路由被验证前调用    // 不能获取组件实例 `this` !    // 因为当守卫执行时,组件实例还没被创建!  },  beforeRouteUpdate(to, from) {    // 在当前路由改变,但是该组件被复用时调用    // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,    // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。    // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`  },  beforeRouteLeave(to, from) {    // 在导航离开渲染该组件的对应路由时调用    // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`  },}

beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。

不过,你可以通过传一个回调给 next 来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数:

beforeRouteEnter (to, from, next) {  next(vm => {    // 通过 `vm` 访问组件实例  })}

注意 beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdatebeforeRouteLeave 来说,this 已经可用了,所以不支持 传递回调,因为没有必要了:

beforeRouteUpdate (to, from) {  // just use `this`  this.name = to.params.name}

这个 离开守卫 通常用来预防用户在还未保存修改前突然离开。该导航可以通过返回 false 来取消。

beforeRouteLeave (to, from) { const answer = window.confirm('Do you really want to leave" />

dist就是我们最终的文件夹

项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确的知道是哪里的代码报错

有了map文件就可以像未加密的代码一样,准确的输出是哪一行哪一列有错。

所以,该文件 如果项目不需要 是可以去除掉

如何在打包前就去除掉?

在 vue.config.js 配置里面,加上 productionSourceMap:false

购买云服务器

项目要让别人访问,就放在服务器上

阿里云 腾讯云 可以购买

安全组设置

让服务器一些端口号打开

xshell链接服务器与linux指令

利用xshell工具登录服务器【需要用户名和密码】

nginx反向代理

麻了…到时候去看【尚硅谷VUE项目】P111的视频吧
这个需要深学!!