拉钩教育移动端项目实战 - 支付、打包优化

338 阅读4分钟

笔记来源:拉勾教育 - 大前端就业集训营

文章内容:学习过程中的笔记、感悟、和经验

支付、打包优化

支付功能

组件准备

支付组件设置为单独的组件

处理路由,实现点击、跳转支付页面,使用路径参数,需要登陆才能访问

检查是否登录,登陆了带着课程id跳转,没登录先跳转登录然后再回来课程详情页

//src/router/index.js  添加支付路由

{ // 支付购买
  name: 'pay',
  path: '/pay/:courseId',
  component: () => import(/* webpackChunkName: 'pay' */'@/views/pay'),
  // 设置路径参数
  props: true,
  meta: { login: true }
},
src/views/pay/index.vue  新建目录和文件,创建支付页面

<template>
  <div class="pay">支付组件</div>
</template>

<script>
export default {
  name: 'pay'
}
</script>
src/views/course-info/index.vue  给立即购买按钮添加点击事件,判断是否登录,根据是否登录判断是否需要跳转登陆页

<!-- 立即购买按钮添加点击事件跳转购买页 -->
<van-button type="primary" @click="goPay">立即购买</van-button>

// 立即购买点击事件
goPay () {
  if (this.$store.state.user) {
    // 如果已经登陆,直接带课程id跳转购买
    this.$router.push(`/pay/${this.courseId}`)
  } else {
    // 如果没登陆,跳转登陆,将要前往的页面路由保存为参数
    this.$router.push({
      name: 'login',
      query: {
        path: this.$route.fullPath
      }
    })
  }
}

布局处理

依旧可以使用单元格组件

设置基本结构、处理样式,最后支付方式撑满剩余高度

src/views/pay/index.vue  调用接口获取数据,设置顶部和中间结构和样式

<template>
  <!-- 单元格容器 -->
  <van-cell-group>
    <!-- 顶部课程信息 -->
    <van-cell>
      <!-- 课程图片 -->
      <img :src="courseInfo.courseImgUrl"/>
      <div>
        <!-- 课程名称 -->
        <p v-text="courseInfo.courseName"></p>
        <!-- 价格 -->
        <p>¥ {{courseInfo.discounts}}</p>
      </div>
    </van-cell>
    <!-- 中间用户信息 -->
    <van-cell>
      <p>购买信息</p>
      <p>购买课程后使用此账号登录【拉勾教育】学习课程</p>
      <!-- 当前用户手机号 -->
      <p>当前账号:{{phone}}</p>
    </van-cell>
    <!-- 支付信息 -->
    <van-cell>支付</van-cell>
  </van-cell-group>
</template>

<script>
// 引入接口:获取课程信息
import { getCourseById } from '@/api/course'
export default {
  name: 'pay',
  // 参数
  props: {
    // 路径参数
    courseId: {
      type: [Number, String],
      required: true
    }
  },
  data () {
    return {
      // 课程信息
      courseInfo: {}
    }
  },
  // 钩子函数
  created () {
    // 获取课程信息
    this.getCourse()
  },
  methods: {
    // 获取课程信息
    async getCourse () {
      // 调用接口
      const { data } = await getCourseById(this.courseId)
      // 获取成功将获取到的信息传递给data
      if (data.state === 1) this.courseInfo = data.content
    }
  },
  // 计算属性
  computed: {
    // 处理用户手机号
    phone () {
      // 使用replace正则替换中间四位为 *
      return this.$store.state.user.organization.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2')
    }
  }
}
</script>

<style lang="scss" scoped>
// 整体容器
.van-cell-group {
  display: flex;
  flex-direction: column;
  height: 100vh;
  background: #f8f9fa;
}
// 去掉所有单元格底部分割线
.van-cell::after {
  display: none;
}
// 顶部课程信息
.van-cell:nth-child(1) {
  padding: 40px 20px 0;
  margin-bottom: 10px;
  .van-cell__value {
    display: flex;
    height: 130px;
    img {
      width: 80px;
      height: 107px;
      border-radius: 10px
    }
    div {
      display: flex;
      height: 107px;
      box-sizing: border-box;
      flex-direction: column;
      justify-content: space-between;
      padding: 5px 20px;
      p {
        margin: 0;
      }
      p:nth-child(1) {
        font-size: 16px;
      }
      p:nth-child(2) {
        font-size: 22px;
        color: #ff7452;
        font-weight: 700;
      }
    }
  }
}
// 中间用户信息
.van-cell:nth-child(2) {
  padding: 10px 16px;
  margin-bottom: 10px;
  p {
    margin: 0;
  }
  p:nth-child(2) {
    font-size: 12px;
    color: #999;
  }
  p:nth-child(3) {
    margin: 20px 0 10px;
    font-size: 16px;
  }
}
// 底部支付信息
.van-cell:nth-child(3) {
  // 底部撑满剩余高度
  flex: 1;
}
</style>

