vue实践之商场app - 结算、支付与会员中心

接下来是其余部分的实现。

接口

支付相关

在apis文件夹下新建pay.js

1
2
3
4
5
export const getOrderAPI = (id) => {
return request({
url: `/member/order/${id}`
})
}

会员中心

在apis文件夹下新建checkout.js

1
2
3
4
5
6
7
8
9
10
11
12
13
export const getCheckInfoAPI = () => {
return request({
url: 'member/order/pre'
})
}

export const createOrderAPI = (data) => {
return request({
url: 'member/order',
method: 'POST',
data
})
}

新建order.js

1
2
3
4
5
6
7
export const getUserOrderAPI = (params) => {
return request({
url: 'member/order',
method: 'GET',
params
})
}

user.js下新增一个接口

1
2
3
4
5
6
7
8
export const getLikeListAPI = ({ limit = 4 }) => {
return request({
url: '/goods/relevant',
params: {
limit
}
})
}

确认订单网页

在views/checkout文件夹下新建index.vue源码如下

效果图(但这里有不少bug,比如选中的是所有货物而非勾选的货物,还有会漏掉第一项)

其中的这部分

1
<dt><i></i>费:</dt>

可以使用<i></i>来实现有效的字段偏移,实现等宽显示。

独立出来的<el-dialog />部分用于显示切换地址部分。

效果图

其中需要一个接口获取相关信息

1
2
3
4
5
6
7
8
9
const getCheckInfo = async () => {
const res = await getCheckInfoAPI()
checkInfo.value = res.result

const item = checkInfo.value.userAddresses.find(item => item.isDefault === 0)
curAddress.value = item
}

onMounted(() => getCheckInfo())

