美文网首页Vue
支付、打包优化

支付、打包优化

作者: amanohina | 来源:发表于2021-03-18 23:42 被阅读0次

    支付页

    组件准备

    创建支付组件文件

    // views/pay/index.vue
    <template>
      <div class="pay">支付功能</div>
    </template>
    
    <script>
    export default {
      name: 'Pay'
    }
    </script>
    
    <style lang="scss" scoped></style>
    

    添加路由,注意,支付页要登录才能显示

    // router/index.js
    ...
      // 支付页
      {
        path: '/pay/:courseId/',
        name: 'pay',
        component: () => import(/* webpackChunkName: 'pay' */'@/views/pay/index'),
        meta: { requiresAuth: true },
        props: true
      },
    ...
    

    课程详细页中点击购买后跳转,并且传递课程ID

    • 检测用户是否登录,登录时跳转支付,未登录时跳转登录页并且记录当前页信息
    // course-info/index.vue
    ...
    <van-button
      type="primary"
      @click="handlePay"
    >立即购买</van-button>
    ...
    <script>
    ...
    handlePay () {
      // 检测是否登录
      if (this.$store.state.user) {
        // 如果已登录,跳转支付页
        this.$router.push({
          name: 'pay',
          params: {
            courseId: this.courseId
          }
        })
      } else {
        // 如果未登录,跳转登录页,并记录本页面信息,登录成功跳回到当前页
        console.log(this.$route.fullPath)
        this.$router.push({
          name: 'login',
          query: {
            redirect: this.$route.fullPath
          }
        })
      }
    },
    ...
    

    布局处理

    支付组件分为上中下三部分

    • 顶部为课程信息
    • 中间为账户信息
    • 底部为支付方式
      整体布局使用Vant的cell单元格组件,内部进行细节处理,代码:
    // pay/index.vue
    <template>
      <div class="pay">
        <van-cell-group>
          <van-cell class="course-info">
            <img src="xxxxx-demo.png" alt="">
            <div class="price-info">
              <div class="course-name" v-text="示例课程名称"></div>
              <div class="discounts">¥100000</div>
            </div>
          </van-cell>
          <van-cell class="account-info">
            <div>购买信息</div>
            <div>购买课程后使用此账号登录【拉勾教育】学习课程</div>
            <div class="username">当前账号:1122334455</div>
          </van-cell>
          <van-cell class="pay-channel">
            <div class="title">支付方式</div>
          </van-cell>
        </van-cell-group>
      </div>
    </template>
    ...
    <style lang="scss" scoped>
    // 让容器盛满屏幕,用于 #app 没有宽度,设置定位脱标,让元素参考窗口尺寸
    .pay {
      position: absolute;
      width: 100%;
      height: 100%;
    }
    // 容器
    .van-cell-group {
      width: 100%;
      height: 100%;
      background-color: #f8f9fa;
      display: flex;
    }
    // 课程信息
    .course-info {
      height: 170px;
      padding: 40px 20px 0;
      margin-bottom: 10px;
      box-sizing: border-box;
    }
    // 让图片与右侧信息同行显示
    .course-info .van-cell__value{
      display: flex;
    }
    // 课程图片
    .course-info img {
      width: 80px;
      height: 107px;
      border-radius: 10px;
    }
    .price-info {
      height: 107px;
      padding: 5px 20px;
      box-sizing: border-box;
      display: flex;
      flex-direction: column;
      justify-content: space-between;
    }
    .price-info .course-name {
      font-size: 16px;
    }
    .price-info .discounts {
      font-size: 22px;
      font-weight: 700;
      color: #ff7452;
    }
    
    // 账户信息
    .account-info {
      height: 120px;
      margin-bottom: 10px;
    }
    
    .account-info div:nth-child(2) {
      font-size: 12px;
      color: #999;
    }
    
    .account-info .username {
      margin: 20px 0 10px;
      font-size: 16px;
    }
    </style>
    

    数据绑定

    步骤:

    • 课程信息
      • props接收路由参数courseId
      • 引入course.js的getCourseById接口并请求数据
      • 绑定数据
    • 账户信息
      • 从store中读取用户信息
      • 使用计算属性将手机号中间四位替换为****
    // pay/index.vue
    ...
            <!-- 课程图片 -->
            <img :src="course.courseImgUrl" alt="">
            <div class="price-info">
              <!-- 课程名称 -->
              <div class="course-name" v-text="course.courseName"></div>
              <!-- 课程价格 -->
              <div class="discounts">¥{{ course.discounts }}</div>
            </div>
          </van-cell>
          <van-cell class="account-info">
            ...
            <!-- 账号信息 -->
            <div class="username">当前账号:{{ username }}</div>
          </van-cell>
            ...
    <script>
    import { getCourseById } from '@/services/course'
    export default {
      name: 'Pay',
      props: {
        courseId: {
          type: [String, Number],
          required: true
        }
      },
      data () {
        return {
          course: {}
        }
      },
      created () {
        this.loadCourse()
      },
      methods: {
        async loadCourse () {
          const { data } = await getCourseById({
            courseId: this.courseId
          })
          this.course = data.content
          console.log(data)
        }
      },
      computed: {
        username () {
          return this.$store.state.user.organization.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
        }
      }
    }
    </script>
    ...
    

    支付结构

    使用Vant的[Radio单选框]组件与cell一并使用
    设置到页面中

    • 添加左侧支付宝与微信图标
    • 进行布局样式设置
    // pay/index.vue
          ...
          <!-- 支付方式 -->
          <van-cell class="pay-channel">
            <div>
              <p>支付方式</p>
              <van-radio-group v-model="radio">
                <van-cell-group>
                  <van-cell @click="radio = '1'">
                    <!-- 将左侧标题设置为插槽,添加对应支付图标 -->
                    <template #title>
                      <img src="http://www.lgstatic.com/lg-app-fed/pay/images/wechat_b787e2f4.png" alt="">
                      <span>微信支付</span>
                    </template>
                    <template #right-icon>
                      <van-radio name="1" />
                    </template>
                  </van-cell>
                  <van-cell clickable @click="radio = '2'">
                    <template #title>
                      <img src="http://www.lgstatic.com/lg-app-fed/pay/images/ali_ed78fdae.png" alt="">
                      <span>支付宝支付</span>
                    </template>
                    <template #right-icon>
                      <van-radio name="2" />
                    </template>
                  </van-cell>
                </van-cell-group>
              </van-radio-group>
            </div>
            <van-button>¥{{ course.discounts }} 立即支付</van-button>
          </van-cell>
        </van-cell-group>
      </div>
    </template>
    
    <script>
    ...
      data () {
        return {
          ...
          radio: '1'
        }
      },
        ..
    </script>
    
    <style lang="scss" scoped>
    ...
    // 支付区域(占满剩余空间)
    .pay-channel {
      flex: 1;
    }
    // 让 radio 与 按钮在上下两端
    .pay-channel .van-cell__value {
      display: flex;
      flex-direction: column;
      justify-content: space-between;
    }
    .pay-channel .van-cell {
      padding: 20px 10px;
    }
    // 左侧标题插槽
    .pay-channel .van-cell__title {
      display: flex;
      align-items: center;
    }
    .pay-channel .van-cell img {
      width: 28px;
      height: 28px;
    }
    .pay-channel .van-cell span {
      font-size: 16px;
      margin-left: 10px;
    }
    
    // 右侧 radio 选中颜色
    ::v-deep .van-radio__icon--checked .van-icon{
      background-color: #fbc546;
      border-color: #fbc546;
    }
    
    // 底部按钮样式
    .pay-channel .van-button {
      background: linear-gradient(270deg,#faa83e,#fbc546);
      border-radius: 20px;
      margin-bottom: 5px;
      font-size: 18px;
    }
    </style>
    

    逻辑处理

    支付功能需要有以下的步骤:

    • 支付页面打开后,需要根据商品ID(课程ID)创建商品订单
    • 用户操作选择支付方式(微信,支付宝)
    • 跳转到支付页,支付页会自动唤起对应的APP
    • 支付,并且跳转成功,跳转Learn组件

    需要使用到的接口

    • 订单接口:
      • 创建商品订单接口:地址
    • 支付接口
      • 获取支付方式:地址
      • 创建订单(发起支付):地址
    • 查询订单(支付结果):地址

    封装接口

    新建src/services/pay.js

    // services/pay.js
    import request from '@/utils/request'
    
    // 创建商品订单接口
    //  - goodsId 商品(课程)ID 必传
    export const createOrder = data => {
      return request({
        method: 'POST',
        url: '/front/order/saveOrder',
        data
      })
    }
    
    // 获取支付方式接口
    //  - shopOrderNo 订单号必传
    export const getPayInfo = params => {
      return request({
        method: 'GET',
        url: '/front/pay/getPayInfo',
        params
      })
    }
    
    // 创建订单(发起支付)
    //  - goodsOrderNo, channel, returnUrl 必传
    export const initPayment = data => {
      return request({
        method: 'POST',
        url: '/front/pay/saveOrder',
        data
      })
    }
    
    // 查询订单(查询支付结果)
    //  - orderNo 订单号必传
    //  - 由于接口要求传递 JSON,所以进行 headers 设置
    export const getPayResult = params => {
      return request({
        method: 'GET',
        url: '/front/pay/getPayResult',
        headers: { 'content-type': 'application/json' },
        params
      })
    }
    

    创建订单与获取支付方式

    引入并调用接口

    • 调用创建商品订单接口获取订单号
    • 调用获取支付方式接口
    • 根据支付方式接口设置支付区域的radio数据
    // pay/index.vue
    ...
    <script>
    ...
    import { createOrder, getPayInfo } from '@/services/pay'
    ...
    data () {
        return {
          ...
          // 订单号
          orderNo: null,
          // 支付方式信息
          payInfo: {}
        }
      },
      created () {
        ...
        this.loadOrder()
      },
      methods: {
        // 创建订单,获取订单号
        async loadOrder () {
            // 创建订单,获取订单号
          const { data } = await createOrder({
            goodsId: this.courseId
          })
          this.orderNo = data.content.orderNo
          // 获取支付方式
          const { data: payInfo } = await getPayInfo({
            shopOrderNo: this.orderNo
          })
          this.payInfo = payInfo.content.supportChannels
        },
    ...
    </script>
    ...
    <van-radio-group v-model="radio">
      <van-cell-group>
        <van-cell @click="radio = payInfo[1].channelCode">
          ...
          <template #right-icon>
            <van-radio :name="1" />
          </template>
        </van-cell>
        <van-cell clickable @click="radio = payInfo[0].channelCode">
          ...
          <template #right-icon>
            <van-radio :name="2" />
          </template>
        </van-cell>
      </van-cell-group>
    </van-radio-group>
    

    支付请求

    点击支付按钮时,发送请求

    // pay/index.vue
    ...
    <van-button @click="handlePay">...</van-button>
    ...
    <script>
    import { ..., initPayment } from '@/services/pay'
    ...
    async handlePay () {
      // 发起支付请求
      const { data } = await initPayment({
        goodsOrderNo: this.orderNo,
        channel: this.radio === 1 ? 'weChat' : 'aliPay',
        returnUrl: 'http://edufront.lagou.com/'
      })
      // 接收响应地址,并进行跳转
      window.location.href = data.content.payUrl
    },
    ...
    

    设置完毕,在手机上进行测试

    • 微信支付地址为PC支付功能,无法正常唤起微信支付,为接口原因
    • 支付宝地址可以正确跳转App支付,所有课程无论标价多少,后台都是默认1分钱,需要真实支付才可以成功购买,所以要尽快完成调试,不要浪费你的小金库
      (支付过程就不截图了,肉疼环节)

    查询支付结果

    发起支付请求后,需要轮询支付结果

    • 设置定时器,每隔1000ms进行一次查询请求
    • 注意接口传入的orderNo不是商品订单号,而是支付订单号,从支付请求的响应内容中得到
    // pay/index.vue
    ...
    import { ..., getPayResult } from '@/services/pay'
    ...
    async handlePay () {
      ...
      const timer = setInterval(async () => {
        // 发起查询支付结果请求(此处使用)
        const { data: payResult } = await getPayResult({
          orderNo: data.content.orderNo
        })
        // 如果支付结果成功,清除定时器,并提示购买成功,跳回到学习页
        if (payResult.content && payResult.content.status === 2) {
          clearInterval(timer)
          this.$toast.success('购买成功!')
          this.$router.push({
            name: 'learn'
          })
        }
      }, 1000)
    },
    ...
    

    打包优化

    普通打包结果如下:



    打包优化主要体现在两个方面

    • 打包过程优化
    • 打包结果优化

    打包过程优化指的是打包速度方面,减少项目中没有使用到的包,去除没有使用到的样式等等

    打包结果优化指的是打包后的文件体积,比如压缩文件大小等等
    当然,也有很多优化项既可以对过程优化,也可以对结果进行优化

    如果我们要对打包进行优化,就需要更改打包配置,由于Vue CLI是基于webpack构建的,打包配置其实就是webpack配置

    Vue CLI配置文件

    Vue CLI内部包含了对webpack的默认配置,所以项目中大多数情况都无需进行配置。如果需要手动添加或者修改配置,就需要在项目根目录下创建vue.config.js配置文件
    对于该文件的项目配置,Vue CLI提供了详细的配置文档

    productionSourceMap

    当项目打包后,如果出现了代码错误,可以从控制台找到错误对应的源码位置,这是由于打包时生成了 .map 文件,可以帮助定位错误信息。
    用户不可能对我们的代码进行调试,所以 .map 文件就没有存在的意义了。这时设置 productionSourceMap 为 false,不仅可以不生成 .map 文件,同时会对源码加密,防止代码被盗用。
    vue.config.js:

    module.exports = {
      productionSourceMap: false 
    }
    

    css.extract

    打包时,css 默认会打包为独立文件,这样会增加页面的请求数量,由于项目单个页面组件的 css 体积通常不是很大,可以设置为行内引入方式,以减少网页请求次数。(到底用不用这个,取决于CSS在项目内占比大不大)
    设置方式:

    // vue.config.js
    module.exports = {
      css: {
        extract: false
      },
      ...
    }
    
    上述两个方法使用之后的打包结果

    图片压缩

    图片压缩会在一定程度上影响图片的质量,使用时根据具体场景选择是否使用。图片有被压缩的必要才需要进行此步处理,如果网站需要高清展示图片就没必要这么做了

    安装

    需要使用image-webpack-loader首先安装

    npm i image-webpack-loader -D

    如果安装失败,则必须删除项目中的 node_modules 再重新安装依赖。
    实在太慢可以考虑使用cnpm

    配置

    在vue.config.js中设置以下信息,用来对webpack进行loader配置

    // vue.config.js
    module.exports = {
      ...
      // 图片压缩 loader 配置
      chainWebpack: config => {
        // 配置图片压缩
        config.module
          .rule('images')
          .use('image-webpack-loader')
          .loader('image-webpack-loader')
          .options({
            bypassOnDebug: true
          })
          .end()
      }
    }
    

    Vant 自动按需引入组件

    官方介绍中说到,引入所有组件会增加代码包的体积,不推荐整体引入。

    自动按需引入需要使用 babel 的插件 babel-plugin-import,这个插件会在编译过程中将 import 写法自动转换为按需引入方式。

    首先安装插件:

    npm install babel-plugin-import -D

    在babel.config.js中添加配置

    • 文档要求在.babelrc中设置,.babelrc与babel.config.js文件作用相同,一般有babel.config.js就不用.babelrc了
    ...
      "plugins": [
        // 注意:webpack 1 无需设置 libraryDirectory
        ["import", {
          "libraryName": "vant",
          "libraryDirectory": "es",
          "style": true
        }]
      ]
    

    在任意组件中按以下方式引入Vant组件,插件会在编译时自动转换为按需引入(引入JS与CSS)形式(哪里用了哪里都需要这样改正)

    import { Button } from 'vant'
    ...
    components: {
      VanButton: Button
    }
    

    Toast这种需要进行方法调用的组件需要在引入后将this.$toast更改为Toast()

    // pay/index.vue 示例
    import { ..., Toast } from 'vant'
    ...
    // this.$toast.success('购买成功!')
    Toast.success('购买成功!')
    

    同时,main.js 中的整体引入就可以去除了。

    // main.js
    -  import Vant from 'vant'
    -  import 'vant/lib/index.css'
    -  Vue.use(Vant)
    

    从打包结果来看,体积显著减小

    错误说明

    如果出现以下类似的报错,说明在不同组件中进行相同子组件引入(包括 Vant 组件)的顺序不同,例如 A 组件内先引组件 X 后引入组件 Y,B 组件内先引组件 Y 后引入组件 X,当 Webpack 将这些代码打包到同一个文件中时,就会无法处理从而导致 webpack 的 mini-css-extract-plugin 插件报错。调整引入顺序即可。
    报错示意图如下:


    CDN

    在 public/index.html 中通过 CDN 的方式引入 Vue、Vant,这样就无需在 main.js 中进行引入了。
    当我们在项目中使用 CDN 链接之后,就没必要下载打包第三方包了

    // public/index.html
    <!DOCTYPE html>
    <html lang="">
      <head>
        ...
        <!-- Vant 样式 -->
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vant@2.12/lib/index.css" />
      </head>
      <body>
        ...
        <!-- 引入 Vue -->
        <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
        <!-- 引入 Vant -->
        <script src="https://cdn.jsdelivr.net/npm/vant@2.12/lib/vant.min.js"></script>
        ...
      </body>
    </html>
    
    // vue.config.js
    module.exports = {
      ...
      configureWebpack: {
        // 通过 CDN 引入
        externals: {
          'vue': 'Vue',
          'vant': 'vant'
        }
      }
    }
    

    比较一下:
    npm 安装方式打包结果



    CDN安装方式


    显而易见,打包体积有了明显的变化

    这里演示的仅为 Vue 与 Vant 的 CDN 引入方式,其他工具也可以如此操作,但通常我们只会将体积比较大的第三方文件进行 CDN 引入,而不会将所有包都设置为这种方式(文件数多,首次的请求数也会变多)。

    完毕

    相关文章

      网友评论

        本文标题:支付、打包优化

        本文链接:https://www.haomeiwen.com/subject/mvoucltx.html