绑定数据

初始化获取课程信息和用户信息,进行数据绑定

手机号直接使用vuex就可以了,通过计算属性进行处理 - replace替换

上面已经绑定了

支付结构处理

使用Vant单选框组件,在基础上进行样式结构处理,使用cell插槽

进行数据绑定

微信图标地址

支付宝图标地址

修改单选按钮颜色可能需要样式穿透(我这里直接使用组件的属性)

src/views/pay/index.vue  增加支付信息及按钮

<!-- 支付信息 -->
<van-cell class="pay">
  <p>支付方式</p>
  <!-- 结合cell的单选框 -->
  <van-radio-group v-model="radio">
    <!-- 单选框 -->
    <van-cell-group>
      <van-cell title="单选框 1" clickable @click="radio = '1'">
        <!-- 插槽 -->
        <template #title>
          <img src="http://www.lgstatic.com/lg-app-fed/pay/images/wechat_b787e2f4.png" alt="">
          <span class="custom-title">微信支付</span>
        </template>
        <!-- 插槽 -->
        <template #right-icon>
          <van-radio name="1"  checked-color="#fbc547" />
        </template>
      </van-cell>
      <van-cell title="单选框 2" clickable @click="radio = '2'">
        <template #title>
          <img src="http://www.lgstatic.com/lg-app-fed/pay/images/ali_ed78fdae.png" alt="">
          <span class="custom-title">支付宝支付</span>
        </template>
        <template #right-icon>
          <van-radio name="2"  checked-color="#fbc547" />
        </template>
      </van-cell>
    </van-cell-group>
  </van-radio-group>
  <!-- 立即购买按钮 -->
  <van-button>¥ {{courseInfo.discounts}} 立即支付</van-button>
</van-cell>

