From 77649c76ec1c3afcb93e9963243d637665b3a307 Mon Sep 17 00:00:00 2001
From: liujie <1793218484@qq.com>
Date: 星期五, 21 八月 2026 16:56:28 +0800
Subject: [PATCH] update

---
 backend/src/main/java/com/tourguide/modules/order/domain/PaymentMethod.java                    |   15 +
 backend/src/main/resources/application-dev.yml                                                 |    3 
 data/guide-applications/20563e26d1bc43c4a1aac5abc87ae480.jpg                                   |    0 
 mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/order-pending-guide/index.vue            |   86 ++++++
 mingdaojingjiang_guide_app/services/order.js                                                   |    7 
 mingdaojingjiang_guide_app/pages/workbench/index.vue                                           |   33 ++
 backend/src/test/java/com/tourguide/modules/order/service/OrderRefundServiceTest.java          |   17 +
 backend/src/main/java/com/tourguide/api/guide/GuideTourOrderController.java                    |    6 
 mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/home/index.vue                           |   38 +-
 backend/src/main/java/com/tourguide/modules/order/service/TourOrderPaymentService.java         |    5 
 backend/src/main/java/com/tourguide/modules/order/service/GuideTourOrderService.java           |   60 ++++
 backend/src/main/java/com/tourguide/modules/order/service/MiniTourOrderCommandService.java     |   23 +
 backend/src/test/java/com/tourguide/modules/order/service/GuideTourOrderServiceTest.java       |   91 +++++++
 backend/src/test/java/com/tourguide/modules/order/service/MiniTourOrderCommandServiceTest.java |  134 ++++++---
 mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/order-serving/index.vue                  |  232 ++++++++++++++---
 data/guide-applications/1e3862aa6fda49d580017e360126062a.jpg                                   |    0 
 backend/src/main/java/com/tourguide/modules/order/service/OrderRefundService.java              |    8 
 data/guide-applications/b57f19850ed74770bf45906b9d3d2b19.jpg                                   |    0 
 data/guide-applications/7eff1dce4fc54626bde8a8b05021b5fd.jpg                                   |    0 
 backend/src/main/java/com/tourguide/modules/user/mapper/GuideReceivingSessionMapper.java       |    8 
 20 files changed, 630 insertions(+), 136 deletions(-)

diff --git a/backend/src/main/java/com/tourguide/api/guide/GuideTourOrderController.java b/backend/src/main/java/com/tourguide/api/guide/GuideTourOrderController.java
index f2bb9ef..5c11ce8 100644
--- a/backend/src/main/java/com/tourguide/api/guide/GuideTourOrderController.java
+++ b/backend/src/main/java/com/tourguide/api/guide/GuideTourOrderController.java
@@ -31,4 +31,10 @@
                                                   @PathVariable Long id) {
         return ApiResponse.success(orderService.detail(principal.getUserId(), id));
     }
+
+    @PostMapping("/{id}/start")
+    public ApiResponse<GuideOrderResponse> start(@AuthenticationPrincipal AppUserPrincipal principal,
+                                                 @PathVariable Long id) {
+        return ApiResponse.success(orderService.start(principal.getUserId(), id));
+    }
 }
diff --git a/backend/src/main/java/com/tourguide/modules/order/domain/PaymentMethod.java b/backend/src/main/java/com/tourguide/modules/order/domain/PaymentMethod.java
new file mode 100644
index 0000000..ca67f2e
--- /dev/null
+++ b/backend/src/main/java/com/tourguide/modules/order/domain/PaymentMethod.java
@@ -0,0 +1,15 @@
+package com.tourguide.modules.order.domain;
+
+/** 订单支付方式。 */
+public enum PaymentMethod {
+    WECHAT,
+    TEMPORARY_BYPASS;
+
+    public String getCode() {
+        return name();
+    }
+
+    public boolean matches(String code) {
+        return name().equals(code);
+    }
+}
diff --git a/backend/src/main/java/com/tourguide/modules/order/service/GuideTourOrderService.java b/backend/src/main/java/com/tourguide/modules/order/service/GuideTourOrderService.java
index 214bc65..31135b0 100644
--- a/backend/src/main/java/com/tourguide/modules/order/service/GuideTourOrderService.java
+++ b/backend/src/main/java/com/tourguide/modules/order/service/GuideTourOrderService.java
@@ -8,9 +8,14 @@
 import com.tourguide.modules.order.dto.GuideOrderResponse;
 import com.tourguide.modules.order.entity.TourOrder;
 import com.tourguide.modules.order.mapper.TourOrderMapper;
+import com.tourguide.modules.user.entity.GuideReceivingSession;
+import com.tourguide.modules.user.mapper.GuideRealtimeStatusMapper;
+import com.tourguide.modules.user.mapper.GuideReceivingSessionMapper;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.StringUtils;
 
+import java.time.LocalDateTime;
 import java.util.Arrays;
 import java.util.stream.Collectors;
 
@@ -23,9 +28,14 @@
     private static final String TAB_COMPLETED = "completed";
 
     private final TourOrderMapper orderMapper;
+    private final GuideReceivingSessionMapper sessionMapper;
+    private final GuideRealtimeStatusMapper realtimeStatusMapper;
 
