feat(订单): 添加支付倒计时功能并优化订单状态管理
- 在订单卡片和订单列表中添加支付倒计时显示 - 实现倒计时自动更新和超时自动取消订单功能 - 优化订单状态更新逻辑,确保响应式更新 - 添加倒计时定时器清理机制,防止内存泄漏
Showing
3 changed files
with
178 additions
and
9 deletions
| 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> | ... | ... |
-
Please register or login to post a comment