<style lang="scss" scoped>
.....................
// 底部支付信息
.pay {
  // 底部撑满剩余高度
  flex: 1;
  van-cell_value {
    padding: 40px;
  }
  .van-cell-group::after {
      display: none;
  }
  .van-cell--clickable {
    padding: 20px 10px;
    .van-cell__title {
      display: flex;
      align-items: center;
      img {
        width: 28px;
        height: 28px;
      }
      span {
        margin-left: 10px;
        font-size: 16px;
      }
    }
  }
  // 立即购买按钮
  .van-button {
    position: absolute;
    bottom: 10px;
    width: 100%;
    border-radius: 22px;
    background-image: linear-gradient(to right, #fbc547, #faad4a);
    font-size: 18px;
  }
}
</style>

接口封装

使用创建商品订单接口生成订单,userid不需要传递

使用创建订单(发起支付)进行支付

使用查询订单(支付结果)获取支付结果(轮询),这个就扣可以更换为json格式传递data,但是axios不能识别,不过后端可以解析出来

可以试着使用下获取支付方式接口

当前使用这个课程没有被屏蔽了支付功能(不能生成订单),可以新建一个课程(在后台项目),前台搜索就可以支付了,这里直接找同学们之前生成的课程即可

穿件订获取成订单号 => 获取允许的支付方式

// src/api/pay.js  创建文件封装支付需要的四个接口

// 引入axios
import axios from './axios'

// 创建订单
export const saveOrder = data => {
  return axios({
    method: 'post',
    url: '/front/order/saveOrder',
    data
  })
}

// 获取支付方式
export const getPayInfo = shopOrderNo => {
  return axios({
    method: 'get',
    url: `/front/pay/getPayInfo?shopOrderNo=${shopOrderNo}`
  })
}

// 发起支付
export const saveOrderBuy = data => {
  return axios({
    method: 'post',
    url: '/front/pay/saveOrder',
    data
  })
}

// 获取支付结果
export const getPayResult = params => {
  return axios({
    method: 'get',
    url: '/front/pay/getPayResult',
    headers: { 'content-type': 'application/json' },
    params
  })
}

src/views/pay/index.vue  添加支付功能

<template>
  <!-- 单元格容器 -->
  <van-cell-group>
    <!-- 顶部课程信息 -->
    <van-cell class="course">
      <!-- 课程图片 -->
      <img :src="courseInfo.courseImgUrl"/>
      <div>
        <!-- 课程名称 -->
        <p v-text="courseInfo.courseName"></p>
        <!-- 价格 -->
        <p>¥ {{courseInfo.discounts}}</p>
      </div>
    </van-cell>
    <!-- 中间用户信息 -->
    <van-cell class="user">
      <p>购买信息</p>
      <p>购买课程后使用此账号登录【拉勾教育】学习课程</p>
      <!-- 当前用户手机号 -->
      <p>当前账号:{{phone}}</p>
    </van-cell>
    <!-- 支付信息 -->
    <van-cell class="pay">
      <p>支付方式</p>
      <!-- 结合cell的单选框 -->
      <van-radio-group v-model="radio">
        <!-- 单选框 -->
        <van-cell-group>
          <van-cell title="单选框 1" clickable @click="radio = '1'">
            <!-- 插槽 -->
            <template #title>
              <img src="http://www.lgstatic.com/lg-app-fed/pay/images/wechat_b787e2f4.png" alt="">
              <span class="custom-title">微信支付</span>
            </template>
            <!-- 插槽 -->
            <template #right-icon>
              <van-radio name="1"  checked-color="#fbc547" />
            </template>
          </van-cell>
          <van-cell title="单选框 2" clickable @click="radio = '2'">
            <template #title>
              <img src="http://www.lgstatic.com/lg-app-fed/pay/images/ali_ed78fdae.png" alt="">
              <span class="custom-title">支付宝支付</span>
            </template>
            <template #right-icon>
              <van-radio name="2"  checked-color="#fbc547" />
            </template>
          </van-cell>
        </van-cell-group>
      </van-radio-group>
      <!-- 立即购买按钮 -->
      <van-button @click="buyCourse">¥ {{courseInfo.discounts}} 立即支付</van-button>
    </van-cell>
  </van-cell-group>
</template>

<script>
// 引入接口:获取课程信息
import { getCourseById } from '@/api/course'
// 引入接口:创建订单、获取支付方式、发起支付,查询支付状态
import { saveOrder, getPayInfo, saveOrderBuy, getPayResult } from '@/api/pay'
export default {
  name: 'pay',
  // 参数
  props: {
    // 路径参数
    courseId: {
      type: [Number, String],
      required: true
    }
  },
  data () {
    return {
      // 课程信息
      courseInfo: {},
      // 当前选择的支付方式
      radio: null,
      // 创建的订单号(不是发起支付的订单号)
      orderNo: ''
    }
  },
  // 钩子函数
  created () {
    // 获取课程信息
    this.getCourse()
  },
  methods: {
    // 立即支付按钮点击事件!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    async buyCourse () {
      // 调用接口创建订单
      const { data } = await saveOrder({
        goodsId: this.courseId
      })
      // 如果创建成功将订单好存储起来(也可以不存储)
      if (data.state === 1) this.orderNo = data.content.orderNo
      // 调用接口获取支付方式,利用上面创建的订单号(这个可以不要,我这里只是尝试获取一下)
      const { data: data2 } = await getPayInfo(data.content.orderNo)
      console.log(data2)
      // 调用接口发起支付,使用上面的订单号、支付方式,returnUrl必须有但是可以随便填
      const { data: data3 } = await saveOrderBuy({
        goodsOrderNo: data.content.orderNo,
        channel: this.radio === 1 ? 'weChat' : 'aliPay',
        returnUrl: 'http://edufront.lagou.com/'
      })
      // 如果发起成功跳转支付(这里使用支付宝,因为微信的方式有点问题)
      if (data.state === 1) window.location.href = data3.content.payUrl
      // 创建定时器轮询
      const timer = setInterval(async () => {
      // 调用接口不断查询支付状态
        const { data: data4 } = await getPayResult({
          orderNo: data3.content.orderNo
        })
        // 一旦发现返回的数据status = 2(代表支付成功)
        if (data4.content.status === 2) {
          // 停止定时器
          clearInterval(timer)
          // 跳转学习页面
          this.$router.push('/study')
        }
      }, 1000)
    },
    // 获取课程信息
    async getCourse () {
      // 调用接口
      const { data } = await getCourseById(this.courseId)
      // 获取成功将获取到的信息传递给data
      if (data.state === 1) this.courseInfo = data.content
    }
  },
  // 计算属性
  computed: {
    // 处理用户手机号
    phone () {
      // 使用replace正则替换中间四位为*
      return this.$store.state.user.organization.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2')
    }
  }
}
</script>

