交易订单页面
- 先搞定
静态组件
,就三个动作
- 复制粘贴'静态组件'
- 路由注册一下
- 访问path测试
### src.router.routers.js
......
import Trade from '@/pages/Trade'
export default [
{
name:"trade",
path: "/trade",
component: Trade,
meta: {
show: true
}
},
......
- 测试: http://localhost:8080/#/trade
- 修改一下
购物车页面
的结算链接
### ShopCart.index.vue
......
<div class="sumbtn">
<!-- <a class="sum-btn" href="###" target="_blank">结算</a> -->
<!--只是作简单的跳转,直接router-link-->
<router-link to='/trade' class="sum-btn">结算</router-link>
</div>
- 先搞定
交易页面
的收货地址
,开始老套路
- 配置请求
- vuex三连环
- 组件内渲染数据
### api.index.js
......
export const reqAddressInfo = ()=>requests({url:'/user/userAddress/auth/findUserAddressList',method:'get'})
### trade.index.js
......
import { reqAddressInfo } from "@/api"
const state = {
address:[]
}
const actions = {
async getUserAddress({commit}){
var res = await reqAddressInfo()
if(res.code == 200){
commit('GETUSERADDRESS',res.data) // res.data就是收货地址
return 'ok'
}else{
return Promise.reject(new Error('获取用户收货地址失败'))
}
}
}
### Trade.index.vue
......
<script>
export default {
name: 'Trade',
mounted(){
this.$store.dispatch('getUserAddress') // 派发请求
}
}
</script>
- 正常响应的数据是这样
{code: 200, message: "成功",…}
code: 200
data: [{id: 5, userAddress: "北京市昌平区1000", userId: 2, provinceId: 1, consignee: "test110",…}]
0: {id: 5, userAddress: "北京市昌平区1000", userId: 2, provinceId: 1, consignee: "test110",…}
message: "成功"
ok: true
- 组件数据的渲染稍后再处理,先搞定一个
交易订单
的请求
### api.index.js
......
export const reqOrderInfo = ()=>requests({url:'/order/auth/trade',method:'get'})
### trade.index.js
......
import { reqAddressInfo,reqOrderInfo } from "@/api"
const state = {
......
orderInfo:{} // 初始化数据,返回值是一个对象,数据有点多,这里就不作展示(直接到仓库去看...)
}
const actions = {
......
async getOrderInfo({commit}){
var res = await reqOrderInfo()
if(res.code == 200){
commit('GETORDERINFO',res.data)
return 'ok'
}else{
return Promise.reject(new Error('获取用户订单信息失败'))
}
}
}
const mutations = {
......
GETORDERINFO(state,orderInfo){
state.orderInfo = orderInfo
}
}
### Trade.index.vue
......
mounted() {
......
this.$store.dispatch('getOrderInfo'); // 派发请求
},
收货地址
和交易订单
数据在组件内的渲染
### Trade.index.vue
......
<template>
<div class="trade-container">
......
<div class="address clearFix" v-for="(address,index) in addressInfo" :key="address.id">
<!--是否有selected样式,由isDefault决定-->
<span class="username" :class="{selected:address.isDefault == 1}">{{address.consignee}}</span>
<!--绑定排它事件: 先统一xx,然后单独项再开小灶-->
<p @click="changeDefault(address,addressInfo)">
<span class="s1">{{address.fullAddress}}</span>
<span class="s2">{{address.phoneNum}}</span>
<!--是否有selected样式,由isDefault决定-->
<span class="s3" v-show="address.isDefault == 1">默认地址</span>
</p>
</div>
......
<div class="trade">
......
寄送至:
<!--用户点击哪个地址,通过计算属性渲染结果-->
<span>{{userDefaultAddress.fullAddress}}</span>
收货人:<span>{{userDefaultAddress.consignee}}</span>
<span>{{userDefaultAddress.phoneNum}}</span>
</div>
</div>
......
</div>
</template>
<script>
import {mapState} from 'vuex'
export default {
name: 'Trade',
mounted() {
......
},
computed:{
// 映射仓库数据
...mapState({addressInfo:state=>state.trade.address}),
// find会遍历item,然后找出符合条件的'项'
userDefaultAddress(){
return this.addressInfo.find(item=>item.isDefault == 1) || {} // 返回{}避免网络请求失败,而出现vue警告
}
},
methods:{
changeDefault(address,addressInfo){
// 排他操作
addressInfo.forEach(item => item.isDefault = 0)
address.isDefault = 1
}
}
}
</script>
- 用户点击哪个地址,通过计算属性渲染结果,其实还可以这么做
- 初始化一个data,然后在changeDefault方法里面,把 address项传给data,结构中直接使用data来渲染数据
订单信息
数据的渲染
<template>
<div class="trade-container">
......
<!--开始渲染-->
<ul class="list clearFix" v-for="(order,index) in orderInfo.detailArrayList" :key="order.skuId">
<li>
<!--这里要加入style样式,否则图片太大-->
<img :src="order.imgUrl" alt="" style="width: 100px;height: 100px;">
</li>
<li>
<p>
{{order.skuName}}</p>
<h4>7天无理由退货</h4>
</li>
<li>
<h3>¥{{order.orderPrice}}.00</h3>
</li>
<li>X{{order.skuNum}}</li>
<li>有货</li>
</ul>
</div>
<div class="bbs">
<h5>买家留言:</h5>
<!--获取买家留言-->
<textarea placeholder="建议留言前先与商家沟通确认" class="remarks-cont" v-model="msg"></textarea>
</div>
......
</div>
<div class="money clearFix">
<ul>
<li>
<b><i>{{orderInfo.totalNum}}</i>件商品,总商品金额</b>
<span>¥{{orderInfo.totalAmount}}.00</span>
</li>
......
</ul>
</div>
<div class="trade">
<div class="price">应付金额: <span>¥{{orderInfo.totalAmount}}.00</span></div>
......
</div>
</template>
<script>
import {mapState} from 'vuex'
export default {
name: 'Trade',
data(){
return {
msg:'' // 初始化数据
}
},
mounted() {
......
},
computed:{
...mapState({
addressInfo:state=>state.trade.address,
orderInfo:state=>state.trade.orderInfo // 映射订单数据信息
}),
......
},
methods:{
......
}
}
</script>
trade
组件最后一个功能,提交订单
按钮的逻辑实现
- 本次请求以后,数据不再放在
vuex
,而是使用类似全局事件总线$bus
的逻辑
### api.index.js
......
// 订单号参数 和 订单信息对象参数
export const reqSubmitOrder = (tradeNo,data)=>requests({url:`/order/auth/submitOrder?tradeNo=${tradeNo}`,method:'post'})
### main.js
......
import * as API from '@/api'
......
new Vue({
......
beforeCreate(){
Vue.prototype.$bus = this;
Vue.prototype.$API = API; // 新增 $API属性
}
}).$mount('#app')
### trade.index.vue
......
......
<div class="sub clearFix">
<!-- <router-link class="subBtn" to="/pay">提交订单</router-link> -->
<!--绑定事件-->
<a class="subBtn" @click="submitOrder">提交订单</a>
</div>
......
<script>
import {mapState} from 'vuex'
export default {
name: 'Trade',
data(){
return {
......
orderId:'' // 初始化
}
},
......
methods:{
......
async submitOrder(){
let {tradeNo} = this.orderInfo; // 获取订单号
let data = { // 构造data对象
"consignee": this.userDefaultAddress.consignee, // 收件人的名字
"consigneeTel": this.userDefaultAddress.phoneNum, // 收件人的手机号
"deliveryAddress": this.userDefaultAddress.fullAddress, // 收件人的地址
"paymentWay": "ONLINE", // 支付方式
"orderComment": this.msg, // 买家的留言信息
"orderDetailList": this.orderInfo.detailArrayList, // 商品清单
};
var res = await this.$API.reqSubmitOrder(tradeNo,data) // 发请求
if(res.code == 200 ){
this.orderId = res.data; // 响应成功就保存订单号并跳转
this.$router.push('/pay?orderId=' + this.orderId)
}else{
alert(res.data)
}
}
}
}
</script>
支付模块
- 先搞定
静态组件
,然后router
注册一下
import Pay from '@/pages/Pay'
export default [
......
{ // 注册
name:"pay",
path: "/pay",
component: Pay,
meta: {
show: true
}
},
- 获取支付信息: 当
Pay
页面挂载完毕以后,立即向后端发请求,获取支付信息
(先配置请求接口)
### api.index.js
......
export const reqPayInfo = (orderId)=>requests({url:`/payment/weixin/createNative/${orderId}`,method:'get'})
### Pay.index.vue
......
<script>
export default {
name: 'Pay',
data(){
return {
payInfo:{} // 初始化
}
},
computed:{
orderId(){ // 由于提交订单经常会失败,这里干脆写死...
return this.$route.query.orderId || 'xxx-yyy-zzz'
}
},
mounted(){ // vue生命周期函数,尽量不要使用async,有坑
this.getPayInfo()
},
methods:{
async getPayInfo(){ // 在这里实现异步
var res = await this.$API.reqPayInfo(this.orderId);
if(res.code == 200){
this.payInfo = res.data
}else{
alert(res.data)
}
}
}
}
</script>
引入element-ui
组件库
- npm install element-ui
- 如果需要
按需引入
,在上述基础上,可以这么搞
- npm install babel-plugin-component -D
### babel.config.js
module.exports = { // 修改配置文件,注意要重启serve,否则element-ui无法生效
presets: [
'@vue/cli-plugin-babel/preset'
],
plugins: [ // 新增
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
main.js
注册为全局组件
### main.js
......
import { Button } from 'element-ui' // 导入按钮,测试用
Vue.component(Button.name,Button)
- 测试
### Pay.index.vue
.......
<template>
<div class="pay-main">
<!--有了-->
<el-button type="primary">测试按钮</el-button>
- 项目中,我们要使用
MessageBox 弹框
这个组件,文档参考地址
- https://element.eleme.cn/2.5/#/zh-CN/component/message-box
第二种
按需引入的方式,绑定到Vue.prototype
上面
### main.js
......
import { Button,MessageBox } from 'element-ui' // 导入
Vue.component(Button.name,Button) // 第一种方式
Vue.prototype.$msgbox = MessageBox // 第二种方式
Vue.prototype.$alert = MessageBox.alert
- 测试.点击按钮,弹窗
### Pay.index.vue
......
<div class="submit">
<!-- <router-link class="btn" to="/paysuccess">立即支付</router-link> -->
<a class="btn" @click="open">立即支付</a> <!--绑定点击事件-->
</div>
......
methods: {
......
open() {
this.$alert('<strong>这是 <i>HTML</i> 片段</strong>', 'HTML 片段', {
dangerouslyUseHTMLString: true,
center:true, // 文字居中
showCancelButton:true, // 显示'取消'按钮
cancelButtonText:'支付遇到问题', // 更改按钮文本文字
confirmButtonText:'已支付成功',
showClose:false // 隐藏`X`项
});
}
}
-
接下来要做的事情,就是把弹窗中的
测试文本
,修改为二维码
- 如果订单提交成功,
payInfo
有以下字段
codeUrl:"weixin://wxpay/bizpayurl?pr=ZuHaocUzz"
- 前面的操作中,如果订单提交失败,这里可以写死
codeUrl
,我们作测试用
### Pay.index.vue ..... open() { this.$alert('<strong>这是 <i>HTML</i> 片段</strong>', 'HTML 片段', { ...... }); this.payInfo.codeUrl = "weixin://wxpay/bizpayurl?pr=ZuHaocUzz" // 加这句 }
- 如果订单提交成功,
-
生成
二维码
相关的库,我们使用qrcode
来实现
- 安装: npm i qrcode
### Pay.index.vue
......
<script>
import QRCode from 'qrcode' // 引入
export default {
......
methods: {
......
open() {
......
this.payInfo.codeUrl = "weixin://wxpay/bizpayurl?pr=ZuHaocUzz"
let res = QRCode.toDataURL(this.payInfo.codeUrl)
// 返回的是一个Promise对象
// Promise {<fulfilled>: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJQA…sSoGAUnI1mBQIKFIou1Lgf8LubvkqQm+8AAAAAElFTkSuQmCC'}
console.log(res)
}
}
}
</script>
- 老套路,加上 async 和 await 直接获取结果
......
async open() { // 加 async 和 await 直接获取结果
......
this.payInfo.codeUrl = "weixin://wxpay/bizpayurl?pr=ZuHaocUzz"
let res = await QRCode.toDataURL(this.payInfo.codeUrl)
// 浏览器打开链接,就是一张二维码图片
console.log(res) // data:image/png;base64,iVBORw0KGgoAAA......
}
- 最终代码
### Pay.index.vue
......
......
methods: {
.......
async open() {
this.payInfo.codeUrl = "weixin://wxpay/bizpayurl?pr=ZuHaocUzz"
let url = await QRCode.toDataURL(this.payInfo.codeUrl) // 获取二维码地址
// 把 url 传给 img 显示
this.$alert(`<img src="${url}"></img>`, '请你微信支付', {
dangerouslyUseHTMLString: true,
center:true,
showCancelButton:true,
cancelButtonText:'支付遇到问题',
confirmButtonText:'已支付成功',
showClose:false
});
}
}
- 当
二维码
出现的时候,用户应完成支付
,而用户何时支付,这个时间是不确定的,所以我们需要每隔一段时间去问服务器,用户是否完成支付
- 后端接口:
### api.index.js
......
// 获取订单的支付状态
export const reqPayStatus = (orderId)=>requests({url:`/payment/weixin/queryPayStatus/${orderId}`,method:'get'})
### Pay.index.vue
......
<script>
export default {
......
data() {
return {
......
timer:null, // 初始化数据
code:''
}
},
computed: {
orderId() { // 之前的数据
return this.$route.query.orderId || 'xxx-yyy-zzz'
}
},
methods: {
......
async open() {
......
// 使用定时器,每隔1s就询问服务器,用户是否已完成支付
if(!this.timer){
this.timer = setInterval(async ()=>{
let res = await this.$API.reqPayStatus(this.orderId)
if(res.code == 200){ // 200响应,说明用户完成支付了
clearInterval(this.timer) // 清除计时器并关闭弹窗,同时跳转到'支付成功'页
this.timer = null
this.code = res.code
this.$msgbox.close()
this.$router.push('/paysuccess')
}
},1000) // 如果后端返回的不是200,就一直重复下去...
}
}
}
}
</script>
- 然后,配置一下
PaySuccess
静态组件
### router.routers.js
......
import PaySuccess from '@/pages/PaySuccess'
export default [
.......
{
name:"paysuccess",
path: "/paysuccess",
component: PaySuccess,
meta: {
show: true
}
},
messageBox弹窗
的按钮逻辑处理
- 该弹窗有两个按钮
支付遇到问题
已成功支付
- 当用户点击'支付遇到问题'的时候,我们弹窗提示消息,并清除计时器,最后关闭 messageBox
- 当用户点击'已成功支付'的时候,我们清除计时器,关闭 messageBox,然后跳转到'/paysuccess'
### Pay.index.vue
......
methods: {
......
async open() {
......
this.$alert(`<img src="${url}"></img>`, '请你微信支付', {
......
// action 是用户的行为:比如'confirm'或者'cancel'
// instance 是 messageBox实例对象,通过它可以调用该实例的一些属性和方法
// done就是function对象,关闭弹窗
// 文档地址: https://element.eleme.cn/2.8/#/zh-CN/component/message-box
beforeClose: (action,instance,done) => {
if(action == 'cancel'){
alert('请联系管理员')
clearInterval(this.timer)
this.timer = null
done()
}else{
// if(this.code == 200){ // 为了测试,先不判断 200 状态码
clearInterval(this.timer)
this.timer = null
done()
this.$router.push('/paysuccess')
// }
}
}
});
if(!this.timer){
......
}
}
}