文章目录
- 一、交易业务Trade
- 1. 获取用户地址
- 2. 获取订单信息
- 二、提交订单
- 三、支付
- 1. 获取支付信息
- 2. 支付页面--ElementUI
- (1) 引入Element UI
- (2) 弹框支付的业务逻辑(这个逻辑其实没那么全)
- (3) 支付逻辑知识点小总结
- 四、个人中心
- 1. 搭建二级路由
- 2. 展示动态数据
- (1). 接口
- (2). 组件获取数据
- (3). 页面表格的合并与展示
- (4). 页面分页器
一、交易业务Trade
首先要配置静态组件及路由,这里没什么重点,不多说了。然后是动态数据的展示
1. 获取用户地址
(1) 接口
用文档里的接口向服务器发送请求时,电商系统的账号和密码得用视频里老师的账号密码登录,才会有数据返回。如果用自己的账号密码,那就自己mock模拟一些数据。
// 文档的接口
export const reqAddressList = () => {
return requests({ url: 'user/userAddress/auth/findUserAddressList', method: 'get' })
}
// 自己mock的请求接口
export const reqAddressList = () => {
return mockRequests({ url: '/userAddress/auth/findUserAddressList', method: 'get' })
}
(2) Vuex三连环
新建trade
小仓库存放交易业务的数据
state: {
addressList: [],
},
actions: {
// 获取用户地址信息
async getUserAddress (context) {
let res = await reqAddressList()
console.log('用户地址', res);
if (res.code === 200) {
context.commit('GETUSERADDRESS', res.data)
}
}
},
mutations: {
GETUSERADDRESS (state, data) {
state.addressList = data
},
},
(3)动态数据渲染组件页面
在mounted函数里dispatch
请求,通过mapState
获取Vuex的数据。
第一个业务逻辑是修改默认地址。点击哪个标签,哪个成为默认地址。
// 修改默认地址,排他思想
changeDefault (address) {
this.addressList.forEach(el => {
el.isDefault = 0
});
address.isDefault = 1 // 值为1表示为选中的默认地址
},
第二个业务逻辑是获取到默认地址的对象信息,动态展示在页面低端。
采用计算属性。需要注意,这里用到了Vuex的值,可能会有undefined
的错误(因为addressList
是异步请求获得的数据,页面刚加载完时,addressList
的值还未获取到,则会报undefined的错误),所以要 || {}
。
<div class="receiveInfo">
寄送至:
<!--这里会报undefined的错误,fullAddress属性undefined-->
<span>{{ userDefAddress.fullAddress }}</span>
收货人:<span>{{ userDefAddress.consignee }}</span>
<span>{{ userDefAddress.phoneNum }}</span>
</div>
<script>
computed: {
...mapState('trade', ['addressList']),
// 将来提交订单最终选中的地址 表达式:undefined || {} 的值是{}
userDefAddress () {
return this.addressList.find((item) => {
return item.isDefault == 1
}) || {}
}
},
</script>
2. 获取订单信息
就是trade页面的这部分信息
(1) 接口
export const reqOrderInfo = () => {
return requests({
url: '/order/auth/trade',
method: 'get',
})
}
(2) vuex存储数据
state: {
addressList: [],
},
actions: {
// 获取订单交易信息
async getOrderInfo (context) {
let res = await reqOrderInfo()
if (res.code === 200) {
context.commit('GETORDERINFO', res.data)
}
}
},
mutations: {
GETORDERINFO (state, data) {
state.orderInfo = data
}
},
(3) 动态数据渲染界面。
这部分没什么重点,组件内取到orderInfo
之后,渲染到页面即可。
二、提交订单
这个功能主要包括收集参数、提交订单,跳转页面。
(测试这个功能用的老师的账号 :账号:13700000000,密码:111111)
1. 提交订单的接口
//URL:/api/order/auth/submitOrder?tradeNo={tradeNo} method:post
export const reqSubmitOrder = (tradeNo, data) => requests(
{
url: `/order/auth/submitOrder?tradeNo=${tradeNo}`,
data,
method: 'post'
});
接下来我们不再用vuex了。发请求就直接在组件内发了。为了方便使用接口,我们不再按需引入,直接配置到Vue原型上(类似于全局事件总线)。
// main.js
// 引入所有接口
import * as API from '@/api'
console.log(API); // 打印测试一下
new Vue({
// KV一致时省略V[router小写的r]
router,
store,
render: h => h(App),
beforeCreate () {
// 全局事件总线
Vue.prototype.$bus = this
// 接口挂在原型上
Vue.prototype.$API = API
}
}).$mount('#app')
打印API得:
2. 组件内点击提交按钮,发送请求 trade组件
methods:{
// 提交订单
async submitOrder () {
//交易编码,对象解构
let { tradeNo } = this.orderInfo;
//其余的六个参数
let data = {
consignee: this.userDefAddress.consignee, //最终收件人的名字
consigneeTel: this.userDefAddress.phoneNum, //最终收件人的手机号
deliveryAddress: this.userDefAddress.fullAddress, //收件人的地址
paymentWay: 'ONLINE', //支付方式
orderComment: this.notes, //订单备注。data里定义了notes属性,与文本框v-model双向绑定。
orderDetailList: this.orderInfo.detailArrayList, //商品清单
};
// 参数: 订单编号tradeNo, 订单信息 data
let res = await this.$API.reqSubmitOrder(tradeNo, data)
console.log('提交订单', res);
if (res.code === 200) {
// 请求成功,跳转到支付页面。路由跳转+路由传参;res.data是服务器返回的订单编号
this.$router.push('/pay?orderId=' + res.data)
} else {
alert(res.message)
}
},
但是尝试了好多次,都是201失败,不知道为啥。
找出原因了,参数orderDetailList
写错了,写成orderDetaiList
,少了个字母l。
这里服务器返回的订单编号需要带到支付页面。在支付页面根据订单编号发送请求,获取这个订单的支付信息。
三、支付
这两处是需要支付信息payInfo动态渲染的地方。
1. 获取支付信息
(1). 接口
// 获取订单支付信息 /api/payment/weixin/createNative/{orderId} get
export const reqOrderPay = (orderId) => {
return requests({ url: `/payment/weixin/createNative/${orderId}`, method: 'get' })
}
(2). 组件中发送请求
请求成功返回的结果:
2. 支付页面–ElementUI
ElementUI主要用来实现这个点击按钮,弹窗的效果。
ElementUI网址:ElementUI官网
(1) 引入Element UI
- 安装:
npm i element-ui -S
引入方式分为按需引入和全部引入,按需引入可减少项目体积。本系统采用按需引入:
- 安装babel-plugin-component:
npm install babel-plugin-component -D
- 修改配置文件,看官网,直接粘贴复制
-
按需引入,
main.js
文件中// 按需引入Element-ui组件 import { MessageBox } from 'element-ui' // Vue.use(MessageBox) 这样引入刚进入界面就会有弹窗 Vue.component(MessageBox.name, MessageBox) Vue.prototype.$msgbox = MessageBox Vue.prototype.$alert = MessageBox.alert;
修改配置文件,需要重新启动项目
(2) 弹框支付的业务逻辑(这个逻辑其实没那么全)
这里弹框页面好写,重要的是支付的逻辑。盘一下逻辑,弹出支付的小窗口,窗口上有两个按钮:
-
如果用户不点击这两个按钮,直接进行支付:
① 发请求获取支付信息,判断是否支付成功。问题是什么时候判断呢?当小窗口弹出来之后,不知道用户什么时候会判断,所以就需要一直发请求进行判断,采用定时器最合适了。
② 如果判断支付成功,清除定时器,关闭窗口,保存支付成功这个信息。进行路由跳转。
❌③如果支付失败,则,这时候用户只能去点窗口上的按钮了。 -
如果用户点击窗口上的
已支付成功
按钮
① 判断用户是否真的支付(用到了上边②保存支付成功的信息)。真的支付成功的话,那就清除定时器,关闭窗口,跳转路由。 -
如果点击窗口上
支付遇到问题
按钮
① 给出提示信息,关闭弹框,清除定时器。
定时器部分
<script>
// 支付
async open () {
// 如果没有定时器,就开启一个定时器
if (!this.timer) {
this.timer = setInterval(async () => {
// 发请求判断是否已支付,需要一直判断;这个接口一会儿再说
let res = await this.$API.reqPaymentState(this.payInfo.orderId)
if (res.code === 200) {
// 支付成功,清除定时器
clearInterval(this.timer);
this.timer = null
this.code = res.code // 保存支付成功的信息
this.$msgbox.close() // 关闭弹出框
// 路由跳转
this.$router.push("/paysuccess")
}
}, 1000)
}
}
</script>
弹框部分
async open () {
// 支付弹框
this.$alert(`<img src=${url} />`, '请微信支付', {
// 将dangerouslyUseHTMLString属性设置为 true,message 就会被当作 HTML 片段处理。
dangerouslyUseHTMLString: true,
center: true, // 布局居中
showClose: false,// 取消显示右上角的叉
showCancelButton: true, // 显示取消按钮
confirmButtonText: '已支付成功', // 确认按钮
cancelButtonText: '取消支付', // 取消按钮
/* beforeClose MessageBox弹出框关闭前的回调
action:区分取消|确定按钮
instance:当前组件实例
done:关闭弹出框的方法
*/
beforeClose: (action, instance, done) => {
// 如果点击支付遇到问题
if (action == 'cancel') {
alert('支付有问题,请联系xxxx')
clearInterval(this.timer) // 清除定时器
this.timer = null
done() // 关闭弹出框
} else {
// 点击已支付成功,判断是否真的支付成功
if (this.code === 200) {
// 清除定时器
clearInterval(this.timer)
this.timer = null;
done(); // 关闭弹出框
this.$router.push("/paysuccess"); // 路由跳转
}
}
}
}
})
.then(() => { }) // 添加错误捕获,具体解释在下边
.catch(() => { });
}
当点击支付遇到问题
按钮时,会报这个错误。和Promise有关,等看完Promise再来看这个地方。
参考博客:Uncaught (in promise) cancel 报错 及 解决方法
解决方法就是加上错误捕获。
(3) 支付逻辑知识点小总结
① 展示二维码。QRcode
可以在npm官网搜索QRcode;
- 安装:
npm i qrcode
- 引入及使用
import QRcode from 'qrcode'
// 生成二维码,
let url = await QRcode.toDataURL(this.payInfo.codeUrl) //这个url就是图片地址
② 获取订单支付状态reqPaymentState接口
// 查询支付订单状态 /api/payment/weixin/queryPayStatus/{orderId}
export const reqPaymentState = (orderId) => {
return requests({ url: `/payment/weixin/queryPayStatus/${orderId}`, method: 'get' })
}
③ 路由跳转的支付成功页面paySuccess
这是一个新组件,配置好路由即可。没什么重点。
④ 完整代码
⑤⑥⑦⑧⑨⑩
四、个人中心
个人中心里,点击不同的订单,右侧显示对应的内容。
结构类似于之前学Vue2搭的Home、About;
1. 搭建二级路由
(1) 路由:
注意
(1)、子路由的路径不要写/
(2)、如果不写重定向,进入个人中心时,右侧内容就一片空白。重定向是进入个人中心,默认跳转到我的订单这个路由上。
(2) 组件内的声明式导航:
2. 展示动态数据
(1). 接口
//获取个人中心的数据
//api/order/auth/{page}/{limit} get
export const reqMyOrderList = (page, limit) => requests({ url: `/order/auth/${page}/${limit}`, method: 'get' });
(2). 组件获取数据
(3). 页面表格的合并与展示
数据结构与页面的对应关系:
每一个订单都是一个表格,所以v-for
循环订单数组records
,records
有几条数据,就有几个表格。
表头thead
,把对应的数据渲染上去即可
<!-- 显示订单时间,编号和删除操作 -->
<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
,每一行都是一类商品信息,所以v-for
循环订单详细信息orderDetailList
,orderDetailList
有几条数据,表格就有几个行标签tr
。
效果:
对于一个订单,收货人等信息都是一样的,所以我们更希望红框里的收货人等信息,展示一行就行了。用到合并单元格:rowspan
跨行合并单元格td
; rospan
的值取决于表格有几行,也就是orderDetailList
数组的长度。
效果:
原因分析:如果orderDetailList
只有一条数据,那orderDetailList[0]
的收货人等信息就会跨一行合并。如果有两条数据,orderDetailList[0]
的收货人等信息就会跨两行合并,而orderDetailList[1]
的收货人就会被挤到后边。
解决方法:这些信息都一样,那就只展示orderDetailList[0]
的收货人等信息。其余的都不显示。
用template
包裹这些标签,只展示第一行的所有信息。