<style lang="scss" scoped>
// 整体容器
body > .van-cell-group {
  display: flex;
  flex-direction: column;
  height: 100vh;
  background: #f8f9fa;
}
// 去掉所有单元格底部分割线
.van-cell::after {
  display: none;
}
// 顶部课程信息
.course {
  padding: 40px 20px 0;
  margin-bottom: 10px;
  .van-cell__value {
    display: flex;
    height: 130px;
    img {
      width: 80px;
      height: 107px;
      border-radius: 10px
    }
    div {
      display: flex;
      height: 107px;
      box-sizing: border-box;
      flex-direction: column;
      justify-content: space-between;
      padding: 5px 20px;
      p {
        margin: 0;
      }
      p:nth-child(1) {
        font-size: 16px;
      }
      p:nth-child(2) {
        font-size: 22px;
        color: #ff7452;
        font-weight: 700;
      }
    }
  }
}
// 中间用户信息
.user {
  padding: 10px 16px;
  margin-bottom: 10px;
  p {
    margin: 0;
  }
  p:nth-child(2) {
    font-size: 12px;
    color: #999;
  }
  p:nth-child(3) {
    margin: 20px 0 10px;
    font-size: 16px;
  }
}
// 底部支付信息
.pay {
  // 底部撑满剩余高度
  flex: 1;
  van-cell_value {
    padding: 40px;
  }
  .van-cell-group::after {
      display: none;
  }
  .van-cell--clickable {
    padding: 20px 10px;
    .van-cell__title {
      display: flex;
      align-items: center;
      img {
        width: 28px;
        height: 28px;
      }
      span {
        margin-left: 10px;
        font-size: 16px;
      }
    }
  }
  // 立即购买按钮
  .van-button {
    position: absolute;
    bottom: 10px;
    width: 100%;
    border-radius: 22px;
    background-image: linear-gradient(to right, #fbc547, #faad4a);
    font-size: 18px;
  }
}
</style>

支付请求

点击支付按钮判断支付方式,发起支付,支付之后获得一幅订单号,之后不断轮训获取支付结果

注意支付方式要使用weChat或者aliPay,微信的不能唤起微信(后台设置问题),所以建议使用支付宝

获取到返回数据直接跳转到数据里的地址,会自动跳转支付宝支付

跳转后不断轮询获取支付结果,支付一旦成功(status = 2)跳转即支付成功页面即可,或者支付失败(status = 2)跳转师傅失败页面即可

支付成功和失败都添加提示信息

已经在上面写了

打包优化

优化打包速度和打包后的文件体积,如果使用静态文件较多,可能一个项目非常大

配置文件

使用Vue-cli配置文件vue.config.js进行打包配置,没有这个文件会使用默认的配置项,具体可参考文档

推荐配置项:

  • productionSourceMap:生产环境项目不需要map文件
  • css.extract:当我们每个css文件体积都比较小,可以把这些文件打包为一个文件,可以减少请求次数

图片压缩

有被压缩的必要才进行压缩处理,如果需要保持高清晰度就不建议压缩

安装图片压缩loader:npm i image-webpack-loader - D,有些包可能使用github地址,可以设置hosts,之后就会安装快很多

loader安装失败先删掉旧的依赖文件,再重新安装,否则可能会出错

vue.config.js配置文件中添加配置

可能是网络原因,使用图片压缩一直安装出问题,先不弄了

Vant按需引入组件

vue.config.jsant提供了方法自动按需引入组件,会把全局引入在编译过程中自动转化为按需引入,缩小打包体积

注意需要修改组件内使用vant组件的方式,要先引入再需要的组件,然后注册为子组件使用,注意Toast比较特殊(不需要注册直接使用)

按需引入后删除掉之前的全局引入

经过以上的步骤才能保证vant能够正常呈现

参考vant官方文档 => 安装npm包 => 修改babel文件内容 => 修改全部的vant组件引入方式(当然如果写的时候直接就按照按需引入就更好了,别问我怎么知道的)=> 删除掉之前的全局引入 => 之后会发现每个小每个模块js文件略微增加,而最大的js明显缩小,这样有利于初次加载的时候更快

CDN引入

内容分发网络 - 稳定,速度快,将某些大的包使用CND引入的方式加快响应速度

bootCdn提供了很多CDN服务,阿里云可以购买CDN服务

Vant提供了CDN引入方式本项目可以把vue和vant都使用CDN引入

CDN引入也先不做了,因为上面实在要修改的太多了,以后写代码的时候直接使用自动按需引入就可以了

以后需要的时候可以回去看视频