-    public GuideTourOrderService(TourOrderMapper orderMapper) {
+    public GuideTourOrderService(TourOrderMapper orderMapper, GuideReceivingSessionMapper sessionMapper,
+                                 GuideRealtimeStatusMapper realtimeStatusMapper) {
         this.orderMapper = orderMapper;
+        this.sessionMapper = sessionMapper;
+        this.realtimeStatusMapper = realtimeStatusMapper;
     }
 
     public PageResponse<GuideOrderResponse> page(Long guideId, String tab, long page, long size) {
@@ -41,16 +51,56 @@
     }
 
     public GuideOrderResponse detail(Long guideId, Long orderId) {
-        TourOrder order = orderMapper.selectOne(new LambdaQueryWrapper<TourOrder>()
-                .eq(TourOrder::getId, orderId)
-                .eq(TourOrder::getGuideId, guideId)
-                .last("LIMIT 1"));
+        TourOrder order = findOwnedOrder(guideId, orderId);
         if (order == null) {
             throw new BusinessException(404, "订单不存在");
         }
         return GuideOrderResponse.from(order);
     }
 
+    @Transactional
+    public GuideOrderResponse start(Long guideId, Long orderId) {
+        TourOrder order = findOwnedOrder(guideId, orderId);
+        if (order == null) {
+            throw new BusinessException(404, "订单不存在");
+        }
+        if (OrderStatus.IN_SERVICE.matches(order.getStatus())) {
+            return GuideOrderResponse.from(order);
+        }
+        if (!OrderStatus.PENDING_MEETING.matches(order.getStatus()) || order.getReceivingSessionId() == null) {
+            throw new BusinessException(400, "当前订单不能开始讲解");
+        }
+
+        GuideReceivingSession session = sessionMapper.selectForUpdate(order.getReceivingSessionId(), guideId);
+        if (session == null) {
+            throw new BusinessException(400, "接单会话不存在或已结束");
+        }
+        if ("IN_SERVICE".equals(session.getStatus())) {
+            order.setStatus(OrderStatus.IN_SERVICE.getCode());
+            return GuideOrderResponse.from(order);
+        }
+        if (!"RECEIVING".equals(session.getStatus()) && !"ASSIGNED".equals(session.getStatus())) {
+            throw new BusinessException(400, "接单会话已结束,不能开始讲解");
+        }
+
+        LocalDateTime startedAt = LocalDateTime.now();
+        if (sessionMapper.startSession(session.getId(), guideId) != 1
+                || orderMapper.startSessionOrders(session.getId(), startedAt) == 0) {
+            throw new BusinessException(409, "讲解状态已变化,请刷新后重试");
+        }
+        realtimeStatusMapper.disableReceiving(guideId);
+        order.setStatus(OrderStatus.IN_SERVICE.getCode());
+        order.setServiceStartedAt(startedAt);
+        return GuideOrderResponse.from(order);
+    }
+
+    private TourOrder findOwnedOrder(Long guideId, Long orderId) {
+        return orderMapper.selectOne(new LambdaQueryWrapper<TourOrder>()
+                .eq(TourOrder::getId, orderId)
+                .eq(TourOrder::getGuideId, guideId)
+                .last("LIMIT 1"));
+    }
+
     private String normalizeTab(String tab) {
         return StringUtils.hasText(tab) ? tab.trim() : TAB_ALL;
     }
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 8114c54..320daf1 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
@@ -10,6 +10,7 @@
 import com.tourguide.modules.operation.mapper.ScenicPricingMapper;
 import com.tourguide.modules.operation.service.PlatformSettingsService;
 import com.tourguide.modules.order.domain.OrderStatus;
+import com.tourguide.modules.order.domain.PaymentMethod;
 import com.tourguide.modules.order.domain.RefundStatus;
 import com.tourguide.modules.order.dto.MiniOrderCreateRequest;
 import com.tourguide.modules.order.dto.TourOrderResponse;
@@ -20,6 +21,7 @@
 import com.tourguide.modules.user.entity.GuideUser;
 import com.tourguide.modules.user.mapper.GuideUserMapper;
 import com.tourguide.modules.user.service.MiniUserService;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -40,6 +42,8 @@
     private final PlatformSettingsService platformSettingsService;
     private final GuideNotificationService notificationService;
     private final OrderRefundService orderRefundService;
+    private final GuideOrderMatchingService matchingService;
+    private final boolean skipPayment;
 
     public MiniTourOrderCommandService(TourOrderMapper orderMapper,
                                        MiniUserService miniUserService,
@@ -49,7 +53,9 @@
                                        GuideUserMapper guideUserMapper,
                                        PlatformSettingsService platformSettingsService,
                                        GuideNotificationService notificationService,
-                                       OrderRefundService orderRefundService) {
+                                       OrderRefundService orderRefundService,
+                                       GuideOrderMatchingService matchingService,
+                                       @Value("${tourguide.order.skip-payment:false}") boolean skipPayment) {
         this.orderMapper = orderMapper;
         this.miniUserService = miniUserService;
         this.scenicSpotMapper = scenicSpotMapper;
@@ -59,6 +65,8 @@
         this.platformSettingsService = platformSettingsService;
         this.notificationService = notificationService;
         this.orderRefundService = orderRefundService;
+        this.matchingService = matchingService;
+        this.skipPayment = skipPayment;
     }
 
     @Transactional
@@ -103,9 +111,20 @@
         order.setMeetingPointCount(1);
         Integer settlementDays = platformSettingsService.getSettings().getSettlementDays();
         order.setSettlementDays(settlementDays == null ? 1 : Math.max(0, settlementDays));
-        order.setStatus(OrderStatus.PENDING_PAYMENT.getCode());
+        if (skipPayment) {
+            order.setPaymentMethod(PaymentMethod.TEMPORARY_BYPASS.getCode());
+            order.setPaymentOrderNo("TEMP" + order.getOrderNo());
+            order.setPaymentAmount(subtotalAmount);
+            order.setPaymentTime(now);
+            order.setStatus(OrderStatus.PENDING_ACCEPT.getCode());
+        } else {
+            order.setStatus(OrderStatus.PENDING_PAYMENT.getCode());
+        }
         order.setRefundStatus(RefundStatus.NONE.getCode());
         orderMapper.insert(order);
+        if (skipPayment) {
+            matchingService.match(order.getId());
+        }
         return TourOrderResponse.from(order);
     }
 
diff --git a/backend/src/main/java/com/tourguide/modules/order/service/OrderRefundService.java b/backend/src/main/java/com/tourguide/modules/order/service/OrderRefundService.java
index b39088a..21d2b11 100644
--- a/backend/src/main/java/com/tourguide/modules/order/service/OrderRefundService.java
+++ b/backend/src/main/java/com/tourguide/modules/order/service/OrderRefundService.java
@@ -1,6 +1,7 @@
 package com.tourguide.modules.order.service;
 
 import com.tourguide.common.huifu.RequestSeqUtil;
+import com.tourguide.modules.order.domain.PaymentMethod;
 import com.tourguide.modules.order.domain.RefundStatus;
 import com.tourguide.modules.order.entity.TourOrder;
 import com.tourguide.modules.order.mapper.TourOrderMapper;
@@ -11,6 +12,7 @@
 import org.springframework.util.StringUtils;
 
 import java.math.BigDecimal;
