支付页
组件准备
创建支付组件文件
// 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 引入,而不会将所有包都设置为这种方式(文件数多,首次的请求数也会变多)。
完毕
网友评论