From a910819c1853081735356c7a2c5e05a2953602ab Mon Sep 17 00:00:00 2001
From: liujie <1793218484@qq.com>
Date: 星期一, 14 九月 2026 17:59:02 +0800
Subject: [PATCH] update

---
 backend/src/main/java/com/tourguide/modules/order/service/MiniTourOrderCommandService.java |   12 ++++
 mingdaojingjiang_guide_app/services/auth.js                                                |    2 
 mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/order-pending-pay/index.vue          |   10 +++
 backend/src/main/java/com/tourguide/modules/user/service/GuideAccountService.java          |   22 ++++---
 mingdaojingjiang_guide_app/manifest.json                                                   |    1 
 mingdaojingjiang_guide_app/pages/workbench/index.vue                                       |   71 +++++++++++++++++------
 backend/src/main/java/com/tourguide/modules/user/service/GuideUserService.java             |   12 ++-
 7 files changed, 96 insertions(+), 34 deletions(-)

diff --git a/backend/src/main/java/com/tourguide/modules/order/service/MiniTourOrderCommandService.java b/backend/src/main/java/com/tourguide/modules/order/service/MiniTourOrderCommandService.java
index ed94927..1e292ec 100644
--- a/backend/src/main/java/com/tourguide/modules/order/service/MiniTourOrderCommandService.java
+++ b/backend/src/main/java/com/tourguide/modules/order/service/MiniTourOrderCommandService.java
@@ -166,6 +166,7 @@
         int unpaidCancelMinutes = scenicSpot == null || scenicSpot.getUnpaidCancelMinutes() == null
                 || scenicSpot.getUnpaidCancelMinutes() < 1
                 ? DEFAULT_UNPAID_CANCEL_MINUTES : scenicSpot.getUnpaidCancelMinutes();
+        cancelExpiredUnpaidIfNeeded(order, unpaidCancelMinutes);
         return TourOrderResponse.from(order)
                 .enrich(scenicSpot, meetingPoint, pricing, guide)
                 .withUnpaidCancelMinutes(unpaidCancelMinutes)
@@ -173,6 +174,17 @@
                         platformSettingsService.getSettings().getUnacceptedAutoRefundMinutes());
     }
 
+    /** 读取订单时惰性取消已超时的未支付订单,避免等待定时任务扫描延迟。 */
+    private void cancelExpiredUnpaidIfNeeded(TourOrder order, int unpaidCancelMinutes) {
+        if (!OrderStatus.PENDING_PAYMENT.matches(order.getStatus())) return;
+        LocalDateTime baseTime = order.getPlacedAt() == null ? order.getCreatedAt() : order.getPlacedAt();
+        if (baseTime == null || baseTime.plusMinutes(unpaidCancelMinutes).isAfter(LocalDateTime.now())) return;
+        if (orderMapper.cancelUnpaid(order.getId()) > 0) {
+            order.setStatus(OrderStatus.CANCELLED.getCode());
+            order.setCancelledAt(LocalDateTime.now());
+        }
+    }
+
     @Transactional
     public TourOrderResponse cancel(Long visitorId, Long orderId) {
         TourOrder order = orderMapper.selectForUpdate(orderId);
diff --git a/backend/src/main/java/com/tourguide/modules/user/service/GuideAccountService.java b/backend/src/main/java/com/tourguide/modules/user/service/GuideAccountService.java
index 4f55282..5271792 100644
--- a/backend/src/main/java/com/tourguide/modules/user/service/GuideAccountService.java
+++ b/backend/src/main/java/com/tourguide/modules/user/service/GuideAccountService.java
@@ -1,6 +1,7 @@
 package com.tourguide.modules.user.service;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.tourguide.common.exception.BusinessException;
 import com.tourguide.modules.order.domain.OrderStatus;
 import com.tourguide.modules.order.entity.TourOrder;
@@ -57,16 +58,17 @@
         if (idCardNo != null && !idCardNo.trim().isEmpty()) {
             guideApplicationMapper.clearIdentityByIdCard(idCardNo);
         }
-        user.setUsername("deleted_" + UUID.randomUUID().toString().replace("-", "").substring(0, 24));
-        user.setMobile(null);
-        user.setCertificateNo(null);
-        user.setIdCardNo(null);
-        user.setIdCardFrontUrl(null);
-        user.setIdCardBackUrl(null);
-        user.setStatus(GuideUserService.STATUS_CANCELLED);
-        user.setAutoDisableWhitelisted(0);
-        user.setAutoDisabled(0);
-        guideUserMapper.updateById(user);
+        guideUserMapper.update(null, new LambdaUpdateWrapper<GuideUser>()
+                .eq(GuideUser::getId, guideId)
+                .set(GuideUser::getUsername, "deleted_" + UUID.randomUUID().toString().replace("-", "").substring(0, 24))
+                .set(GuideUser::getMobile, null)
+                .set(GuideUser::getCertificateNo, null)
+                .set(GuideUser::getIdCardNo, null)
+                .set(GuideUser::getIdCardFrontUrl, null)
+                .set(GuideUser::getIdCardBackUrl, null)
+                .set(GuideUser::getStatus, GuideUserService.STATUS_CANCELLED)
+                .set(GuideUser::getAutoDisableWhitelisted, 0)
+                .set(GuideUser::getAutoDisabled, 0));
         tokenSessionService.remove(payload);
     }
 }