+import java.time.LocalDateTime;
 
 @Service
 public class OrderRefundService {
@@ -40,6 +42,12 @@
         order.setRefundStatus(RefundStatus.REFUNDING.getCode());
         order.setRefundAmount(refundAmount);
         order.setRefundMethod(order.getPaymentMethod());
+        if (PaymentMethod.TEMPORARY_BYPASS.matches(order.getPaymentMethod())) {
+            order.setRefundStatus(RefundStatus.REFUNDED.getCode());
+            order.setRefundTime(LocalDateTime.now());
+            orderMapper.updateById(order);
+            return;
+        }
         orderMapper.updateById(order);
         try {
             paymentRefundGateway.refund(order, refundAmount);
diff --git a/backend/src/main/java/com/tourguide/modules/order/service/TourOrderPaymentService.java b/backend/src/main/java/com/tourguide/modules/order/service/TourOrderPaymentService.java
index 5ac0cfa..d21ebc8 100644
--- a/backend/src/main/java/com/tourguide/modules/order/service/TourOrderPaymentService.java
+++ b/backend/src/main/java/com/tourguide/modules/order/service/TourOrderPaymentService.java
@@ -3,6 +3,7 @@
 import com.tourguide.common.exception.BusinessException;
 import com.tourguide.common.huifu.RequestSeqUtil;
 import com.tourguide.modules.order.domain.OrderStatus;
+import com.tourguide.modules.order.domain.PaymentMethod;
 import com.tourguide.modules.order.entity.TourOrder;
 import com.tourguide.modules.order.entity.TourOrderPaymentAttempt;
 import com.tourguide.modules.order.mapper.TourOrderMapper;
@@ -56,7 +57,7 @@
         attempt.setStatus(ATTEMPT_CREATED);
         paymentAttemptMapper.insert(attempt);
 
-        order.setPaymentMethod("WECHAT");
+        order.setPaymentMethod(PaymentMethod.WECHAT.getCode());
         order.setPaymentOrderNo(reqSeqId);
         orderMapper.updateById(order);
 
@@ -91,7 +92,7 @@
         if (!OrderStatus.PENDING_PAYMENT.matches(order.getStatus())) {
             throw new BusinessException(409, "订单状态不允许确认支付");
         }
-        order.setPaymentMethod("WECHAT");
+        order.setPaymentMethod(PaymentMethod.WECHAT.getCode());
         order.setPaymentOrderNo(notice.getReqSeqId());
         order.setPaymentAmount(notice.getAmount());
         order.setPaymentTime(paidAt);
diff --git a/backend/src/main/java/com/tourguide/modules/user/mapper/GuideReceivingSessionMapper.java b/backend/src/main/java/com/tourguide/modules/user/mapper/GuideReceivingSessionMapper.java
index 1228b8c..dfb0389 100644
--- a/backend/src/main/java/com/tourguide/modules/user/mapper/GuideReceivingSessionMapper.java
+++ b/backend/src/main/java/com/tourguide/modules/user/mapper/GuideReceivingSessionMapper.java
@@ -9,6 +9,10 @@
 import java.util.List;
 
 public interface GuideReceivingSessionMapper extends BaseMapper<GuideReceivingSession> {
+    @Select("SELECT * FROM guide_receiving_session WHERE id = #{id} AND guide_id = #{guideId} " +
+            "AND deleted = 0 LIMIT 1 FOR UPDATE")
+    GuideReceivingSession selectForUpdate(@Param("id") Long id, @Param("guideId") Long guideId);
+
     @Select("SELECT s.* FROM guide_receiving_session s " +
             "INNER JOIN guide_realtime_status r ON r.guide_id = s.guide_id AND r.deleted = 0 " +
             "INNER JOIN guide_user g ON g.id = s.guide_id AND g.deleted = 0 AND g.status = 1 " +
@@ -40,6 +44,10 @@
             "WHERE id = #{id} AND deleted = 0 AND status = 'RECEIVING' AND service_start_at <= NOW()")
     int startDueSession(Long id);
 
+    @Update("UPDATE guide_receiving_session SET status = 'IN_SERVICE', updated_at = NOW() " +
+            "WHERE id = #{id} AND guide_id = #{guideId} AND deleted = 0 AND status IN ('RECEIVING', 'ASSIGNED')")
+    int startSession(@Param("id") Long id, @Param("guideId") Long guideId);
+
     @Update("UPDATE guide_receiving_session SET reminder_pushed_at = #{now}, updated_at = NOW() " +
             "WHERE id = #{id} AND deleted = 0 AND status = 'RECEIVING' AND reminder_pushed_at IS NULL")
     int claimServiceReminder(@Param("id") Long id, @Param("now") LocalDateTime now);
diff --git a/backend/src/main/resources/application-dev.yml b/backend/src/main/resources/application-dev.yml
index ccfd80a..33ed400 100644
--- a/backend/src/main/resources/application-dev.yml
+++ b/backend/src/main/resources/application-dev.yml
@@ -14,6 +14,9 @@
 tourguide:
   guide-login:
     fixed-verification-code: ${GUIDE_LOGIN_FIXED_CODE:}
+  order:
+    # 临时跳过微信支付;恢复真实支付时将默认值改为 false。
+    skip-payment: ${TOURGUIDE_ORDER_SKIP_PAYMENT:true}
 
 logging:
   level:
diff --git a/backend/src/test/java/com/tourguide/modules/order/service/GuideTourOrderServiceTest.java b/backend/src/test/java/com/tourguide/modules/order/service/GuideTourOrderServiceTest.java
new file mode 100644
index 0000000..ba2f178
--- /dev/null
+++ b/backend/src/test/java/com/tourguide/modules/order/service/GuideTourOrderServiceTest.java
@@ -0,0 +1,91 @@
+package com.tourguide.modules.order.service;
+
+import com.tourguide.common.exception.BusinessException;
+import com.tourguide.modules.order.domain.OrderStatus;
+import com.tourguide.modules.order.dto.GuideOrderResponse;
+import com.tourguide.modules.order.entity.TourOrder;
+import com.tourguide.modules.order.mapper.TourOrderMapper;
+import com.tourguide.modules.user.entity.GuideReceivingSession;
+import com.tourguide.modules.user.mapper.GuideRealtimeStatusMapper;
+import com.tourguide.modules.user.mapper.GuideReceivingSessionMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class GuideTourOrderServiceTest {
+    private TourOrderMapper orderMapper;
+    private GuideReceivingSessionMapper sessionMapper;
+    private GuideRealtimeStatusMapper realtimeStatusMapper;
+    private GuideTourOrderService service;
+
+    @BeforeEach
+    void setUp() {
+        orderMapper = mock(TourOrderMapper.class);
+        sessionMapper = mock(GuideReceivingSessionMapper.class);
+        realtimeStatusMapper = mock(GuideRealtimeStatusMapper.class);
+        service = new GuideTourOrderService(orderMapper, sessionMapper, realtimeStatusMapper);
+    }
+
+    @Test
+    void shouldStartAllOrdersInReceivingSession() {
+        TourOrder order = order(OrderStatus.PENDING_MEETING);
+        GuideReceivingSession session = session("ASSIGNED");
+        when(orderMapper.selectOne(any())).thenReturn(order);
+        when(sessionMapper.selectForUpdate(20L, 8L)).thenReturn(session);
+        when(sessionMapper.startSession(20L, 8L)).thenReturn(1);
+        when(orderMapper.startSessionOrders(eq(20L), any())).thenReturn(2);
+
+        GuideOrderResponse response = service.start(8L, 10L);
+
+        assertThat(response.getStatus()).isEqualTo("serving");
+        assertThat(response.getLectureStartTime()).isNotNull();
+        verify(realtimeStatusMapper).disableReceiving(8L);
+    }
+
+    @Test
+    void shouldTreatRepeatedStartAsSuccess() {
+        TourOrder order = order(OrderStatus.IN_SERVICE);
+        when(orderMapper.selectOne(any())).thenReturn(order);
+
+        GuideOrderResponse response = service.start(8L, 10L);
+
+        assertThat(response.getStatus()).isEqualTo("serving");
+        verify(sessionMapper, never()).selectForUpdate(any(), any());
+    }
+
+    @Test
+    void shouldRejectOrderOutsidePendingMeetingState() {
+        TourOrder order = order(OrderStatus.COMPLETED);
+        when(orderMapper.selectOne(any())).thenReturn(order);
+
+        assertThatThrownBy(() -> service.start(8L, 10L))
+                .isInstanceOf(BusinessException.class)
+                .hasMessage("当前订单不能开始讲解");
+    }
+
+    private TourOrder order(OrderStatus status) {
+        TourOrder order = new TourOrder();
+        order.setId(10L);
+        order.setGuideId(8L);
+        order.setReceivingSessionId(20L);
+        order.setStatus(status.getCode());
+        order.setPeopleCount(2);
+        return order;
+    }
+
+    private GuideReceivingSession session(String status) {
+        GuideReceivingSession session = new GuideReceivingSession();
+        session.setId(20L);
+        session.setGuideId(8L);
+        session.setStatus(status);
+        return session;
+    }
+}
diff --git a/backend/src/test/java/com/tourguide/modules/order/service/MiniTourOrderCommandServiceTest.java b/backend/src/test/java/com/tourguide/modules/order/service/MiniTourOrderCommandServiceTest.java
index 5cf75d4..de71e45 100644
--- a/backend/src/test/java/com/tourguide/modules/order/service/MiniTourOrderCommandServiceTest.java
+++ b/backend/src/test/java/com/tourguide/modules/order/service/MiniTourOrderCommandServiceTest.java
@@ -9,6 +9,7 @@
 import com.tourguide.modules.operation.dto.PlatformSettingsResponse;
 import com.tourguide.modules.operation.service.PlatformSettingsService;
 import com.tourguide.modules.order.domain.OrderStatus;
+import com.tourguide.modules.order.domain.PaymentMethod;
 import com.tourguide.modules.order.dto.MiniOrderCreateRequest;
 import com.tourguide.modules.order.dto.TourOrderResponse;
 import com.tourguide.modules.order.entity.TourOrder;
@@ -43,6 +44,7 @@
     private PlatformSettingsService platformSettingsService;
     private GuideNotificationService notificationService;
     private OrderRefundService orderRefundService;
+    private GuideOrderMatchingService matchingService;
     private MiniTourOrderCommandService commandService;
 
     @BeforeEach
@@ -56,13 +58,92 @@
         platformSettingsService = mock(PlatformSettingsService.class);
         notificationService = mock(GuideNotificationService.class);
         orderRefundService = mock(OrderRefundService.class);
+        matchingService = mock(GuideOrderMatchingService.class);
         commandService = new MiniTourOrderCommandService(
                 orderMapper, miniUserService, scenicSpotMapper, meetingPointMapper, pricingMapper,
-                guideUserMapper, platformSettingsService, notificationService, orderRefundService);
+                guideUserMapper, platformSettingsService, notificationService, orderRefundService,
+                matchingService, false);
     }
 
     @Test
     void createShouldKeepOrderPendingPaymentUntilPaymentSucceeds() {
+        MiniOrderCreateRequest request = prepareCreateOrder();
+
+        TourOrderResponse response = commandService.create(7L, request);
+
+        ArgumentCaptor<TourOrder> captor = ArgumentCaptor.forClass(TourOrder.class);
+        verify(orderMapper).insert(captor.capture());
+        TourOrder order = captor.getValue();
+        assertThat(order.getStatus()).isEqualTo(OrderStatus.PENDING_PAYMENT.getCode());
+        assertThat(order.getPaymentMethod()).isNull();
+        assertThat(order.getPaymentOrderNo()).isNull();
+        assertThat(order.getPaymentAmount()).isNull();
+        assertThat(order.getPaymentTime()).isNull();
+        assertThat(order.getSettlementDays()).isEqualTo(7);
+        assertThat(response.getStatus()).isEqualTo(OrderStatus.PENDING_PAYMENT.getCode());
+        verify(matchingService, never()).match(any());
+    }
+
+    @Test
+    void createShouldRejectVisitorWithUnfinishedOrder() {
+        MiniUser visitor = new MiniUser();
+        visitor.setId(7L);
+        when(miniUserService.findEnabledById(7L)).thenReturn(visitor);
+        when(orderMapper.selectCount(any())).thenReturn(1L);
+
+        assertThatThrownBy(() -> commandService.create(7L, new MiniOrderCreateRequest()))
+                .isInstanceOf(com.tourguide.common.exception.BusinessException.class)
+                .hasMessage("您有一个未完成的订单,请先完成或取消后再下单");
+
+        verify(orderMapper, never()).insert(any(TourOrder.class));
+    }
+
+    @Test
+    void createShouldMarkOrderPaidAndStartMatchingWhenPaymentIsSkipped() {
+        MiniOrderCreateRequest request = prepareCreateOrder();
+        commandService = new MiniTourOrderCommandService(
+                orderMapper, miniUserService, scenicSpotMapper, meetingPointMapper, pricingMapper,
+                guideUserMapper, platformSettingsService, notificationService, orderRefundService,
+                matchingService, true);
+
+        TourOrderResponse response = commandService.create(7L, request);
+
+        ArgumentCaptor<TourOrder> captor = ArgumentCaptor.forClass(TourOrder.class);
+        verify(orderMapper).insert(captor.capture());
+        TourOrder order = captor.getValue();
+        assertThat(order.getStatus()).isEqualTo(OrderStatus.PENDING_ACCEPT.getCode());
+        assertThat(order.getPaymentMethod()).isEqualTo(PaymentMethod.TEMPORARY_BYPASS.getCode());
+        assertThat(order.getPaymentOrderNo()).startsWith("TEMPMJ");
+        assertThat(order.getPaymentAmount()).isEqualByComparingTo("144.00");
+        assertThat(order.getPaymentTime()).isNotNull();
+        assertThat(response.getStatus()).isEqualTo(OrderStatus.PENDING_ACCEPT.getCode());
+        verify(matchingService).match(99L);
+    }
+
+    @Test
+    void cancelPendingMeetingOrderShouldNotifyAssignedGuide() {
+        MiniUser visitor = new MiniUser();
+        visitor.setId(7L);
+        when(miniUserService.findEnabledById(7L)).thenReturn(visitor);
+
+        TourOrder order = new TourOrder();
+        order.setId(99L);
+        order.setOrderNo("MJ202607300001");
+        order.setVisitorId(7L);
+        order.setGuideId(8L);
+        order.setStatus(OrderStatus.PENDING_MEETING.getCode());
+        order.setPaymentTime(LocalDateTime.now());
+        order.setPaymentAmount(new BigDecimal("36.00"));
+        when(orderMapper.selectForUpdate(99L)).thenReturn(order);
+
+        commandService.cancel(7L, 99L);
+
+        assertThat(order.getStatus()).isEqualTo(OrderStatus.CANCELLED.getCode());
+        verify(orderRefundService).requestRefund(order);
+        verify(notificationService).notifyVisitorCancelledOrder(order);
+    }
+
+    private MiniOrderCreateRequest prepareCreateOrder() {
         MiniUser visitor = new MiniUser();
         visitor.setId(7L);
         visitor.setNickname("游客");
@@ -104,55 +185,6 @@
         request.setMeetingPointId(12L);
         request.setServicePackageId(13L);
         request.setPeopleCount(4);
-
-        TourOrderResponse response = commandService.create(7L, request);
-
-        ArgumentCaptor<TourOrder> captor = ArgumentCaptor.forClass(TourOrder.class);
-        verify(orderMapper).insert(captor.capture());
-        TourOrder order = captor.getValue();
-        assertThat(order.getStatus()).isEqualTo(OrderStatus.PENDING_PAYMENT.getCode());
-        assertThat(order.getPaymentMethod()).isNull();
-        assertThat(order.getPaymentOrderNo()).isNull();
-        assertThat(order.getPaymentAmount()).isNull();
-        assertThat(order.getPaymentTime()).isNull();
-        assertThat(order.getSettlementDays()).isEqualTo(7);
-        assertThat(response.getStatus()).isEqualTo(OrderStatus.PENDING_PAYMENT.getCode());
-    }
-
-    @Test
-    void createShouldRejectVisitorWithUnfinishedOrder() {
-        MiniUser visitor = new MiniUser();
-        visitor.setId(7L);
-        when(miniUserService.findEnabledById(7L)).thenReturn(visitor);
-        when(orderMapper.selectCount(any())).thenReturn(1L);
-
-        assertThatThrownBy(() -> commandService.create(7L, new MiniOrderCreateRequest()))
-                .isInstanceOf(com.tourguide.common.exception.BusinessException.class)
-                .hasMessage("您有一个未完成的订单,请先完成或取消后再下单");
-
-        verify(orderMapper, never()).insert(any(TourOrder.class));
-    }
-
-    @Test
-    void cancelPendingMeetingOrderShouldNotifyAssignedGuide() {
-        MiniUser visitor = new MiniUser();
-        visitor.setId(7L);
-        when(miniUserService.findEnabledById(7L)).thenReturn(visitor);
-
-        TourOrder order = new TourOrder();
-        order.setId(99L);
-        order.setOrderNo("MJ202607300001");
-        order.setVisitorId(7L);
-        order.setGuideId(8L);
-        order.setStatus(OrderStatus.PENDING_MEETING.getCode());
-        order.setPaymentTime(LocalDateTime.now());
-        order.setPaymentAmount(new BigDecimal("36.00"));
-        when(orderMapper.selectForUpdate(99L)).thenReturn(order);
-
-        commandService.cancel(7L, 99L);
-
-        assertThat(order.getStatus()).isEqualTo(OrderStatus.CANCELLED.getCode());
-        verify(orderRefundService).requestRefund(order);
-        verify(notificationService).notifyVisitorCancelledOrder(order);
+        return request;
     }
 }
