hookehuyr

feat(订单): 添加支付倒计时功能并优化订单状态管理

- 在订单卡片和订单列表中添加支付倒计时显示
- 实现倒计时自动更新和超时自动取消订单功能
- 优化订单状态更新逻辑,确保响应式更新
- 添加倒计时定时器清理机制,防止内存泄漏
1 <!-- 1 <!--
2 * @Date: 2023-12-20 14:11:11 2 * @Date: 2023-12-20 14:11:11
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-07-15 15:56:28 4 + * @LastEditTime: 2025-07-16 18:20:38
5 * @FilePath: /jgdl/src/components/payCard.vue 5 * @FilePath: /jgdl/src/components/payCard.vue
6 * @Description: 文件描述 6 * @Description: 文件描述
7 --> 7 -->
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
11 <view style="padding: 2rem 1rem; text-align: center;"> 11 <view style="padding: 2rem 1rem; text-align: center;">
12 <view style="font-size: 32rpx;">实付金额</view> 12 <view style="font-size: 32rpx;">实付金额</view>
13 <view style="color: red; margin: 10rpx 0;"><text style="font-size: 50rpx;">¥</text><text style="font-size: 80rpx;">{{ price }}</text></view> 13 <view style="color: red; margin: 10rpx 0;"><text style="font-size: 50rpx;">¥</text><text style="font-size: 80rpx;">{{ price }}</text></view>
14 - <!-- <view style="font-size: 28rpx; margin-bottom: 20rpx;">支付剩余时间 <text style="color: red;">{{ formatTime(remain_time) }}</text></view> --> 14 + <view style="font-size: 28rpx; margin-bottom: 20rpx;">支付剩余时间 <text style="color: red;">{{ formatTime(remain_time) }}</text></view>
15 <nut-button block color="#fb923c" @tap="goToPay">立即支付</nut-button> 15 <nut-button block color="#fb923c" @tap="goToPay">立即支付</nut-button>
16 </view> 16 </view>
17 </nut-action-sheet> 17 </nut-action-sheet>
......
...@@ -709,3 +709,15 @@ ...@@ -709,3 +709,15 @@
709 color: #374151; 709 color: #374151;
710 line-height: 1.5; 710 line-height: 1.5;
711 } 711 }
712 +
713 +.payment-countdown {
714 + display: flex;
715 + justify-content: flex-end;
716 + margin: 16rpx 0;
717 +}
718 +
719 +.countdown-text {
720 + color: #ff4757;
721 + font-size: 24rpx;
722 + font-weight: 500;
723 +}
......
1 <!-- 1 <!--
2 * @Date: 2022-09-19 14:11:06 2 * @Date: 2022-09-19 14:11:06
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-07-14 18:18:12 4 + * @LastEditTime: 2025-07-16 18:22:16
5 * @FilePath: /jgdl/src/pages/myOrders/index.vue 5 * @FilePath: /jgdl/src/pages/myOrders/index.vue
6 * @Description: 订单管理页面 6 * @Description: 订单管理页面
7 --> 7 -->
...@@ -77,8 +77,13 @@ ...@@ -77,8 +77,13 @@
77 </nut-col> 77 </nut-col>
78 </nut-row> 78 </nut-row>
79 79
80 + <!-- 支付剩余时间 -->
81 + <view v-if="viewMode === 'buy' && order.status === 3" class="payment-countdown">
82 + <text class="countdown-text">剩余支付时间:{{ formatCountdown(order.remain_time) }}</text>
83 + </view>
84 +
80 <!-- 操作按钮 --> 85 <!-- 操作按钮 -->
81 - <view class="order-actions"> 86 + <view v-if="!order.is_sold" class="order-actions">
82 <!-- 买车模式:待支付状态 --> 87 <!-- 买车模式:待支付状态 -->
83 <template v-if="viewMode === 'buy' && order.status === 3"> 88 <template v-if="viewMode === 'buy' && order.status === 3">
84 <nut-button type="default" size="small" @click="handleCancelOrder(order.id)" 89 <nut-button type="default" size="small" @click="handleCancelOrder(order.id)"
...@@ -309,7 +314,7 @@ ...@@ -309,7 +314,7 @@
309 </template> 314 </template>
310 315
311 <script setup> 316 <script setup>
312 -import { ref, computed, onMounted } from 'vue' 317 +import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
313 import Taro from '@tarojs/taro' 318 import Taro from '@tarojs/taro'
314 import './index.less' 319 import './index.less'
315 import { $ } from '@tarojs/extend' 320 import { $ } from '@tarojs/extend'
...@@ -361,6 +366,9 @@ const soldOrders = ref([]) ...@@ -361,6 +366,9 @@ const soldOrders = ref([])
361 const currentPage = ref(0) 366 const currentPage = ref(0)
362 const pageLimit = ref(10) 367 const pageLimit = ref(10)
363 368
369 +// 倒计时相关状态
370 +const countdownIntervals = ref(new Map()) // 存储每个订单的倒计时定时器
371 +
364 /** 372 /**
365 * 根据当前视图模式和筛选条件获取过滤后的订单列表 373 * 根据当前视图模式和筛选条件获取过滤后的订单列表
366 */ 374 */
...@@ -425,10 +433,17 @@ const loadOrderData = async (isLoadMore = false) => { ...@@ -425,10 +433,17 @@ const loadOrderData = async (isLoadMore = false) => {
425 // 处理多个订单数据 433 // 处理多个订单数据
426 const newOrders = response.data.list.map(orderData => { 434 const newOrders = response.data.list.map(orderData => {
427 // 处理details数组,取第一个元素作为details对象 435 // 处理details数组,取第一个元素作为details对象
428 - return { 436 + const processedOrder = {
429 ...orderData, 437 ...orderData,
430 details: orderData.details && orderData.details.length > 0 ? orderData.details[0] : null 438 details: orderData.details && orderData.details.length > 0 ? orderData.details[0] : null
431 } 439 }
440 +
441 + // 为待支付订单添加mock的倒计时时间(1800秒用于测试)
442 + if (processedOrder.status === 3) {
443 + processedOrder.remain_time = processedOrder.pay_time || 1800
444 + }
445 +
446 + return processedOrder
432 }) 447 })
433 448
434 const targetOrders = viewMode.value === 'buy' ? boughtOrders : soldOrders 449 const targetOrders = viewMode.value === 'buy' ? boughtOrders : soldOrders
...@@ -441,6 +456,13 @@ const loadOrderData = async (isLoadMore = false) => { ...@@ -441,6 +456,13 @@ const loadOrderData = async (isLoadMore = false) => {
441 currentPage.value = 0 456 currentPage.value = 0
442 } 457 }
443 458
459 + // 为待支付订单启动倒计时
460 + newOrders.forEach(order => {
461 + if (order.status === 3 && order.remain_time) {
462 + startCountdown(order)
463 + }
464 + })
465 +
444 // 判断是否还有更多数据 466 // 判断是否还有更多数据
445 hasMore.value = newOrders.length === pageLimit.value 467 hasMore.value = newOrders.length === pageLimit.value
446 } else { 468 } else {
...@@ -535,12 +557,124 @@ const getStatusClass = (status) => { ...@@ -535,12 +557,124 @@ const getStatusClass = (status) => {
535 } 557 }
536 558
537 /** 559 /**
560 + * 格式化倒计时显示
561 + * @param {number} seconds - 剩余秒数
562 + * @returns {string} 格式化的时间字符串 HH:MM:SS
563 + */
564 +const formatCountdown = (seconds) => {
565 + if (!seconds || seconds <= 0) {
566 + return '00:00:00'
567 + }
568 +
569 + const hours = Math.floor(seconds / 3600)
570 + const minutes = Math.floor((seconds % 3600) / 60)
571 + const secs = seconds % 60
572 +
573 + return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
574 +}
575 +
576 +/**
577 + * 启动订单倒计时
578 + * @param {Object} order - 订单对象
579 + */
580 +const startCountdown = (order) => {
581 + if (!order || order.status !== 3 || !order.remain_time) {
582 + return
583 + }
584 +
585 + // 清除已存在的定时器
586 + if (countdownIntervals.value.has(order.id)) {
587 + clearInterval(countdownIntervals.value.get(order.id))
588 + }
589 +
590 + const timer = setInterval(async () => {
591 + const orders = viewMode.value === 'buy' ? boughtOrders : soldOrders
592 + const targetOrderIndex = orders.value.findIndex(o => o.id === order.id)
593 +
594 + if (targetOrderIndex !== -1) {
595 + const targetOrder = orders.value[targetOrderIndex]
596 +
597 + if (targetOrder.remain_time > 0) {
598 + // 创建新的订单对象来触发响应式更新
599 + const updatedOrder = {
600 + ...targetOrder,
601 + remain_time: targetOrder.remain_time - 1
602 + }
603 +
604 + // 替换数组中的订单对象
605 + orders.value.splice(targetOrderIndex, 1, updatedOrder)
606 +
607 + // 使用nextTick确保DOM更新
608 + await nextTick()
609 + } else {
610 + // 时间到,取消订单
611 + clearInterval(timer)
612 + countdownIntervals.value.delete(order.id)
613 + handleTimeoutCancel(targetOrder)
614 + }
615 + } else {
616 + // 订单不存在,清除定时器
617 + clearInterval(timer)
618 + countdownIntervals.value.delete(order.id)
619 + }
620 + }, 1000)
621 +
622 + countdownIntervals.value.set(order.id, timer)
623 +}
624 +
625 +/**
626 + * 处理超时取消订单
627 + * @param {Object} order - 订单对象
628 + */
629 +const handleTimeoutCancel = async (order) => {
630 + // 清除该订单的倒计时定时器
631 + if (countdownIntervals.value.has(order.id)) {
632 + clearInterval(countdownIntervals.value.get(order.id))
633 + countdownIntervals.value.delete(order.id)
634 + }
635 +
636 + // 更新订单状态为已取消
637 + const orders = viewMode.value === 'buy' ? boughtOrders : soldOrders
638 + const targetOrderIndex = orders.value.findIndex(o => o.id === order.id)
639 +
640 + if (targetOrderIndex !== -1) {
641 + // 创建新的订单对象来触发响应式更新
642 + const updatedOrder = {
643 + ...orders.value[targetOrderIndex],
644 + status: 7
645 + }
646 +
647 + // 替换数组中的订单对象
648 + orders.value.splice(targetOrderIndex, 1, updatedOrder)
649 +
650 + // 使用nextTick确保DOM更新
651 + await nextTick()
652 + }
653 +
654 + Taro.showToast({
655 + title: '订单已超时取消',
656 + icon: 'none',
657 + duration: 2000
658 + })
659 +}
660 +
661 +/**
662 + * 清除所有倒计时定时器
663 + */
664 +const clearAllCountdowns = () => {
665 + countdownIntervals.value.forEach((timer) => {
666 + clearInterval(timer)
667 + })
668 + countdownIntervals.value.clear()
669 +}
670 +
671 +/**
538 * 处理支付 672 * 处理支付
539 */ 673 */
540 -const handlePayment = ({ id, total_amount }) => { 674 +const handlePayment = ({ id, total_amount, remain_time }) => {
541 onPay({ 675 onPay({
542 id, 676 id,
543 - remain_time: 1800, // 30分钟 677 + remain_time, // 30分钟
544 price: total_amount 678 price: total_amount
545 }) 679 })
546 } 680 }
...@@ -775,8 +909,26 @@ const performCancelOrder = async (orderId) => { ...@@ -775,8 +909,26 @@ const performCancelOrder = async (orderId) => {
775 const order = orders.value.find(o => o.id === orderId) 909 const order = orders.value.find(o => o.id === orderId)
776 910
777 if (order) { 911 if (order) {
912 + // 清除该订单的倒计时定时器
913 + if (countdownIntervals.value.has(orderId)) {
914 + clearInterval(countdownIntervals.value.get(orderId))
915 + countdownIntervals.value.delete(orderId)
916 + }
917 +
778 // 更新订单状态为已取消 918 // 更新订单状态为已取消
779 - order.status = 7 919 + const orders = viewMode.value === 'buy' ? boughtOrders : soldOrders
920 + const targetOrderIndex = orders.value.findIndex(o => o.id === orderId)
921 +
922 + if (targetOrderIndex !== -1) {
923 + // 创建新的订单对象来触发响应式更新
924 + const updatedOrder = {
925 + ...orders.value[targetOrderIndex],
926 + status: 7
927 + }
928 +
929 + // 替换数组中的订单对象
930 + orders.value.splice(targetOrderIndex, 1, updatedOrder)
931 + }
780 932
781 Taro.showToast({ 933 Taro.showToast({
782 title: '订单已取消', 934 title: '订单已取消',
...@@ -882,6 +1034,11 @@ onMounted(async () => { ...@@ -882,6 +1034,11 @@ onMounted(async () => {
882 } 1034 }
883 }, 500); 1035 }, 500);
884 }) 1036 })
1037 +
1038 +// 页面卸载时清理定时器
1039 +onUnmounted(() => {
1040 + clearAllCountdowns()
1041 +})
885 </script> 1042 </script>
886 1043
887 <script> 1044 <script>
......