diff --git a/backend/src/main/java/com/tourguide/modules/user/service/GuideUserService.java b/backend/src/main/java/com/tourguide/modules/user/service/GuideUserService.java
index 7ed8357..c68ba55 100644
--- a/backend/src/main/java/com/tourguide/modules/user/service/GuideUserService.java
+++ b/backend/src/main/java/com/tourguide/modules/user/service/GuideUserService.java
@@ -1,6 +1,7 @@
 package com.tourguide.modules.user.service;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.tourguide.common.api.PageResponse;
 import com.tourguide.common.exception.BusinessException;
@@ -332,11 +333,12 @@
     }
 
     private void anonymizeCancelledGuide(GuideUser user) {
-        user.setUsername("deleted_" + UUID.randomUUID().toString().replace("-", "").substring(0, 24));
-        user.setMobile(null);
-        user.setCertificateNo(null);
-        user.setIdCardNo(null);
-        userMapper.updateById(user);
+        userMapper.update(null, new LambdaUpdateWrapper<GuideUser>()
+                .eq(GuideUser::getId, user.getId())
+                .set(GuideUser::getUsername, "deleted_" + UUID.randomUUID().toString().replace("-", "").substring(0, 24))
+                .set(GuideUser::getMobile, null)
+                .set(GuideUser::getCertificateNo, null)
+                .set(GuideUser::getIdCardNo, null));
     }
 
     private void ensureUniqueGuideIdentity(GuideApplication application, GuideUser existingUser) {
diff --git a/mingdaojingjiang_guide_app/manifest.json b/mingdaojingjiang_guide_app/manifest.json
index 28bb7a3..7402aa1 100644
--- a/mingdaojingjiang_guide_app/manifest.json
+++ b/mingdaojingjiang_guide_app/manifest.json
@@ -39,6 +39,7 @@
                     "<uses-permission android:name=\"android.permission.CAMERA\"/>",
                     "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
                     "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
                     "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
                     "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
                     "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
diff --git a/mingdaojingjiang_guide_app/pages/workbench/index.vue b/mingdaojingjiang_guide_app/pages/workbench/index.vue
index 0dc2fe3..0a5eda8 100644
--- a/mingdaojingjiang_guide_app/pages/workbench/index.vue
+++ b/mingdaojingjiang_guide_app/pages/workbench/index.vue
@@ -143,7 +143,7 @@
 
 <script>
 import GuideDialog from '../../components/guide-dialog/guide-dialog.vue'
-import { getLoginState, isLoggedIn } from '../../services/auth'
+import { clearGuideLocalData, getLoginState, isLoggedIn } from '../../services/auth'
 import {
   getCurrentGuideReceivingSession,
   getGuideProfile,
@@ -203,27 +203,54 @@
 }
 
 function dialPhone(phoneNumber) {
+  // App 端(5+):先申请拨号权限,再调用系统拨号界面
   if (typeof plus !== 'undefined' && plus.device && typeof plus.device.dial === 'function') {
-    plus.device.dial(phoneNumber, true)
+    const doDial = () => plus.device.dial(phoneNumber, true)
+    // Android 6.0+ 需运行时申请 CALL_PHONE 权限,iOS 直接拨号
+    if (typeof plus.android !== 'undefined' && typeof plus.android.requestPermissions === 'function') {
+      plus.android.requestPermissions(['android.permission.CALL_PHONE'], (result) => {
+        const denied = [
+          ...(result && result.deniedAlways ? result.deniedAlways : []),
+          ...(result && result.denied ? result.denied : [])
+        ]
+        if (denied.length > 0) {
+          showPhoneCallError()
+          return
+        }
+        doDial()
+      }, () => {
+        showPhoneCallError()
+      })
+    } else {
+      doDial()
+    }
     return true
   }
 
-  if (typeof uni === 'undefined' || typeof uni.makePhoneCall !== 'function') {
-    if (typeof window !== 'undefined' && window.location) {
-      window.location.href = `tel:${phoneNumber}`
-      return true
-    }
-    return false
+  // H5 端:用 <a href="tel:"> 触发,兼容微信 WebView
+  if (typeof window !== 'undefined' && typeof document !== 'undefined' && typeof document.createElement === 'function') {
+    const link = document.createElement('a')
+    link.setAttribute('href', `tel:${phoneNumber}`)
+    link.style.display = 'none'
+    document.body.appendChild(link)
+    link.click()
+    document.body.removeChild(link)
+    return true
   }
 
-  const result = uni.makePhoneCall({
-    phoneNumber,
-    fail: handlePhoneCallFailure
-  })
-  if (result && typeof result.catch === 'function') {
-    result.catch(handlePhoneCallFailure)
+  // 小程序端:uni.makePhoneCall
+  if (typeof uni !== 'undefined' && typeof uni.makePhoneCall === 'function') {
+    const result = uni.makePhoneCall({
+      phoneNumber,
+      fail: handlePhoneCallFailure
+    })
+    if (result && typeof result.catch === 'function') {
+      result.catch(handlePhoneCallFailure)
+    }
+    return true
   }
-  return true
+
+  return false
 }
 
 function buildReceivingConfig(session, scenics = [], fallback = {}) {
@@ -594,13 +621,20 @@
         }
         this.countdownSeconds = Math.max(
           0,
-          Math.ceil((this.countdownFallbackEndAt - Date.now()) / 1000)
+          Math.min(
+            leadMinutes * 60,
+            Math.ceil((this.countdownFallbackEndAt - Date.now()) / 1000)
+          )
         )
         return
       }
       this.countdownFallbackEndAt = null
       const endAt = serving ? startAt + durationMinutes * 60 * 1000 : startAt
-      this.countdownSeconds = Math.max(0, Math.ceil((endAt - Date.now()) / 1000))
+      const maxSeconds = (serving ? durationMinutes : leadMinutes) * 60
+      this.countdownSeconds = Math.max(
+        0,
+        Math.min(maxSeconds, Math.ceil((endAt - Date.now()) / 1000))
+      )
     },
     clearCountdown() {
       if (this.countdownTimer) {
@@ -693,7 +727,8 @@
       })
     },
     resubmit() {
-      routeTo('/pages/register-phone/index')
+      clearGuideLocalData()
+      routeTo('/pages/login/index', 'reLaunch')
     },
     openPolicy() {
       routeTo('/pages/agreements/index')
diff --git a/mingdaojingjiang_guide_app/services/auth.js b/mingdaojingjiang_guide_app/services/auth.js
index 64ab7f1..09b8331 100644
--- a/mingdaojingjiang_guide_app/services/auth.js
+++ b/mingdaojingjiang_guide_app/services/auth.js
@@ -100,7 +100,7 @@
     })
 }
 
-function clearGuideLocalData() {
+export function clearGuideLocalData() {
   [
     GUIDE_LOGIN_KEY,
     GUIDE_ORDERS_KEY,
diff --git a/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/order-pending-pay/index.vue b/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/order-pending-pay/index.vue
index 1870da6..af7f590 100644
--- a/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/order-pending-pay/index.vue
+++ b/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/order-pending-pay/index.vue
@@ -224,6 +224,7 @@
 
 function updateCountdown() {
   if (!paymentDeadline.value) return
+  const previous = remainingSeconds.value
   remainingSeconds.value = Math.max(
     0,
     Math.min(
@@ -231,6 +232,15 @@
       Math.ceil((paymentDeadline.value - Date.now()) / 1000)
     )
   )
+  if (previous > 0 && remainingSeconds.value === 0) {
+    onCountdownExpired()
+  }
+}
+
+function onCountdownExpired() {
+  if (pageActive && order.value.id && !redirecting) {
+    refreshOrderStatus()
+  }
 }
 
 function parseServerTime(value) {

--
Gitblit v1.7.1