diff --git a/backend/src/test/java/com/tourguide/modules/order/service/OrderRefundServiceTest.java b/backend/src/test/java/com/tourguide/modules/order/service/OrderRefundServiceTest.java
index 31fae2c..c5d3ddf 100644
--- a/backend/src/test/java/com/tourguide/modules/order/service/OrderRefundServiceTest.java
+++ b/backend/src/test/java/com/tourguide/modules/order/service/OrderRefundServiceTest.java
@@ -1,6 +1,7 @@
 package com.tourguide.modules.order.service;
 
 import com.tourguide.modules.order.domain.RefundStatus;
+import com.tourguide.modules.order.domain.PaymentMethod;
 import com.tourguide.modules.order.entity.TourOrder;
 import com.tourguide.modules.order.mapper.TourOrderMapper;
 import com.tourguide.modules.order.payment.PaymentRefundGateway;
@@ -71,6 +72,22 @@
                 org.mockito.ArgumentMatchers.any());
     }
 
+    @Test
+    void temporaryBypassRefundShouldCompleteWithoutCallingGateway() {
+        TourOrder order = paidOrder();
+        order.setPaymentMethod(PaymentMethod.TEMPORARY_BYPASS.getCode());
+        order.setPaymentOrderNo("TEMPMJ202608030001");
+
+        refundService.requestRefund(order);
+
+        assertThat(order.getRefundStatus()).isEqualTo(RefundStatus.REFUNDED.getCode());
+        assertThat(order.getRefundTime()).isNotNull();
+        assertThat(order.getRefundMethod()).isEqualTo(PaymentMethod.TEMPORARY_BYPASS.getCode());
+        verify(refundGateway, never()).refund(org.mockito.ArgumentMatchers.any(),
+                org.mockito.ArgumentMatchers.any());
+        verify(orderMapper).updateById(order);
+    }
+
     private TourOrder paidOrder() {
         TourOrder order = new TourOrder();
         order.setId(99L);
diff --git a/data/guide-applications/1e3862aa6fda49d580017e360126062a.jpg b/data/guide-applications/1e3862aa6fda49d580017e360126062a.jpg
new file mode 100644
index 0000000..821fb29
--- /dev/null
+++ b/data/guide-applications/1e3862aa6fda49d580017e360126062a.jpg
Binary files differ
diff --git a/data/guide-applications/20563e26d1bc43c4a1aac5abc87ae480.jpg b/data/guide-applications/20563e26d1bc43c4a1aac5abc87ae480.jpg
new file mode 100644
index 0000000..6b50d31
--- /dev/null
+++ b/data/guide-applications/20563e26d1bc43c4a1aac5abc87ae480.jpg
Binary files differ
diff --git a/data/guide-applications/7eff1dce4fc54626bde8a8b05021b5fd.jpg b/data/guide-applications/7eff1dce4fc54626bde8a8b05021b5fd.jpg
new file mode 100644
index 0000000..71c4529
--- /dev/null
+++ b/data/guide-applications/7eff1dce4fc54626bde8a8b05021b5fd.jpg
Binary files differ
diff --git a/data/guide-applications/b57f19850ed74770bf45906b9d3d2b19.jpg b/data/guide-applications/b57f19850ed74770bf45906b9d3d2b19.jpg
new file mode 100644
index 0000000..6b50d31
--- /dev/null
+++ b/data/guide-applications/b57f19850ed74770bf45906b9d3d2b19.jpg
Binary files differ
diff --git a/mingdaojingjiang_guide_app/pages/workbench/index.vue b/mingdaojingjiang_guide_app/pages/workbench/index.vue
index 07930d6..e445198 100644
--- a/mingdaojingjiang_guide_app/pages/workbench/index.vue
+++ b/mingdaojingjiang_guide_app/pages/workbench/index.vue
@@ -65,7 +65,13 @@
             </view>
           </view>
           <button v-if="isServing" class="lecture-button" @tap="finishLecture">结束讲解</button>
-          <button v-else class="lecture-button" @tap="startLecture">开始讲解</button>
+          <button
+            v-else
+            class="lecture-button"
+            :loading="startingLecture"
+            :disabled="startingLecture"
+            @tap="startLecture"
+          >开始讲解</button>
         </template>
         <template v-else>
           <view class="empty-order">暂无订单请耐心等待</view>
@@ -136,7 +142,7 @@
   updateGuideOnlineStatus
 } from '../../services/guide'
 import { openMeetingPointPage } from '../../services/map'