该方法通过相关接口获取订单信息。至于从购物车到订单的转换,是在后端实现的。同时,将显示地址切换为默认地址(找到默认地址。
若需要切换地址,也需要相应的方法。但需要注意,点击地址不等于立即切换,需要先在一个临时变量中存储切换到的值,点击确认后再将临时变量赋值给显示地址。而临时变量本身也承担着控制高亮组件的功能。

1
2
3
4
5
6
7
8
const switchAddress = (item) => {
activeAddAddress.value = item
}
const confirm = () => {
curAddress.value = activeAddAddress.value
showDialog.value = false
activeAddAddress.value = {}
}

创造订单功能,注入相关数据,并跳转到订单页,开始等待支付

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const createOrder = async () => {
const res = await createOrderAPI({
deliveryTimeType: 1,
payType: 1,
pauChannel: 1,
buyMessage: '',
goods: checkInfo.value.goods.map(item => {
return {
skuId: item.skuId,
count: item.count
}
}),
addressId: curAddress.value.id,
})
const orderId = res.result.id
router.push({
path: '/pay',
query: {
id: orderId
}
})
cartStore.updateNewList()
}

等待支付网页

在views/pay文件夹下新建index.vue源码如下

等待支付页面

js部分除了常规的调用接口,还有调用计数器部分,该部分需要在src/composable下新建文件useCountDown.vue文件,源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { computed, onUnmounted, ref } from "vue"
import dayjs from "dayjs"

export const useCountDown = () => {
let timer = null
const time = ref(0)
const formatTime = computed(() => dayjs.unix(time.value).format('mm分ss秒'))
const start = (currentTime) => {
formatTime.value = currentTime
setInterval(() => {
formatTime.value--
}, 1000)
}

onUnmounted(() => {
timer && clearInterval(timer)
})

return {
formatTime,
start
}
}

dayjs库用于格式化日期输出,此处在计算方法中表现。start部分设立了一个每隔1000毫秒减1的计数器,开始时间由参数决定。

接下来配置沙箱网页和返回网页

1
2
3
4
const baseURL = 'http://pcapi-xiaotuxian-front-devtest.itheima.net/'
const backURL = 'http://127.0.0.1:5173/paycallback'
const redirectUrl = encodeURIComponent(backURL)
const payUrl = `${baseURL}pay/aliPay?orderId=${route.query.id}&redirect=${redirectUrl}`

返回页

从沙箱中跳转后,需要一个页面显示支付情况。在pay文件夹下新建payBack.vue文件,源码如下

效果图,但这两天返回后本地端口直接拒绝链接了,所以这里是直接调出来的失败页面

会员中心页面

其实更贴切的说法是用户中心,但原视频如此,这里不好改变。

这里主要涉及三级路由的实现。在Member文件夹下新建components文件夹和index.vue文件,源码如下

用户信息

在components文件夹下新建UserInfo.vue代码如下

效果图

订单信息

在components文件夹下新建UserOrder.vue代码如下

效果图

源码

确认订单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
<script setup>
import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { getCheckInfoAPI, createOrderAPI } from '../../apis/checkout'
import { useCartStore } from '../../stores/cartStore'

const checkInfo = ref({})
const curAddress = ref({})
const cartStore = useCartStore()
const router = useRouter()

const getCheckInfo = async () => {
const res = await getCheckInfoAPI()
checkInfo.value = res.result

const item = checkInfo.value.userAddresses.find(item => item.isDefault === 0)
curAddress.value = item
}

onMounted(() => getCheckInfo())

const showDialog = ref(false)

const activeAddAddress = ref({})
const switchAddress = (item) => {
activeAddAddress.value = item
}

const confirm = () => {
curAddress.value = activeAddAddress.value
showDialog.value = false
activeAddAddress.value = {}
}

const createOrder = async () => {
const res = await createOrderAPI({
deliveryTimeType: 1,
payType: 1,
pauChannel: 1,
buyMessage: '',
goods: checkInfo.value.goods.map(item => {
return {
skuId: item.skuId,
count: item.count
}
}),
addressId: curAddress.value.id,
})
const orderId = res.result.id
router.push({
path: '/pay',
query: {
id: orderId
}
})
cartStore.updateNewList()
}
</script>

<template>
<div class="xtx-pay-checkout-page">
<div class="container">
<div class="wrapper">
<h3 class="box-title">收货地址</h3>
<div class="box-body">
<div class="address">
<div class="text">
<div class="none" v-if="!curAddress">您需要先添加收货地址才可提交订单</div>
<ul v-else>
<li><span><i /><i />人: </span>{{ curAddress.receiver }}</li>
<li><span>联系方式: </span>{{ curAddress.contact }}</li>
<li><span>收货地址</span>{{ curAddress.fullLocation }} {{ curAddress.address }}</li>
</ul>
</div>
<dic class="action">
<el-button size="large" @click="showDialog = true">切换地址</el-button>
<el-button size="large">添加地址</el-button>
</dic>
</div>
</div>

<h3 class="box-title">商品信息</h3>
<div class="box-body">
<table class="goods">
<thead>
<tr>
<th width="520">商品信息</th>
<th width="170">单价</th>
<th width="170">数量</th>
<th width="170">小计</th>
<th width="170">实付</th>
</tr>
</thead>
<tbody>
<tr v-for="i in checkInfo.goods" :key="i.id">
<td>
<a href="javascript:;" class="info">
<img :src="i.picture" alt="">
<div class="right">
<p>{{ i.name }}</p>
<p>{{ i.attrsText }}</p>
</div>
</a>
</td>
<td>&yen;{{ i.price }}</td>
<td>{{ i.price }}</td>
<td>&yen;{{ i.totalPrice }}</td>
<td>&yen;{{ i.totalPayPrice }}</td>
</tr>
</tbody>
</table>

<h3 class="box-title">配送时间</h3>
<div class="box-body">
<a class="my-btn active" href="javascript:;">不限送货时间:周一至周日</a>
<a class="my-btn" href="javascript:;">工作日送货:周一至周五</a>
<a class="my-btn" href="javascript:;">双休日、假日送货:周六至周日</a>
</div>

<h3 class="box-title">支付方式</h3>
<div class="box-body">
<a class="my-btn active" href="javascript:;">在线支付</a>
<a class="my-btn" href="javascript:;">货到付款</a>
<span style="color:#999">货到付款需付5元手续费</span>
</div>

<h3 class="box-title">金额明细</h3>
<div class="box-body">
<div class="total">
<dl>
<dt>商品件数:</dt>
<dd>{{ checkInfo.summary?.goodsCount }}件</dd>
</dl>
<dl>
<dt>商品总价:</dt>
<dd>¥{{ checkInfo.summary?.totalPrice.toFixed(2) }}</dd>
</dl>
<dl>
<dt><i></i>费:</dt>
<dd>¥{{ checkInfo.summary?.postFee.toFixed(2) }}</dd>
</dl>
<dl>
<dt>应付总额:</dt>
<dd class="price">{{ checkInfo.summary?.totalPayPrice.toFixed(2) }}</dd>
</dl>
</div>
</div>

<div class="submit">
<el-button @click="createOrder" type="primary" size="large">提交订单</el-button>
</div>
</div>
</div>

</div>
</div>
<el-dialog v-model="showDialog" title="切换收货地址" width="30%" center>
<div class="addressWrapper">
<div class="text item" :class="{ active: activeAddAddress.id === item.id }" @click="switchAddress(item)"
v-for="item in checkInfo.userAddresses" :key="item.id">
<ul>
<li><span><i /><i />人:</span>{{ item.receiver }} </li>
<li><span>联系方式:</span>{{ item.contact }}</li>
<li><span>收货地址:</span>{{ item.fullLocation + item.address }}</li>
</ul>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button>取消</el-button>
<el-button type="primary" @click="confirm">确定</el-button>
</span>
</template>
</el-dialog>
</template>

<style scoped lang="scss">
.xtx-pay-checkout-page {
margin-top: 20px;

.wrapper {
background: #fff;
padding: 0 20px;

.box-title {
font-size: 16px;
font-weight: normal;
padding-left: 10px;
line-height: 70px;
border-bottom: 1px solid #f5f5f5;
}

.box-body {
padding: 20px 0;
}
}
}

.address {
border: 1px solid #f5f5f5;
display: flex;
align-items: center;

.text {
flex: 1;
min-height: 90px;
display: flex;
align-items: center;

.none {
line-height: 90px;
color: #999;
text-align: center;
width: 100%;
}

>ul {
flex: 1;
padding: 20px;

li {
line-height: 30px;

span {
color: #999;
margin-right: 5px;

>i {
width: 0.5em;
display: inline-block;
}
}
}
}

>a {
color: $xtxColor;
width: 160px;
text-align: center;
height: 90px;
line-height: 90px;
border-right: 1px solid #f5f5f5;
}
}

.action {
width: 420px;
text-align: center;

.btn {
width: 140px;
height: 46px;
line-height: 44px;
font-size: 14px;

&:first-child {
margin-right: 10px;
}
}
}
}

.goods {
width: 100%;
border-collapse: collapse;
border-spacing: 0;

.info {
display: flex;
text-align: left;

img {
width: 70px;
height: 70px;
margin-right: 20px;
}

.right {
line-height: 24px;

p {
&:last-child {
color: #999;
}
}
}
}

tr {
th {
background: #f5f5f5;
font-weight: normal;
}

td,
th {
text-align: center;
padding: 20px;
border-bottom: 1px solid #f5f5f5;

&:first-child {
border-left: 1px solid #f5f5f5;
}

&:last-child {
border-right: 1px solid #f5f5f5;
}
}
}
}

.my-btn {
width: 228px;
height: 50px;
border: 1px solid #e4e4e4;
text-align: center;
line-height: 48px;
margin-right: 25px;
color: #666666;
display: inline-block;

&.active,
&:hover {
border-color: $xtxColor;
}
}

.total {
dl {
display: flex;
justify-content: flex-end;
line-height: 50px;

dt {
i {
display: inline-block;
width: 2em;
}
}

dd {
width: 240px;
text-align: right;
padding-right: 70px;

&.price {
font-size: 20px;
color: $priceColor;
}
}
}
}

.submit {
text-align: right;
padding: 60px;
border-top: 1px solid #f5f5f5;
}

.addressWrapper {
max-height: 500px;
overflow-y: auto;
}

.text {
flex: 1;
min-height: 90px;
display: flex;
align-items: center;

&.item {
border: 1px solid #f5f5f5;
margin-bottom: 10px;
cursor: pointer;

&.active,
&:hover {
border-color: $xtxColor;
background: $xtxColor;
}

>ul {
padding: 10px;
font-size: 14px;
line-height: 30px;
}
}
}
</style>

等待支付

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<script setup>
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { getOrderAPI } from '@/apis/pay'
import { useCountDown } from '../../composables/useCountDown'

const { formatTime, start } = useCountDown()
const orderInfo = ref({})
const route = useRoute()
const getOrderInfo = async () => {
const res = await getOrderAPI(route.query.id)
orderInfo.value = res.result
start(res.result.countdown)
}

onMounted(() => getOrderInfo())

const baseURL = 'http://pcapi-xiaotuxian-front-devtest.itheima.net/'
const backURL = 'http://127.0.0.1:5173/paycallback'
const redirectUrl = encodeURIComponent(backURL)
const payUrl = `${baseURL}pay/aliPay?orderId=${route.query.id}&redirect=${redirectUrl}`

</script>

<template>

<div class="xtx-pay-page">
<div class="container">
<div class="pay-info">
<span class="iconfont icon-queren"></span>
<div class="tip">
<p>订单提交成功,请尽快完成支付</p>
<p>支付还剩<span>{{ formatTime }}</span>,超时后将取消订单</p>
</div>
<div class="amount">
<span>应付总额:</span>
<span>&yen; {{ orderInfo.payMoney?.toFixed(2) }}</span>
</div>
</div>
</div>

<div class="pay-type">
<p class="head">请选择以下支付方式付款</p>
<div class="item">
<p>支付平台</p>
<a class="btn wx" href="javascript:;"></a>
<a class="btn alipay" :href="payUrl"></a>
</div>
<div class="item">
<a class="btn" href="javascript:;">招商银行</a>
<a class="btn" href="javascript:;">工商银行</a>
<a class="btn" href="javascript:;">建设银行</a>
<a class="btn" href="javascript:;">农业银行</a>
<a class="btn" href="javascript:;">交通银行</a>
</div>
</div>
</div>
</template>

<style scoped lang="scss">
.xtx-pay-page {
margin-top: 20px;
}

.pay-info {

background: #fff;
display: flex;
align-items: center;
height: 240px;
padding: 0 80px;

.icon {
font-size: 80px;
color: #1dc779;
}

.tip {
padding-left: 10px;
flex: 1;

p {
&:first-child {
font-size: 20px;
margin-bottom: 5px;
}

&:last-child {
color: #999;
font-size: 16px;
}
}
}

.amount {
span {
&:first-child {
font-size: 16px;
color: #999;
}

&:last-child {
color: $priceColor;
font-size: 20px;
}
}
}
}

.pay-type {
margin-top: 20px;
background-color: #fff;
padding-bottom: 70px;

p {
line-height: 70px;
height: 70px;
padding-left: 30px;
font-size: 16px;

&.head {
border-bottom: 1px solid #f5f5f5;
}
}

.btn {
width: 150px;
height: 50px;
border: 1px solid #e4e4e4;
text-align: center;
line-height: 48px;
margin-left: 30px;
color: #666666;
display: inline-block;

&.active,
&:hover {
border-color: $xtxColor;
}

&.alipay {
background: url(https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/7b6b02396368c9314528c0bbd85a2e06.png) no-repeat center / contain;
}

&.wx {
background: url(https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/c66f98cff8649bd5ba722c2e8067c6ca.jpg) no-repeat center / contain;
}
}
}
</style>

返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<script setup>
import { getOrderAPI } from '@/apis/pay'
import { onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';

const orderInfo = ref({})
const route = useRoute()

const getOrderInfo = async () => {
const res = await getOrderAPI(route.query.orderId)
orderInfo.value = res.result
}

onMounted(() => getOrderInfo())
</script>

<template>
<div class="xtx-pay-page">
<div class="container">
<div class="pay-result">
<span class="iconfont icon-queren green" v-if="$route.query.payResult === 'true'"></span>
<span class="iconfont icon-shanchu red" v-else></span>
<p class="tit">支付{{ $route.query.payResult === 'true' ? '成功' : '失败' }}</p>
<p class="tip">我们将尽快为您发货,收货期间请保持手机畅通</p>
<p>支付方式:<span>支付宝</span></p>
<p>支付金额:<span>&yen;{{ orderInfo.payMoney?.toFixed(2) }}</span></p>
<div class="btn">
<el-button type="primary" style="margin-right: 20px;">查看订单</el-button>
<el-button>进入首页</el-button>
</div>
<p class="alert">
<span class="iconfont icon-tip"></span>
温馨提示:小兔鲜儿不会以订单异常、系统升级为由要求您点击任何网址链接进行退款操作,谨防诈骗!
</p>
</div>
</div>
</div>
</template>

<style scoped lang="scss">
.pay-result {
padding: 100px 0;
background: #fff;
text-align: center;
margin-top: 20px;

>.iconfont {
font-size: 100px;
}

.green {
color: #1dc779;
}

.red {
color: $priceColor;
}

.tit {
font-size: 24px;
}

.tip {
color: #999;
}

p {
line-height: 40px;
font-size: 16px;
}

.btn {
margin-top: 50px;
}

.alert {
font-size: 12px;
color: #999;
margin-top: 50px;
}
}
</style>

会员开始页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<script setup>

</script>

<template>
<div class="container">
<div class="xtx-member-aside">
<div class="user-manage">
<h4>我的账户</h4>
<div class="links">
<router-link to="/member">个人中心</router-link>
</div>
<h4>交易管理</h4>
<div class="links">
<router-link to="/member/order">我的订单</router-link>
</div>
</div>
</div>
<div class="article">
<router-view />
</div>
</div>
</template>

<style scoped lang="scss">
.container {
display: flex;
padding-top: 20px;

.xtx-member-aside {
width: 220px;
margin-right: 20px;
border-radius: 2px;
background-color: #fff;

.user-manage {
background-color: #fff;

h4 {
font-size: 18px;
font-weight: 400;
padding: 20px 52px 5px;
border-top: 1px solid #f6f6f6;
}

.links {
padding: 0 52px 10px;
}

a {
display: block;
line-height: 1;
padding: 15px 0;
font-size: 14px;
color: #666;
position: relative;

&:hover {
color: $xtxColor;
}

&.active,
&.router-link-exact-active {
color: $xtxColor;

&:before {
display: block;
}
}

&:before {
content: '';
display: none;
width: 6px;
height: 6px;
border-radius: 50%;
position: absolute;
top: 19px;
left: -16px;
background-color: $xtxColor;
}
}
}
}

.article {
width: 1000px;
background-color: #fff;
}
}
</style>

UserInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
<script setup>
import { useUserStore } from '../../../stores/user'
import { getLikeListAPI } from '../../../apis/user'
import { onMounted, ref } from 'vue'
import GoodsItem from '../../Home/components/GoodsItem.vue'

const userStore = useUserStore()

const likeList = ref([])
const getLikeList = async () => {
const res = await getLikeListAPI({ limit: 4 })
likeList.value = res.result
}

onMounted(() => getLikeList())
</script>

<template>
<div class="home-overview">
<div class="user-meta">
<div class="avatar"><img :src="userStore.userInfo?.avatar" alt="" /></div>
<h4>{{ userStore.userInfo?.account }}</h4>
</div>
<div class="item">
<a href="javascript:;">
<span class="iconfont icon-huiyuan"></span>
<p>会员中心</p>
</a>
<a href="javascript:;">
<span class="iconfont icon-anquan"></span>
<p>安全设置</p>
</a>
<a href="javascript:;">
<span class="iconfont icon-dw"></span>
<p>地址管理</p>
</a>
</div>
</div>
<div class="like-container">
<div class="home-panel">
<div class="header">
<h4 data-v-bcb266e0="">猜你喜欢</h4>
</div>
<div class="goods-list">
<GoodsItem v-for="good in likeList" :key="good.id" :goods="good" />
</div>
</div>
</div>
</template>

<style scoped lang="scss">
.home-overview {
height: 132px;
background: url(@/assets/images/center-bg.png) no-repeat center / cover;
display: flex;

.user-meta {
flex: 1;
display: flex;
align-items: center;

.avatar {
width: 85px;
height: 85px;
border-radius: 50%;
overflow: hidden;
margin-left: 60px;

img {
width: 100%;
height: 100%;
}
}

h4 {
padding-left: 26px;
font-size: 18px;
font-weight: normal;
color: white;
}
}

.item {
flex: 1;
display: flex;
align-items: center;
justify-content: space-around;

&:first-child {
border-right: 1px solid #f4f4f4;
}

a {
color: white;
font-size: 16px;
text-align: center;

.iconfont {
font-size: 32px;
}

p {
line-height: 32px;
}
}
}
}

.like-container {
margin-top: 20px;
border-radius: 4px;
background-color: #fff;
}

.home-panel {
background-color: #fff;
padding: 0 20px;
margin-top: 20px;
height: 400px;

.header {
height: 66px;
border-bottom: 1px solid #f5f5f5;
padding: 18px 0;
display: flex;
justify-content: space-between;
align-items: baseline;

h4 {
font-size: 22px;
font-weight: 400;
}

}

.goods-list {
display: flex;
justify-content: space-around;
}
}
</style>

UserOrder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
<script setup>
import { onMounted, ref } from 'vue';
import { getUserOrderAPI } from '@/apis/order'

const tabTypes = [
{ name: "all", label: "全部订单" },
{ name: "unpay", label: "待付款" },
{ name: "deliver", label: "待发货" },
{ name: "receive", label: "待收货" },
{ name: "comment", label: "待评价" },
{ name: "complete", label: "已完成" },
{ name: "cancel", label: "已取消" }
]

const orderList = ref([])
const total = ref(0)
const params = ref({
orderState: 0,
page: 1,
pageSize: 2
})

const getOrderList = async () => {
const res = await getUserOrderAPI(params.value)
orderList.value = res.result.items
total.value = res.result.counts
}

onMounted(() => getOrderList())

const tabChange = (type) => {
params.value.orderState = type
getOrderList()
}

const pageChange = (page) => {
console.log(page);
params.value.page = page
getOrderList()
}

const formatPayState = (payState) => {
const stateMap = {
1: '待付款',
2: '待发货',
3: '待收货',
4: '待评价',
5: '已完成',
6: '已取消'
}
return stateMap[payState]
}
</script>

<template>
<div class="order-container">
<el-tabs @tab-change="tabChange">
<el-tab-pane v-for="item in tabTypes" :key="item.name" :label="item.label" />

<div class="main-container">
<div class="holder-container" v-if="orderList.length === 0"><el-empty description="暂无数据" /></div>
<div v-else>
<div class="order-item" v-for="order in orderList" :key="order.id">
<div class="head">
<span>下单时间:{{ order.createTime }}</span>
<span>订单编号:{{ order.id }}</span>
<span class="down-time" v-if="order.orderState === 1">
<i class="iconfont icon-down-time"></i>
<b>付款截止:{{ order.countdown }}</b>
</span>
</div>
<div class="body">
<div class="column goods">
<ul>
<li v-for="item in order.skus" :key="item.id">
<a class="image" href="javascript:;"><img :src="item.image" alt="" /></a>
<div class="info">
<p class="name ellipsis-2">{{ item.name }}</p>
<p class="attr ellipsis"><span>{{ item.attrsText }}</span></p>
</div>
<div class="price">&yen;{{ item.realPay?.toFixed(2) }}</div>
<div class="count">×{{ item.quantity }}</div>
</li>
</ul>
</div>
<div class="column state">
<p>{{ formatPayState(order.orderState) }}</p>
<p v-if="order.orderState === 3"><a href="javascript:;" class="green">查看物流</a></p>
<p v-if="order.orderState === 4"><a href="javascript:;" class="green">评价商品</a></p>
<p v-if="order.orderState === 5"><a href="javascript:;" class="green">查看评价</a></p>
</div>
<div class="column amount">
<p class="red">&yen;{{ order.payMoney?.toFixed(2) }}</p>
<p>(含运费:{{ order.postFree?.toFixed(2) }})</p>
<p>在线支付</p>
</div>
<div class="column action">
<el-button v-if="order.orderState === 1" type="primary" size="small">立即付款</el-button>
<el-button v-if="order.orderState === 3" type="primary" size="small">确认收货</el-button>
<p><a href="javascript:;">查看详情</a></p>
<p v-if="[2, 3, 4, 5].includes(order.orderState)"><a href="javascript:;">再次购买</a></p>
<p v-if="[4, 5].includes(order.orderState)"><a href="javascript:;">申请售后</a></p>
<p v-if="order.orderState === 1"><a href="javascript:;">取消订单</a></p>
</div>
</div>
</div>
<div class="pagination-container"><el-pagination :total="total" @current-change="pageChange" :page-size="params.pageSize"
background layout="prev, pager, next" /></div>
</div>
</div>
</el-tabs>
</div>
</template>

<style scoped lang="scss">
.order-container {
padding: 10px 20px;

.pagination-container {
display: flex;
justify-content: center;
}

.main-container {
min-height: 500px;

.holder-container {
min-height: 500px;
display: flex;
justify-content: center;
align-items: center;
}
}
}

.order-item {
margin-bottom: 20px;
border: 1px solid #f5f5f5;

.head {
height: 50px;
line-height: 50px;
background: #f5f5f5;
padding: 0 20px;
overflow: hidden;

span {
margin-right: 20px;

&.down-time {
margin-right: 0;
float: right;

i {
vertical-align: middle;
margin-right: 3px;
}

b {
vertical-align: middle;
font-weight: normal;
}
}
}

.del {
margin-right: 0;
float: right;
color: #999;
}
}

.body {
display: flex;
align-items: stretch;

.column {
border-left: 1px solid #f5f5f5;
text-align: center;
padding: 20px;

>p {
padding-top: 10px;
}

&:first-child {
border-left: none;
}

&.goods {
flex: 1;
padding: 0;
align-self: center;

ul {
li {
border-bottom: 1px solid #f5f5f5;
padding: 10px;
display: flex;

&:last-child {
border-bottom: none;
}

.image {
width: 70px;
height: 70px;
border: 1px solid #f5f5f5;
}

.info {
width: 220px;
text-align: left;
padding: 0 10px;

p {
margin-bottom: 5px;

&.name {
height: 38px;
}

&.attr {
color: #999;
font-size: 12px;

span {
margin-right: 5px;
}
}
}
}

.price {
width: 100px;
}

.count {
width: 80px;
}
}
}
}

&.state {
width: 120px;

.green {
color: $xtxColor;
}
}

&.amount {
width: 200px;

.red {
color: $priceColor;
}
}

&.action {
width: 140px;

a {
display: block;

&:hover {
color: $xtxColor;
}
}
}
}
}
}
</style>

vue实践之商场app - 结算、支付与会员中心
http://example.com/2025/05/08/mall-front-8/
作者
Ivan Chen
发布于
2025年5月8日
许可协议
IVAN