-import { getGuideOrders } from '../../services/order'
+import { getGuideOrders, startGuideLecture } from '../../services/order'
 import { playNotificationAlert } from '../../services/message'
 import { getWorkbenchData } from '../../services/workbench'
 import { formatDuration, maskPhone } from '../../utils/format'
@@ -196,6 +202,7 @@
       countdownTimer: null,
       refreshTimer: null,
       refreshing: false,
+      startingLecture: false,
       dialog: {
         visible: false,
         title: '提示',
@@ -480,9 +487,27 @@
       })
     },
     startLecture() {
-      updateGuideOnlineStatus('serving', this.guide.receivingConfig || {}).then(() => {
-        this.loadData()
+      const orderId = this.activeReceivingOrder.id
+      if (this.startingLecture) {
+        return
+      }
+      if (!orderId) {
+        uni.showToast({ title: '当前没有可开始的订单', icon: 'none' })
+        return
+      }
+      this.startingLecture = true
+      startGuideLecture(orderId).then((startedOrder) => {
+        this.updateGuide({ ...this.guide, onlineStatus: 'serving' })
+        this.updateOrders(this.orders.map((order) => (
+          order.id === startedOrder.id ? { ...order, ...startedOrder } : order
+        )))
+        this.setupCountdown()
         uni.showToast({ title: '已开始讲解', icon: 'none' })
+        return this.loadData({ silent: true })
+      }).catch((error) => {
+        uni.showToast({ title: error.message || '开始讲解失败', icon: 'none' })
+      }).finally(() => {
+        this.startingLecture = false
       })
     },
     finishLecture() {
diff --git a/mingdaojingjiang_guide_app/services/order.js b/mingdaojingjiang_guide_app/services/order.js
index c292d77..a9777fc 100644
--- a/mingdaojingjiang_guide_app/services/order.js
+++ b/mingdaojingjiang_guide_app/services/order.js
@@ -55,6 +55,13 @@
   return request({ url: `/guide/orders/${orderId}` }).then(normalizeGuideOrder)
 }
 
+export function startGuideLecture(orderId) {
+  return request({
+    url: `/guide/orders/${orderId}/start`,
+    method: 'POST'
+  }).then(normalizeGuideOrder)
+}
+
 function normalizeOrderTab(filter) {
   if (filter === 'active' || filter === 'pending') {
     return 'accepted'
diff --git a/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/home/index.vue b/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/home/index.vue
index 89e0a76..96dd44b 100644
--- a/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/home/index.vue
+++ b/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/home/index.vue
@@ -323,14 +323,14 @@
   const textMap = {
     service: '立即呼叫讲解',
     meeting: '下一步',
-    payment: '确认支付'
+    payment: '确认下单'
   }
   return textMap[guideFlowStep.value] || textMap.service
 })
 
-const guideActionLoadingText = computed(() => (guideFlowStep.value === 'payment' ? '支付中...' : '处理中...'))
+const guideActionLoadingText = computed(() => (guideFlowStep.value === 'payment' ? '下单中...' : '处理中...'))
 
-const guideActionSuccessText = computed(() => (guideFlowStep.value === 'payment' ? '支付成功' : '已完成'))
+const guideActionSuccessText = computed(() => (guideFlowStep.value === 'payment' ? '下单成功' : '已完成'))
 
 const guideActionDisabled = computed(() => {
   if (guideFlowStep.value === 'meeting') return !selectedMeetingPointId.value
@@ -902,19 +902,8 @@
   const order = activeHomeOrder.value
   if (!order || !order.id) return
 
-  const routeMap = {
-    pendingPay: '/pages/order-pending-pay/index',
-    waiting: '/pages/order-waiting/index',
-    pendingGuide: '/pages/order-pending-guide/index',
-    serving: '/pages/order-serving/index',
-    completed: '/pages/order-completed/index',
-    pendingReview: '/pages/order-pending-review/index',
-    canceled: '/pages/order-canceled/index'
-  }
-  const target = routeMap[order.status] || '/pages/orders/index'
-
   uni.navigateTo({
-    url: `${target}?orderId=${order.id}`
+    url: `${getActiveOrderRoute(order)}?orderId=${order.id}`
   })
 }
 
@@ -987,13 +976,13 @@
 
   try {
     paymentSubmitting.value = true
-    const orderId = await createPendingGuideOrder()
+    const order = await createPendingGuideOrder()
     paymentSubmitting.value = false
     paymentSuccess.value = true
 
     setTimeout(() => {
       uni.navigateTo({
-        url: `/pages/order-pending-pay/index?orderId=${orderId}`
+        url: `${getActiveOrderRoute(activeHomeOrder.value)}?orderId=${order.id}`
       })
     }, 220)
   } catch (error) {
@@ -1035,7 +1024,20 @@
     peopleCount: selectedServiceQuantity.value
   })
   activeHomeOrder.value = normalizeActiveHomeOrder(order)
-  return order.id
+  return order
+}
+
+function getActiveOrderRoute(order) {
+  const routeMap = {
+    pendingPay: '/pages/order-pending-pay/index',
+    waiting: '/pages/order-waiting/index',
+    pendingGuide: '/pages/order-pending-guide/index',
+    serving: '/pages/order-serving/index',
+    completed: '/pages/order-completed/index',
+    pendingReview: '/pages/order-pending-review/index',
+    canceled: '/pages/order-canceled/index'
+  }
+  return routeMap[order && order.status] || '/pages/orders/index'
 }
 
 function normalizeActiveHomeOrder(order) {
diff --git a/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/order-pending-guide/index.vue b/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/order-pending-guide/index.vue
index f767a96..63ec5da 100644
--- a/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/order-pending-guide/index.vue
+++ b/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/order-pending-guide/index.vue
@@ -120,7 +120,7 @@
 
 <script setup>
 import { computed, ref } from 'vue'
-import { onLoad } from '@dcloudio/uni-app'
+import { onHide, onLoad, onShow, onUnload } from '@dcloudio/uni-app'
 import AppNav from '@/components/app-nav/app-nav.vue'
 import CancelOrderSheet from '@/components/cancel-order-sheet/cancel-order-sheet.vue'
 import { cancelTourOrder, getTourOrder } from '@/services/order'
@@ -129,8 +129,15 @@
 
 useLoginGuard('/pages/order-pending-guide/index')
 
+const ORDER_POLL_INTERVAL = 3000
+
 const orderId = ref('')
 const orderDetail = ref({})
+let orderPollTimer = null
+let orderRefreshing = false
+let pageActive = false
+let redirecting = false
+let loadErrorShown = false
 
 const meetingPoint = computed(() => ({
   longitude: Number(orderDetail.value.meetingPointLongitude || 0),
@@ -186,21 +193,86 @@
 
 onLoad((options = {}) => {
   orderId.value = options.orderId || options.id || ''
-  loadOrderDetail()
 })
 
-async function loadOrderDetail() {
+onShow(() => {
+  pageActive = true
+  redirecting = false
+  startOrderPolling()
+})
+
+onHide(() => {
+  pageActive = false
+  stopOrderPolling()
+})
+
+onUnload(() => {
+  pageActive = false
+  stopOrderPolling()
+})
+
+function startOrderPolling() {
+  stopOrderPolling()
   if (!orderId.value) {
     uni.showToast({ title: '订单参数缺失', icon: 'none' })
     return
   }
-  try {
-    orderDetail.value = await getTourOrder(orderId.value)
-  } catch (error) {
-    uni.showToast({ title: error.message || '订单加载失败', icon: 'none' })
+
+  refreshOrderStatus()
+  orderPollTimer = setInterval(refreshOrderStatus, ORDER_POLL_INTERVAL)
+}
+
+function stopOrderPolling() {
+  if (orderPollTimer) {
+    clearInterval(orderPollTimer)
+    orderPollTimer = null
   }
 }
 
+async function refreshOrderStatus() {
+  if (!pageActive || !orderId.value || orderRefreshing || redirecting) return
+
+  orderRefreshing = true
+  try {
+    const detail = await getTourOrder(orderId.value)
+    if (!pageActive) return
+    orderDetail.value = detail
+    loadErrorShown = false
+    redirectForOrderStatus(detail && detail.status)
+  } catch (error) {
+    if (!loadErrorShown) {
+      loadErrorShown = true
+      uni.showToast({ title: error.message || '订单状态更新失败', icon: 'none' })
+    }
+    console.warn('待讲解订单状态查询失败', error)
+  } finally {
+    orderRefreshing = false
+  }
+}
+
+function redirectForOrderStatus(status) {
+  const routeMap = {
+    PENDING_PAYMENT: '/pages/order-pending-pay/index',
+    PENDING_ACCEPT: '/pages/order-waiting/index',
+    IN_SERVICE: '/pages/order-serving/index',
+    COMPLETED: '/pages/order-pending-review/index',
+    CANCELLED: '/pages/order-canceled/index',
+    REFUNDED: '/pages/order-canceled/index'
+  }
+  const target = routeMap[status]
+  if (!target || redirecting) return
+
+  redirecting = true
+  stopOrderPolling()
+  uni.redirectTo({
+    url: `${target}?orderId=${orderId.value}`,
+    fail() {
+      redirecting = false
+      if (pageActive) startOrderPolling()
+    }
+  })
+}
+
 function formatAmount(value) {
   const amount = Number(value || 0)
   return Number.isFinite(amount) ? amount.toFixed(2) : '0.00'
diff --git a/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/order-serving/index.vue b/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/order-serving/index.vue
index 52c83b4..ca7a30c 100644
--- a/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/order-serving/index.vue
+++ b/mingdaojingjiang_uniapp/mingdaojingjiang_uniapp/pages/order-serving/index.vue
@@ -6,20 +6,20 @@
       <view class="status-panel">
         <text class="status-title">讲解中</text>
         <text class="countdown-text">{{ countdownText }}</text>
-        <view class="team-badge" :style="familyBadgeStyle">
-          <text class="team-badge__text">家庭号 {{ familyGroup.number }}</text>
+        <view v-if="order.familyNo" class="team-badge" :style="familyBadgeStyle">
+          <text class="team-badge__text">家庭号 {{ order.familyNo }}</text>
         </view>
       </view>
 
       <view class="detail-card">
         <view class="guide-row">
           <view class="guide-avatar">
-            <image class="guide-avatar__icon" src="/static/ui-icons/user-blue.svg" mode="aspectFit" />
+            <image class="guide-avatar__icon" :src="order.guideAvatar || '/static/ui-icons/user-blue.svg'" mode="aspectFill" />
           </view>
           <view class="guide-main">
-            <text class="guide-name">导游 · 赵四</text>
+            <text class="guide-name">导游 · {{ order.guideName }}</text>
             <view class="guide-stars">
-              <text v-for="item in 5" :key="item" class="guide-star">★</text>
+              <text v-for="item in 5" :key="item" class="guide-star" :class="{ 'guide-star--inactive': item > guideStarCount }">★</text>
             </view>
           </view>
           <view class="phone-action" @tap="handleCallGuide">
@@ -42,7 +42,7 @@
 
           <view class="price-row">
             <view class="unit-block">
-              <text class="unit-price">¥{{ order.unitPrice }}/人</text>
+              <text class="unit-price">¥{{ order.unitPrice }}{{ order.unit }}</text>
               <text class="quantity">x{{ order.quantity }}</text>
             </view>
             <text class="total-price">¥{{ order.total }}</text>
@@ -68,34 +68,45 @@
 </template>
 
 <script setup>
-import { computed, onBeforeUnmount, ref } from 'vue'
+import { computed, ref } from 'vue'
+import { onHide, onLoad, onShow, onUnload } from '@dcloudio/uni-app'
 import AppNav from '@/components/app-nav/app-nav.vue'
+import { getTourOrder } from '@/services/order'
+import { resolveProfileAssetUrl } from '@/services/profile'
 import { useLoginGuard } from '@/utils/auth-guard'
 
 useLoginGuard('/pages/order-serving/index')
 
-const order = {
-  id: 'MJ20260612003',
-  scenic: '青城山景区',
-  service: '拼团·快讲',
-  duration: '30分钟',
-  meetingPoint: '青城山正门汇合',
-  unitPrice: 36,
-  quantity: 4,
-  total: '366.00'
-}
+const ORDER_POLL_INTERVAL = 3000
 
-const familyGroup = {
-  number: 1,
-  color: '#278BFF'
-}
+const orderId = ref('')
+const orderDetail = ref({})
+const remainingSeconds = ref(0)
+let countdownTimer = null
+let orderPollTimer = null
+let orderRefreshing = false
+let pageActive = false
+let redirecting = false
+let loadErrorShown = false
 
-const remainingSeconds = ref(30 * 60 * 60)
-const timer = setInterval(() => {
-  if (remainingSeconds.value > 0) {
-    remainingSeconds.value -= 1
-  }
-}, 1000)
+const order = computed(() => ({
+  id: orderDetail.value.id || orderId.value,
+  scenic: orderDetail.value.scenicName || '-',
+  service: formatServiceTitle(orderDetail.value.packageName, orderDetail.value.orderType),
+  duration: orderDetail.value.durationMinutes ? `${orderDetail.value.durationMinutes}分钟` : '-',
+  meetingPoint: orderDetail.value.meetingPointName || '-',
+  unitPrice: formatAmount(orderDetail.value.unitPrice),
+  unit: String(orderDetail.value.orderType || '').startsWith('GROUP_') ? '/人' : '/团',
+  quantity: Number(orderDetail.value.peopleCount || 1),
+  total: formatAmount(orderDetail.value.paymentAmount ?? orderDetail.value.amount),
+  familyNo: orderDetail.value.familyNo || '',
+  guideName: orderDetail.value.guideName || '未分配',
+  guideMobile: orderDetail.value.guideMobile || '',
+  guideAvatar: resolveProfileAssetUrl(orderDetail.value.guideAvatar),
+  guideRating: Number(orderDetail.value.guideRating || 0)
+}))
+
+const guideStarCount = computed(() => Math.max(0, Math.min(5, Math.round(order.value.guideRating))))
 
 const countdownText = computed(() => {
   const hours = Math.floor(remainingSeconds.value / 3600)
@@ -105,36 +116,159 @@
 })
 
 const familyBadgeStyle = computed(() => ({
-  backgroundColor: familyGroup.color
+  backgroundColor: '#278BFF'
 }))
 
-const detailRows = [
-  { label: '下单时间', value: '2026-06-04 11:31:02' },
-  { label: '接单时间', value: '2026-06-04 11:31:02' },
-  { label: '支付金额', value: '¥366.00' },
-  { label: '支付流水号', value: '46145113464411' },
-  { label: '讲解开始时间', value: '2026-06-04 11:31:02' },
-  { label: '讲解结束时间', value: '-' }
-]
+const detailRows = computed(() => [
+  { label: '下单时间', value: formatDateTime(orderDetail.value.placedAt) },
+  { label: '接单时间', value: formatDateTime(orderDetail.value.acceptedAt) },
+  { label: '支付金额', value: `¥${formatAmount(orderDetail.value.paymentAmount ?? orderDetail.value.amount)}` },
+  { label: '支付流水号', value: orderDetail.value.paymentOrderNo || '-' },
+  { label: '讲解开始时间', value: formatDateTime(orderDetail.value.serviceStartedAt) },
+  { label: '讲解结束时间', value: formatDateTime(orderDetail.value.completedAt) }
+])
 
-onBeforeUnmount(() => {
-  clearInterval(timer)
+onLoad((options = {}) => {
+  orderId.value = options.orderId || options.id || ''
 })
+
+onShow(() => {
+  pageActive = true
+  redirecting = false
+  startOrderPolling()
+  startCountdown()
+})
+
+onHide(() => {
+  pageActive = false
+  clearTimers()
+})
+
+onUnload(() => {
+  pageActive = false
+  clearTimers()
+})
+
+function startOrderPolling() {
+  if (!orderId.value) {
+    uni.showToast({ title: '订单参数缺失', icon: 'none' })
+    return
+  }
+
+  clearInterval(orderPollTimer)
+  refreshOrderStatus()
+  orderPollTimer = setInterval(refreshOrderStatus, ORDER_POLL_INTERVAL)
+}
+
+async function refreshOrderStatus() {
+  if (!pageActive || !orderId.value || orderRefreshing || redirecting) return
+
+  orderRefreshing = true
+  try {
+    const detail = await getTourOrder(orderId.value)
+    if (!pageActive) return
+    orderDetail.value = detail
+    loadErrorShown = false
+    syncCountdown(detail)
+    redirectForOrderStatus(detail && detail.status)
+  } catch (error) {
+    if (!loadErrorShown) {
+      loadErrorShown = true
+      uni.showToast({ title: error.message || '订单状态更新失败', icon: 'none' })
+    }
+    console.warn('讲解中订单状态查询失败', error)
+  } finally {
+    orderRefreshing = false
+  }
+}
+
+function syncCountdown(detail) {
+  const durationSeconds = Math.max(0, Number(detail && detail.durationMinutes) || 0) * 60
+  const startedAt = parseDateTime(detail && detail.serviceStartedAt)
+  const elapsedSeconds = startedAt ? Math.max(0, Math.floor((Date.now() - startedAt) / 1000)) : 0
+  remainingSeconds.value = Math.max(0, durationSeconds - elapsedSeconds)
+}
+
+function startCountdown() {
+  clearInterval(countdownTimer)
+  countdownTimer = setInterval(() => {
+    if (remainingSeconds.value > 0) remainingSeconds.value -= 1
+  }, 1000)
+}
+
+function redirectForOrderStatus(status) {
+  const routeMap = {
+    PENDING_PAYMENT: '/pages/order-pending-pay/index',
+    PENDING_ACCEPT: '/pages/order-waiting/index',
+    PENDING_MEETING: '/pages/order-pending-guide/index',
+    COMPLETED: '/pages/order-pending-review/index',
+    CANCELLED: '/pages/order-canceled/index',
+    REFUNDED: '/pages/order-canceled/index'
+  }
+  const target = routeMap[status]
+  if (!target || redirecting) return
+
+  redirecting = true
+  clearTimers()
+  uni.redirectTo({
+    url: `${target}?orderId=${orderId.value}`,
+    fail() {
+      redirecting = false
+      if (pageActive) {
+        startOrderPolling()
+        startCountdown()
+      }
+    }
+  })
+}
+
+function clearTimers() {
+  if (countdownTimer) {
+    clearInterval(countdownTimer)
+    countdownTimer = null
+  }
+  if (orderPollTimer) {
+    clearInterval(orderPollTimer)
+    orderPollTimer = null
+  }
+}
+
+function parseDateTime(value) {
+  if (!value) return 0
+  const timestamp = new Date(String(value).replace(' ', 'T')).getTime()
+  return Number.isFinite(timestamp) ? timestamp : 0
+}
 
 function formatTime(value) {
   return String(value).padStart(2, '0')
 }
 
+function formatAmount(value) {
+  const amount = Number(value || 0)
+  return Number.isFinite(amount) ? amount.toFixed(2) : '0.00'
+}
+
+function formatDateTime(value) {
+  return value ? String(value).replace('T', ' ').slice(0, 19) : '-'
+}
+
+function formatServiceTitle(packageName, orderType) {
+  if (packageName) return String(packageName).replace(/\s*·\s*/g, '·')
+  const serviceTitleMap = {
+    GROUP_QUICK: '拼团·快讲',
+    GROUP_PREMIUM: '拼团·精讲',
+    PRIVATE_QUICK: '包团·快讲',
+    PRIVATE_PREMIUM: '包团·精讲'
+  }
+  return serviceTitleMap[orderType] || '讲解服务'
+}
+
 function handleCallGuide() {
-  uni.showActionSheet({
-    itemList: ['拨打导游电话'],
-    success() {
-      uni.showToast({
-        title: '电话联系开发中',
-        icon: 'none'
-      })
-    }
-  })
+  if (!order.value.guideMobile) {
+    uni.showToast({ title: '暂无导游联系电话', icon: 'none' })
+    return
+  }
+  uni.makePhoneCall({ phoneNumber: order.value.guideMobile })
 }
 </script>
 
@@ -256,6 +390,10 @@
   line-height: 1;
 }
 
+.guide-star--inactive {
+  color: #d8dde6;
+}
+
 .phone-action {
   display: flex;
   flex-direction: column;

--
Gitblit v1.7.1