rk
2026-06-11 8caa1157044d2229e56a288cc5665fadf526dd45
app/pages/order-detail/order-detail.vue
@@ -1,14 +1,17 @@
<template>
   <view class="order-detail-page">
   <view class="order-detail-page" v-if="orderDetail">
      <view v-if="!showMapStatus" class="order-detail-page__simple-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
         <view class="order-detail-page__simple-nav-inner">
            <text class="order-detail-page__simple-nav-title">{{ statusTextMap[orderDetail.status] || '订单详情' }}</text>
            <u-icon name="arrow-left" color="#ffffff" size="20" @click="handleBack"></u-icon>
            <text class="order-detail-page__simple-nav-title">订单详情</text>
            <u-icon name="arrow-left" color="#106EFA" size="20"></u-icon>
         </view>
      </view>
      <view v-if="showMapStatus" class="order-detail-page__fixed-top">
         <view class="order-detail-page__map-wrap">
            <map
               id="orderDetailMap"
               class="order-detail-page__map"
               :latitude="mapData.center.latitude"
               :longitude="mapData.center.longitude"
@@ -19,9 +22,6 @@
               :enable-zoom="true"
               :enable-scroll="true"
            ></map>
            <view class="order-detail-page__map-bubble">
               <text class="order-detail-page__map-bubble-text">剩余3.2km,约4分钟</text>
            </view>
         </view>
         <view class="order-detail-page__status-bar">
@@ -30,8 +30,8 @@
               <text class="order-detail-page__status-title">{{ statusTextMap[orderDetail.status] || '待取货' }}</text>
            </view>
            <view class="order-detail-page__status-right">
               <text v-if="orderDetail.status === 3" class="order-detail-page__status-cancel">取消订单</text>
               <text class="order-detail-page__status-no">#{{ orderIndex }}</text>
               <text v-if="orderDetail.status === 3" class="order-detail-page__status-cancel" @click="handleCancelOrder">取消订单</text>
               <text class="order-detail-page__status-no" v-if="orderIndex">#{{ orderIndex }}</text>
            </view>
         </view>
      </view>
@@ -42,8 +42,11 @@
               <view v-if="showMapStatus" class="order-detail-page__summary">
                  <view class="order-detail-page__summary-left">
                     <view class="order-detail-page__head-left">
                        <text class="order-detail-page__time">{{ orderDetail.remainMinutes }}</text>
                        <text class="order-detail-page__time-sub">分钟</text>
                        <template v-if="formattedRemainTime">
                           <text class="order-detail-page__time">{{ formattedRemainTime }}内</text>
                           <text class="order-detail-page__time-sub">送达</text>
                        </template>
                        <text class="order-detail-page__time" v-else>配送已超时,请尽快送达</text>
                     </view>
                     <view class="order-detail-page__tags">
@@ -54,8 +57,8 @@
                  </view>
                  <view class="order-detail-page__summary-right">
                     <text class="order-detail-page__price">¥{{ (orderDetail.driverFee / 100).toFixed(1) }}</text>
                     <text v-if="orderDetail.urgentAmount" class="order-detail-page__extra">含加急¥{{ orderDetail.urgentAmount / 100 }}</text>
                     <text class="order-detail-page__price">¥{{ orderDetail.platformRewardAmount ? ((orderDetail.driverFee + orderDetail.platformRewardAmount) / 100).toFixed(2) : (orderDetail.driverFee / 100).toFixed(2) }}</text>
                     <text v-if="orderDetail.platformRewardAmount" class="order-detail-page__extra">含加急¥{{ (orderDetail.platformRewardAmount / 100).toFixed(2) }}</text>
                  </view>
               </view>
@@ -70,46 +73,42 @@
                  </view>
                  <view class="order-detail-page__done-summary-right">
                     <view class="order-detail-page__done-price-row">
                        <text class="order-detail-page__price">¥{{ (orderDetail.driverFee / 100).toFixed(1) }}</text>
                        <text class="order-detail-page__price">¥{{ (orderDetail.driverFee / 100).toFixed(2) }}</text>
                     </view>
                     <text v-if="orderDetail.urgentAmount" class="order-detail-page__extra">含加急¥{{ orderDetail.urgentAmount / 100 }}</text>
                     <text v-if="orderDetail.isUrgent === 1" class="order-detail-page__extra">含加急¥{{ (orderDetail.urgentAmount / 100).toFixed(2) }}</text>
                  </view>
               </view>
               <view class="order-detail-page__route-list">
                  <view class="order-detail-page__route-item">
                     <view class="order-detail-page__route-left">
                        <text class="order-detail-page__distance-top">{{ orderDetail.takeDistance }}</text>
                        <text class="order-detail-page__distance-unit">m</text>
                        <view class="order-detail-page__route-badge order-detail-page__route-badge--take">取</view>
                        <view class="order-detail-page__route-divider"></view>
                     </view>
                     <view class="order-detail-page__route-main">
                        <view class="order-detail-page__route-texts">
                           <text class="order-detail-page__route-title">{{ orderDetail.takeName }}</text>
                           <text class="order-detail-page__route-desc">{{ orderDetail.depositShopAddress }}</text>
                        </view>
                        <view class="order-detail-page__route-actions">
                           <image class="order-detail-page__route-icon" src="/static/image/ic_c1all@2x.png" mode="aspectFit"></image>
                           <image class="order-detail-page__route-icon" src="/static/image/ic_daohang@2x.png" mode="aspectFit"></image>
                        </view>
                     </view>
                  </view>
                  <view class="order-detail-page__route-item order-detail-page__route-item--end">
                     <view class="order-detail-page__route-left">
                        <view class="order-detail-page__route-pin"></view>
                        <view class="order-detail-page__route-divider order-detail-page__route-divider--light"></view>
                        <text class="order-detail-page__distance-top">{{ orderDetail.depositDistance }}</text>
                        <text class="order-detail-page__distance-unit"></text>
                     </view>
                     <view class="order-detail-page__route-main">
                        <view class="order-detail-page__route-texts">
                           <text class="order-detail-page__route-title">{{ orderDetail.depositShopName }}</text>
                           <text class="order-detail-page__route-desc">{{ orderDetail.depositShopAddress }}</text>
                        </view>
                        <view class="order-detail-page__route-actions">
                           <image class="order-detail-page__route-icon" src="/static/image/ic_c1all@2x.png" mode="aspectFit"></image>
                           <image class="order-detail-page__route-icon" src="/static/image/ic_daohang@2x.png" mode="aspectFit"></image>
                        <view class="order-detail-page__route-actions" v-if="![7,99].includes(orderDetail.status)">
                           <image class="order-detail-page__route-icon" src="/static/image/ic_c1all@2x.png" mode="aspectFit" v-if="[3,4,5].includes(orderDetail.status) && orderDetail.depositShopPhone" @click="makeShopCall('deposit')"></image>
                           <image class="order-detail-page__route-icon" src="/static/image/ic_daohang@2x.png" mode="aspectFit" @click="navigateToAddress('deposit')"></image>
                        </view>
                     </view>
                  </view>
                  <view class="order-detail-page__route-item order-detail-page__route-item--end" style="padding-bottom: 30rpx; box-sizing: border-box; border-bottom: 1px solid #E5E5E5;">
                     <view class="order-detail-page__route-left">
                        <view class="order-detail-page__route-badge order-detail-page__route-badge--send">送</view>
                     </view>
                     <view class="order-detail-page__route-main">
                        <view class="order-detail-page__route-texts">
                           <text class="order-detail-page__route-title">{{ orderDetail.takeName }}</text>
                           <text class="order-detail-page__route-desc">{{ orderDetail.takeAddress }}</text>
                        </view>
                        <view class="order-detail-page__route-actions" v-if="![7,99].includes(orderDetail.status)">
                           <image class="order-detail-page__route-icon" src="/static/image/ic_c1all@2x.png" mode="aspectFit" v-if="[4,5].includes(orderDetail.status) && orderDetail.takeContactPhone" @click="makeShopCall('take')"></image>
                           <image class="order-detail-page__route-icon" src="/static/image/ic_daohang@2x.png" mode="aspectFit" @click="navigateToAddress('take')"></image>
                        </view>
                     </view>
                  </view>
@@ -120,30 +119,32 @@
                     <image class="order-detail-page__qrcode-image" :src="'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=' + orderDetail.driverVerifyCode" mode="aspectFit"></image>
                  </view>
                  <text class="order-detail-page__qrcode-value">{{ orderDetail.driverVerifyCode }}</text>
                  <text class="order-detail-page__qrcode-label">取货码</text>
                  <text class="order-detail-page__qrcode-label">{{ orderDetail.status === 3 ? '取货码' : '存件码' }}</text>
               </view>
            </view>
            <view class="order-detail-page__section">
               <text class="order-detail-page__section-title">客户信息</text>
               <view class="order-detail-page__row-info">
                  <text class="order-detail-page__row-text">{{ orderDetail.contactPhone }}</text>
                  <image class="order-detail-page__row-icon" src="/static/image/ic_call@2x.png" mode="aspectFit" @click="makePhoneCall"></image>
               </view>
               <view v-if="orderDetail.status === 7" class="order-detail-page__comment-card">
                  <text class="order-detail-page__comment-title">客户已评价:</text>
                  <view class="order-detail-page__comment-score">
                     <text class="order-detail-page__comment-star">★</text>
                     <text class="order-detail-page__comment-score-text">4.5</text>
            <view class="order-detail-page__section" style="margin-top: 30rpx; padding: 0 30rpx; box-sizing: border-box;">
               <view style="width: 100%; padding-bottom: 30rpx; box-sizing: border-box; border-bottom: 1px solid #E5E5E5;">
                  <text class="order-detail-page__section-title">客户信息</text>
                  <view class="order-detail-page__row-info">
                     <text class="order-detail-page__row-text">{{ orderDetail.customerInfo || '' }}</text>
                     <!-- <image class="order-detail-page__row-icon" v-if="[3,4].includes(orderDetail.status)" src="/static/image/ic_call@2x.png" mode="aspectFit" @click="makePhoneCall"></image> -->
                  </view>
                  <text class="order-detail-page__comment-content">送的很快,东西完好无损</text>
                  <image class="order-detail-page__comment-image" src="/static/logo.png" mode="aspectFill"></image>
                  <view v-if="orderDetail.commentStatus === 1" class="order-detail-page__comment-card">
                     <text class="order-detail-page__comment-title">客户已评价:</text>
                     <view class="order-detail-page__comment-score">
                        <text class="order-detail-page__comment-star">★</text>
                        <text class="order-detail-page__comment-score-text">4.5</text>
                     </view>
                     <text class="order-detail-page__comment-content">{{ orderDetail.commentContent || '' }}</text>
                     <image class="order-detail-page__comment-image" src="/static/logo.png" mode="aspectFill"></image>
                  </view>
               </view>
            </view>
            <view class="order-detail-page__section">
            <view class="order-detail-page__section" style="margin-top: 30rpx; padding: 0 30rpx; box-sizing: border-box;">
               <text class="order-detail-page__section-title">物品清单(共{{ goodsList.length }}件)</text>
               <view class="order-detail-page__goods-list">
               <view class="order-detail-page__goods-list" style="width: 100%; padding-bottom: 30rpx; box-sizing: border-box; border-bottom: 1px solid #E5E5E5;">
                  <view v-for="item in goodsList" :key="item.name" class="order-detail-page__goods-item">
                     <text class="order-detail-page__goods-name" :style="item.isOversized === 1 ? 'color: #FF0020;' : ''">{{ item.name }}</text>
                     <text class="order-detail-page__goods-count">x{{ item.quantity }}</text>
@@ -151,32 +152,48 @@
               </view>
            </view>
            <view class="order-detail-page__section">
            <view class="order-detail-page__section" style="margin-top: 30rpx; padding: 0 30rpx; box-sizing: border-box;">
               <text class="order-detail-page__section-title">物品信息</text>
               <text class="order-detail-page__goods-category">文件</text>
               <text class="order-detail-page__goods-category">{{ orderDetail.goodTypeName || '' }}</text>
               <view class="order-detail-page__photos">
                  <image v-for="(item, index) in photos" :key="index" class="order-detail-page__photo" :src="item" mode="aspectFill"></image>
                  <view class="order-detail-page__photo" v-for="(item, index) in photos" :key="index">
                     <image :src="item" mode="heightFix" @click="previewImage(item)"></image>
                  </view>
               </view>
            </view>
            <view class="order-detail-page__section order-detail-page__section--last">
            <view style="width: 100%; height: 30rpx; background-color: #f9f9f9;"></view>
            <view class="order-detail-page__section order-detail-page__section--last" style="margin-top: 30rpx; padding: 0 30rpx; box-sizing: border-box;">
               <text class="order-detail-page__section-title">订单信息</text>
               <view class="order-detail-page__detail-list">
                  <view class="order-detail-page__detail-item">
                     <text class="order-detail-page__detail-label">订单编号:</text>
                     <text class="order-detail-page__detail-value">{{ orderDetail.code }}</text>
                     <text class="order-detail-page__detail-value">{{ orderDetail.code || '-' }}</text>
                  </view>
                  <view v-if="orderDetail.createTime" class="order-detail-page__detail-item">
                     <text class="order-detail-page__detail-label">下单时间:</text>
                     <text class="order-detail-page__detail-value">{{ orderDetail.createTime }}</text>
                     <text class="order-detail-page__detail-value">{{ orderDetail.createTime || '-' }}</text>
                  </view>
                  <view v-if="orderDetail.acceptTime" class="order-detail-page__detail-item">
                  <view v-if="[3,4,5,6,7,99].includes(orderDetail.status)" class="order-detail-page__detail-item">
                     <text class="order-detail-page__detail-label">接单时间:</text>
                     <text class="order-detail-page__detail-value">{{ orderDetail.acceptTime }}</text>
                     <text class="order-detail-page__detail-value">{{ orderDetail.acceptTime || '-' }}</text>
                  </view>
                  <view class="order-detail-page__detail-item">
                  <view v-if="[3,4,5,6,7,99].includes(orderDetail.status)" class="order-detail-page__detail-item">
                     <text class="order-detail-page__detail-label">订单备注:</text>
                     <text class="order-detail-page__detail-value">{{ orderDetail.remark || '-' }}</text>
                  </view>
                  <view v-if="[4,5,6,7,99].includes(orderDetail.status)" class="order-detail-page__detail-item">
                     <text class="order-detail-page__detail-label">取货时间:</text>
                     <text class="order-detail-page__detail-value">{{ orderDetail.driverTakeTime || '-' }}</text>
                  </view>
                  <view v-if="[5,6,7,99].includes(orderDetail.status)" class="order-detail-page__detail-item">
                     <text class="order-detail-page__detail-label">完成时间:</text>
                     <text class="order-detail-page__detail-value">{{ orderDetail.finishTime || '-' }}</text>
                  </view>
                  <view v-if="orderDetail.isEvaluated === 1" class="order-detail-page__detail-item">
                     <text class="order-detail-page__detail-label">评价时间:</text>
                     <text class="order-detail-page__detail-value">{{ orderDetail.commentTime || '-' }}</text>
                  </view>
               </view>
            </view>
@@ -184,17 +201,47 @@
      </scroll-view>
      <view v-if="footerButtons.length" class="order-detail-page__footer">
         <button
            v-for="button in footerButtons"
            :key="button.text"
            class="order-detail-page__footer-btn"
            :class="button.primary ? 'order-detail-page__footer-btn--primary' : 'order-detail-page__footer-btn--ghost'"
            hover-class="order-detail-page__footer-btn--hover"
            @click="handleFooterAction(button)"
         >
            {{ button.text }}
         </button>
         <view></view>
         <view style="display: flex; align-items: center; gap: 20rpx;">
            <button
               v-for="button in footerButtons"
               :key="button.text"
               class="order-detail-page__footer-btn"
               :class="button.primary ? 'order-detail-page__footer-btn--primary' : 'order-detail-page__footer-btn--ghost'"
               hover-class="order-detail-page__footer-btn--hover"
               @click="handleFooterAction(button)"
            >
               {{ button.text }}
            </button>
         </view>
      </view>
      <u-modal
         :show="showCancelModal"
         showCancelButton
         @cancel="showCancelModal = false"
         cancelColor="#666666"
         confirmColor="#0055FF"
         title="取消订单确认"
         @confirm="confirmCancelOrder">
         <view style="text-align: center;color: #333333;font-size: 28rpx;font-weight: 400;">
            您今日还可取消 {{ cancelRemain }} 次订单,次数用尽后今日将无法接单,是否确认取消?
         </view>
      </u-modal>
      <u-modal
         :show="showGrabModal"
         showCancelButton
         @cancel="showGrabModal = false"
         cancelColor="#666666"
         confirmColor="#0055FF"
         title="温馨提示"
         @confirm="confirmGrabOrder">
         <view style="text-align: center;color: #333333;font-size: 28rpx;font-weight: 400;">
            {{ orderDetail && orderDetail.hasOversized === 1 ? '本订单有特大件尺寸行李,请确认是否继续抢单?' : '是否确认接单?' }}
         </view>
      </u-modal>
      <u-popup :show="showPhotoPopup" round="20" mode="bottom" @close="closePhotoPopup">
         <view class="photo-popup">
@@ -233,26 +280,112 @@
            <button class="photo-popup__submit" hover-class="photo-popup__submit--hover" @click="submitPhotoPopup">{{ photoPopupSubmitText }}</button>
         </view>
      </u-popup>
      <u-popup :show="showTimelinePopup" round="20" mode="bottom" @close="closeTimelinePopup">
         <view class="track-popup">
            <view class="track-popup__header">
               <text class="track-popup__title">订单轨迹</text>
               <view class="track-popup__close" @click="closeTimelinePopup">×</view>
            </view>
            <scroll-view scroll-y class="track-popup__body">
               <view class="track-empty" v-if="!timelineList.length">暂无轨迹信息</view>
               <view class="track-list" v-else>
                  <view class="track-item" v-for="(track, index) in timelineList" :key="track.key || index">
                     <view class="track-item__content">
                        <view class="track-item__rail">
                           <image v-if="index === 0" class="track-item__dot track-item__dot--active" src="/static/image/dian.png" mode="aspectFit"></image>
                           <view v-else class="track-item__dot"></view>
                           <view class="track-item__line" v-if="index !== timelineList.length - 1"></view>
                        </view>
                        <view class="track-item__body">
                           <text class="track-item__time">{{ track.time }}</text>
                           <text class="track-item__desc">{{ track.title }}</text>
                           <view class="track-item__images" v-if="track.images && track.images.length">
                              <image
                                 v-for="(img, imgIndex) in track.images"
                                 :key="track.key + '-' + imgIndex"
                                 :src="img"
                                 mode="aspectFill"
                                 @click="previewTimelineImages(track.images, imgIndex)"
                              ></image>
                           </view>
                        </view>
                     </view>
                  </view>
               </view>
            </scroll-view>
         </view>
         <!-- <view class="timeline-popup">
            <view class="timeline-popup__header">
               <view class="timeline-popup__placeholder"></view>
               <text class="timeline-popup__title">订单轨迹</text>
               <u-icon name="close" color="#A7ACB5" size="26" @click="closeTimelinePopup"></u-icon>
            </view>
            <scroll-view class="timeline-popup__scroll" scroll-y>
               <view v-if="timelineList.length" class="timeline-popup__list">
                  <view v-for="(item, index) in timelineList" :key="index" class="timeline-popup__item">
                     <view class="timeline-popup__axis">
                        <view class="timeline-popup__dot" :class="index === 0 ? 'timeline-popup__dot--active' : ''"></view>
                        <view v-if="index !== timelineList.length - 1" class="timeline-popup__line"></view>
                     </view>
                     <view class="timeline-popup__content">
                        <text class="timeline-popup__time">{{ item.time || '-' }}</text>
                        <text class="timeline-popup__desc">{{ item.title || '-' }}</text>
                        <view v-if="item.images.length" class="timeline-popup__images">
                           <image
                              v-for="(image, imageIndex) in item.images"
                              :key="imageIndex"
                              class="timeline-popup__image"
                              :src="image"
                              mode="aspectFill"
                              @click="previewTimelineImages(item.images, imageIndex)"
                           ></image>
                        </view>
                     </view>
                  </view>
               </view>
               <view v-else class="timeline-popup__empty">暂无订单轨迹</view>
            </scroll-view>
         </view> -->
      </u-popup>
   </view>
</template>
<script>
   import { mapState } from 'vuex'
   import { chooseImageWithNotice, getLocationWithNotice } from '@/utils/utils'
   export default {
      data() {
         return {
            orderId: null,
            orderIndex: null,
            orderDetail: {},
            orderDetail: null,
            statusBarHeight: 0,
            topFixedHeight: 0,
            showPhotoPopup: false,
            photoPopupMode: '',
            photoRemark: '',
            uploadedPhotos: [],
            showCancelModal: false,
            cancelRemain: 0,
            showGrabModal: false,
            showTimelinePopup: false,
            timelineList: [],
            currentLocation: null,
            routePoints: [],
            locationTimer: null,
            distance: 0,
            duration: 0,
            isWithinOperationRadius: true,
            statusTextMap: {
               2: '待接单',
               3: '待取货',
               4: '配送中',
               5: '已送达',
               7: '已完成',
               99: '已取消'
            },
@@ -261,65 +394,116 @@
         }
      },
      computed: {
         ...mapState(['userInfo']),
         formattedRemainTime() {
            const minutes = this.orderDetail.remainMinutes
            if (!minutes) return null
            if (minutes >= 60) {
               const hours = Math.floor(minutes / 60)
               const mins = minutes % 60
               return mins > 0 ? `${hours}小时${mins}分钟` : `${hours}小时`
            }
            return `${minutes}分钟`
         },
         showMapStatus() {
            return this.orderDetail.status === 2 || this.orderDetail.status === 3 || this.orderDetail.status === 4
            return this.orderDetail.status === 3 || this.orderDetail.status === 4
         },
         mapData() {
            const startPoint = { latitude: 31.829512, longitude: 117.239211 }
            const endPoint = { latitude: 31.841268, longitude: 117.278695 }
            const routePoints = [
            const startPoint = this.currentLocation || { latitude: this.orderDetail.navigateLat, longitude: this.orderDetail.navigateLng }
            const hasEndPoint = this.orderDetail.navigateLat && this.orderDetail.navigateLng
            const endPoint = { latitude: this.orderDetail.navigateLng, longitude: this.orderDetail.navigateLat }
            let center
            let scale = 12
            if (this.currentLocation && hasEndPoint) {
               const latSpan = Math.abs(this.currentLocation.latitude - endPoint.latitude)
               const lngSpan = Math.abs(this.currentLocation.longitude - endPoint.longitude)
               const maxSpan = Math.max(latSpan, lngSpan)
               center = {
                  latitude: (this.currentLocation.latitude + endPoint.latitude) / 2,
                  longitude: (this.currentLocation.longitude + endPoint.longitude) / 2
               }
               if (maxSpan > 0.3) {
                  scale = 9
               } else if (maxSpan > 0.15) {
                  scale = 10
               } else if (maxSpan > 0.08) {
                  scale = 11
               } else if (maxSpan > 0.04) {
                  scale = 12
               } else if (maxSpan > 0.02) {
                  scale = 13
               } else if (maxSpan > 0.01) {
                  scale = 14
               } else if (maxSpan > 0.005) {
                  scale = 15
               } else if (maxSpan > 0.002) {
                  scale = 16
               } else {
                  scale = 17
               }
            } else if (this.currentLocation) {
               center = this.currentLocation
            } else {
               center = { latitude: this.orderDetail.navigateLat, longitude: this.orderDetail.navigateLng }
            }
            const markers = [
               { id: 1, latitude: startPoint.latitude, longitude: startPoint.longitude, iconPath: '/static/image/start.png', width: 32, height: 38, anchor: { x: 0.5, y: 1 } },
               { id: 2, latitude: endPoint.latitude, longitude: endPoint.longitude, iconPath: '/static/image/end.png', width: 32, height: 38, anchor: { x: 0.5, y: 1 } },
               { id: 3, latitude: startPoint.latitude, longitude: startPoint.longitude, iconPath: '/static/image/dizhi.png', width: 12, height: 12, anchor: { x: 0.5, y: 0.5 } }
            ]
            const routePoints = this.routePoints.length > 0 ? this.routePoints : [
               startPoint,
               { latitude: 31.831624, longitude: 117.247836 },
               { latitude: 31.834918, longitude: 117.255467 },
               { latitude: 31.838214, longitude: 117.265358 },
               { latitude: 31.840126, longitude: 117.272481 },
               endPoint
            ]
            return {
               center: { latitude: 31.83539, longitude: 117.258953 },
               markers: [
                  { id: 1, latitude: startPoint.latitude, longitude: startPoint.longitude, iconPath: '/static/image/map_marker_start.svg', width: 32, height: 38, anchor: { x: 0.5, y: 1 } },
                  { id: 2, latitude: endPoint.latitude, longitude: endPoint.longitude, iconPath: '/static/image/map_marker_end.svg', width: 32, height: 38, anchor: { x: 0.5, y: 1 } }
            const result = {
               center,
               markers,
               polyline: this.routePoints.length > 0 ? [
                  { points: routePoints, color: '#05be76', width: 25, arrowLine: true, dottedLine: false }
               ] : [
                  { points: routePoints, color: '#05be76', width: 25, arrowLine: true, dottedLine: true }
               ],
               polyline: [
                  { points: routePoints, color: '#00c67a', width: 20, arrowLine: true, dottedLine: true, borderColor: '#469972', borderWidth: 10 }
               ],
               includePoints: routePoints,
               scale: 12
               includePoints: [startPoint, endPoint],
               scale
            }
            return result
         },
         bodyStyle() {
            const footerHeight = uni.upx2px(116)
            const simpleNavHeight = this.statusBarHeight + uni.upx2px(88)
            return {
               paddingTop: (this.showMapStatus ? this.topFixedHeight : simpleNavHeight) + 'px',
               height: `calc(100vh - ${this.footerButtons.length ? footerHeight : 0}px)`
               height: `calc(100vh - ${this.footerButtons.length ? footerHeight + 20 : 20}px)`
            }
         },
         footerButtons() {
            const status = this.orderDetail.status
            const takeShopId = this.orderDetail.takeShopId
            const buttons = [{ text: '订单轨迹', primary: false, action: 'timeline' }]
            if (status === 2) {
               return [{ text: '立即抢单', primary: true, action: 'grab' }]
               return buttons.concat([{ text: '立即抢单', primary: true, action: 'grab' }])
            }
            if (status === 3) {
               return [
               return buttons.concat([
                  { text: '取消订单', primary: false, action: 'cancel' },
                  { text: '拍照取货', primary: true, action: 'pickup' }
               ]
               ])
            }
            if (status === 4) {
               if (!takeShopId) {
                  return [{ text: '拍照送达', primary: true, action: 'deliver' }]
                  return buttons.concat([{ text: '拍照送达', primary: true, action: 'deliver' }])
               }
               return []
               return buttons
            }
            return []
            return buttons
         },
         photoPopupTitle() {
            return this.photoPopupMode === 'deliver' ? '拍照送达' : '拍照取货'
@@ -338,34 +522,248 @@
         const systemInfo = uni.getSystemInfoSync()
         this.statusBarHeight = systemInfo.statusBarHeight || 0
         this.orderId = options.id || pageOptions.id
         this.orderIndex = options.index || pageOptions.index
         this.orderIndex = options.index
         this.topFixedHeight = uni.upx2px(500 + 92)
         if (this.orderId) {
            this.getOrderDetail()
         }
      },
      onUnload() {
         if (this.locationTimer) {
            clearInterval(this.locationTimer)
            this.locationTimer = null
         }
      },
      methods: {
         getOrderDetail() {
            console.log('getOrderDetail', this.orderId)
            this.$u.api.orderDetail({ orderId: this.orderId }).then(res => {
               console.log(res)
               if (res.code === 200) {
                  this.orderDetail = res.data
                  console.log(this.orderDetail)
                  this.goodsList = res.data.items || []
                  this.photos = res.data.photos || []
               }
            }).catch(err => {
               console.log('err', err)
         handleBack() {
            uni.navigateBack({ delta: 1 });
         },
         initOperationRadius() {
            console.log('initOperationRadius')
            return new Promise((resolve) => {
               getLocationWithNotice({
                  type: 'gcj02',
                  success: (res) => {
                     this.$u.api.checkDriverOperationRadius({
                        lat: res.latitude,
                        lng: res.longitude,
                        orderId: this.orderId
                     }).then(res => {
                        if (res.code === 200) {
                           this.isWithinOperationRadius = res.data
                           console.log(res.data)
                           if (!this.isWithinOperationRadius) {
                              uni.showToast({
                                 title: '您当前位置与收货地址距离超出范围,请在地址附近重新拍照',
                                 icon: 'none'
                              })
                              resolve(false)
                           } else {
                              resolve(true)
                           }
                        } else {
                           resolve(false)
                        }
                     }).catch(() => {
                        resolve(false)
                     })
                  },
                  fail: () => {
                     this.isWithinOperationRadius = false
                     uni.showToast({
                        title: '无法获取您的位置信息,请前往设置开启定位权限',
                        icon: 'none'
                     })
                     resolve(false)
                  }
               }).catch(() => {
                  resolve(false)
               })
            })
         },
         makePhoneCall() {
            if (this.orderDetail.contactPhone) {
         getOrderDetail() {
            this.$u.api.orderDetail({ orderId: this.orderId }).then(res => {
               if (res.code === 200) {
                  console.log('orderDetail:', res.data)
                  this.orderDetail = res.data
                  this.goodsList = res.data.items || []
                  this.photos = res.data.orderImages || []
                  if ((this.orderDetail.status === 3 || this.orderDetail.status === 4) && this.orderDetail.navigateLat && this.orderDetail.navigateLng) {
                     this.getCurrentLocation()
                  } else {
                     console.log('Skipping getCurrentLocation - status or coordinates not available')
                  }
               }
            })
         },
         getCurrentLocation() {
            this.fetchLocation()
            this.locationTimer = setInterval(() => {
               this.fetchLocation()
            }, 60000)
         },
         fetchLocation() {
            getLocationWithNotice({
               type: 'gcj02',
               success: (res) => {
                  this.currentLocation = {
                     latitude: res.latitude,
                     longitude: res.longitude
                  }
                  this.getRoutePlan()
               },
               fail: (err) => {
                  console.log('获取位置失败', err)
               }
            }).catch(() => {})
         },
         getRoutePlan() {
            if (!this.currentLocation || !this.orderDetail.navigateLat || !this.orderDetail.navigateLng) {
               console.log('Skipping route plan - missing data')
               return
            }
            const from = `${this.currentLocation.latitude},${this.currentLocation.longitude}`
            const to = `${this.orderDetail.navigateLng},${this.orderDetail.navigateLat}`
            console.log('driverType', this.userInfo.driverType)
            this.$u.api.directionInfo({
               from,
               to,
               mode: this.userInfo.driverType
            }).then(res => {
               console.log('paths success:', res.data.route.paths[0])
               if (res.code === 200) {
                  const path = res.data.route.paths[0]
                  this.distance = path.distance
                  this.duration = path.duration
                  const points = []
                  path.steps.forEach(step => {
                     const polylineStr = step.polyline
                     const coordinates = polylineStr.split(';')
                     coordinates.forEach(coord => {
                        const [lng, lat] = coord.split(',')
                        points.push({
                           latitude: parseFloat(lat),
                           longitude: parseFloat(lng)
                        })
                     })
                  })
                  this.routePoints = points
                  this.$forceUpdate()
               }
            })
         },
         // makePhoneCall() {
         //    if (this.orderDetail.contactPhone) {
         //       uni.makePhoneCall({
         //          phoneNumber: this.orderDetail.contactPhone
         //       })
         //    }
         // },
         makeShopCall(type) {
            const phone = type === 'take' ? this.orderDetail.takeContactPhone : this.orderDetail.depositShopPhone
            if (phone) {
               uni.makePhoneCall({
                  phoneNumber: this.orderDetail.contactPhone
                  phoneNumber: phone
               })
            }
         },
         navigateToAddress(type) {
            let latitude, longitude, name, address
            if (type === 'deposit') {
               latitude = this.orderDetail.depositShopLat
               longitude = this.orderDetail.depositShopLng
               name = this.orderDetail.depositShopName
               address = this.orderDetail.depositShopAddress
            } else {
               latitude = this.orderDetail.takeLat
               longitude = this.orderDetail.takeLng
               name = this.orderDetail.takeName
               address = this.orderDetail.takeAddress
            }
            if (!latitude || !longitude) {
               uni.showToast({ title: '地址坐标缺失', icon: 'none' })
               return
            }
            uni.openLocation({
               latitude,
               longitude,
               name,
               address,
               success: () => {},
               fail: (err) => {
                  uni.showToast({ title: '打开地图失败', icon: 'none' })
                  console.error('openLocation fail:', err)
               }
            })
         },
         previewImage(current) {
            uni.previewImage({
               current,
               urls: this.photos
            })
         },
         normalizeTimelineImages(images) {
            if (Array.isArray(images)) {
               return images.filter(item => !!item)
            }
            if (typeof images === 'string') {
               return images.split(',').map(item => item.trim()).filter(item => !!item)
            }
            return []
         },
         openTimelinePopup() {
            if (!this.orderId) {
               uni.showToast({ title: '订单ID不存在', icon: 'none' })
               return
            }
            uni.showLoading({ title: '加载中...' })
            this.$u.api.timeline(this.orderId).then(res => {
               if (res.code === 200) {
                  const list = Array.isArray(res.data) ? res.data : []
                  this.timelineList = list.map(item => ({
                     time: item.time || '',
                     title: item.title || '',
                     images: this.normalizeTimelineImages(item.images)
                  }))
                  this.showTimelinePopup = true
               } else {
                  uni.showToast({ title: res.msg || '获取订单轨迹失败', icon: 'none' })
               }
            }).catch(() => {
               uni.showToast({ title: '获取订单轨迹失败', icon: 'none' })
            }).finally(() => {
               uni.hideLoading()
            })
         },
         closeTimelinePopup() {
            this.showTimelinePopup = false
         },
         previewTimelineImages(images, currentIndex) {
            if (!images || !images.length) {
               return
            }
            uni.previewImage({
               current: images[currentIndex],
               urls: images
            })
         },
         handleFooterAction(button) {
@@ -377,10 +775,14 @@
            }
            if (action === 'pickup' || action === 'deliver') {
               this.uploadedPhotos = []
               this.photoRemark = ''
               this.photoPopupMode = action
               this.showPhotoPopup = true
               this.initOperationRadius().then((isValid) => {
                  console.log(isValid)
                  if (!isValid) return
                  this.uploadedPhotos = []
                  this.photoRemark = ''
                  this.photoPopupMode = action
                  this.showPhotoPopup = true
               })
               return
            }
@@ -388,45 +790,49 @@
               this.handleGrabOrder()
               return
            }
            if (action === 'timeline') {
               this.openTimelinePopup()
            }
         },
         handleCancelOrder() {
            uni.showModal({
               title: '温馨提示',
               content: '确定要取消订单吗?',
               confirmColor: '#0055FF',
               cancelColor: '#666666',
               success: (res) => {
                  if (res.confirm) {
                     this.$u.api.cancelOrder({ orderId: this.orderId }).then(res => {
                        if (res.code === 200) {
                           uni.showToast({ title: '取消成功', icon: 'success' })
                           this.getOrderDetail()
                        } else {
                           uni.showToast({ title: res.msg || '取消失败', icon: 'none' })
                        }
                     })
                  }
            this.$u.api.cancelLimit().then(res => {
               if (res.code === 200) {
                  this.cancelRemain = res.data.remain
               }
            }).finally(() => {
               this.showCancelModal = true
            })
         },
         confirmCancelOrder() {
            this.$u.api.cancelOrder({ orderId: this.orderId }).then(res => {
               this.showCancelModal = false
               if (res.code === 200) {
                  uni.showToast({ title: '取消成功', icon: 'success' })
                  this.getOrderDetail()
               }
            }).finally(() => {
               this.showCancelModal = false
            })
         },
         handleGrabOrder() {
            uni.showModal({
               title: '温馨提示',
               content: '是否确认接单?',
               confirmColor: '#0055FF',
               cancelColor: '#666666',
               success: (res) => {
                  if (res.confirm) {
                     this.$u.api.grabOrder({ orderId: this.orderId }).then(res => {
                        if (res.code === 200) {
                           uni.showToast({ title: '接单成功', icon: 'success' })
                           uni.navigateBack()
                        } else {
                           uni.showToast({ title: res.msg || '接单失败', icon: 'none' })
                        }
                     })
                  }
            this.showGrabModal = true
         },
         confirmGrabOrder() {
            this.$u.api.grabOrder({ orderId: this.orderId }).then(res => {
               this.showGrabModal = false
               if (res.code === 200) {
                  uni.showToast({ title: '接单成功', icon: 'success' })
                  this.getOrderDetail()
                  uni.$emit('jiedanSuccess')
                  setTimeout(() => {
                     uni.navigateBack()
                  }, 1500)
               } else {
                  uni.showToast({ title: res.msg || '接单失败', icon: 'none' })
               }
            }).catch(() => {
               this.showGrabModal = false
            })
         },
         closePhotoPopup() {
@@ -434,33 +840,51 @@
         },
         chooseImage() {
            const count = 3 - this.uploadedPhotos.length
            uni.chooseImage({
            chooseImageWithNotice({
               count: count,
               sourceType: ['camera', 'album'],
               success: (res) => {
                  const tempFilePaths = res.tempFilePaths
                  this.uploadedPhotos = this.uploadedPhotos.concat(tempFilePaths)
               }
            })
            }).catch(() => {})
         },
         deletePhoto(index) {
            this.uploadedPhotos.splice(index, 1)
         },
         submitPhotoPopup() {
            submitPhotoPopup() {
            if (this.uploadedPhotos.length === 0) {
               uni.showToast({ title: '请上传照片', icon: 'none' })
               return
            }
            uni.showLoading({ title: '上传中...' })
            getLocationWithNotice({
               type: 'gcj02',
               success: (locationRes) => {
                  this.doUploadPhotos(locationRes.latitude, locationRes.longitude)
               },
               fail: () => {
                  this.doUploadPhotos(null, null)
               }
            }).catch(() => {
               this.doUploadPhotos(null, null)
            })
         },
         doUploadPhotos(latitude, longitude) {
            const uploadTasks = this.uploadedPhotos.map(path => {
               return new Promise((resolve, reject) => {
                  const formData = { folder: 'orders' }
                  if (latitude && longitude) {
                     formData.latitude = latitude
                     formData.longitude = longitude
                  }
                  console.log('formData:', formData)
                  uni.uploadFile({
                     url: this.$baseUrl + 'web/public/upload',
                     filePath: path,
                     name: 'file',
                     formData: {
                        folder: 'order'
                     },
                     formData: formData,
                     success: (uploadRes) => {
                        const data = JSON.parse(uploadRes.data)
                        if (data.code === 200) {
@@ -477,12 +901,15 @@
            })
            Promise.all(uploadTasks).then(images => {
               console.log(images)
               const api = this.photoPopupMode === 'deliver' ? 'confirmDeliver' : 'confirmPickup'
               const params = {
                  images: images.map(img => img.imgaddr),
                  orderId: this.orderId,
                  remark: this.photoRemark
               }
               if (latitude && longitude) {
                  params.latitude = latitude
                  params.longitude = longitude
               }
               return this.$u.api[api](params)
            }).then(res => {
@@ -505,9 +932,7 @@
<style lang="scss" scoped>
   .order-detail-page {
      height: 100vh;
      background: #f6f8fc;
      overflow: hidden;
      background: #ffffff;
      &__simple-nav {
         position: fixed;
@@ -522,7 +947,9 @@
         height: 88rpx;
         display: flex;
         align-items: center;
         padding: 0 24rpx;
         justify-content: space-between;
         padding: 0 30rpx;
         box-sizing: border-box;
      }
      &__simple-nav-title {
@@ -544,30 +971,15 @@
         position: relative;
         margin: 0;
         height: 500rpx;
         width: 750rpx;
         border-radius: 0;
         overflow: hidden;
         background: #dbe8ff;
      }
      &__map {
         width: 100%;
         height: 100%;
      }
      &__map-bubble {
         position: absolute;
         left: 34rpx;
         bottom: 32rpx;
         padding: 12rpx 18rpx;
         border-radius: 14rpx;
         background: rgba(255, 255, 255, 0.96);
         box-shadow: 0 8rpx 18rpx rgba(23, 74, 163, 0.12);
      }
      &__map-bubble-text {
         font-size: 24rpx;
         font-weight: 600;
         color: #2f6ff2;
         width: 750rpx;
         height: 500rpx;
      }
      &__status-bar {
@@ -615,13 +1027,11 @@
      }
      &__content {
         padding: 16rpx 0 calc(env(safe-area-inset-bottom) + 26rpx);
         // padding: 16rpx 0 calc(env(safe-area-inset-bottom) + 26rpx);
      }
      &__section {
         margin: 16rpx 20rpx 0;
         padding: 26rpx 22rpx;
         border-radius: 20rpx;
         // margin: 16rpx 20rpx 0;
         background: #ffffff;
         &--main {
@@ -637,13 +1047,18 @@
         display: flex;
         justify-content: space-between;
         align-items: flex-start;
         padding: 30rpx;
         box-sizing: border-box;
      }
      &__done-summary {
         padding: 30rpx;
         box-sizing: border-box;
         display: flex;
         justify-content: space-between;
         align-items: flex-start;
         gap: 20rpx;
         background: #F6F9FF;
      }
      &__done-summary-left {
@@ -699,9 +1114,9 @@
      }
      &__time {
         font-size: 42rpx;
         font-weight: 700;
         color: #ff972c;
         font-size: 34rpx;
         font-weight: 600;
         color: #FA8010;
      }
      &__time-sub,
@@ -722,7 +1137,7 @@
      }
      &__price {
         font-size: 44rpx;
         font-size: 26rpx;
         font-weight: 700;
         color: #ff4132;
      }
@@ -751,22 +1166,25 @@
      &__tag-text {
         padding: 5rpx 12rpx;
         border-radius: 8rpx;
         background: #ff9c45;
         background: linear-gradient(319deg, #EE9D0E 0%, #FF4E4E 100%);
         font-size: 22rpx;
         font-weight: 600;
         color: #ffffff;
      }
      &__route-list {
         margin-top: 22rpx;
         margin-top: 36rpx;
         padding: 0 30rpx;
         box-sizing: border-box;
      }
      &__route-item {
         display: flex;
         align-items: flex-start;
         align-items: stretch;
         position: relative;
         &--end {
            margin-top: 24rpx;
            margin-top: 20rpx;
         }
      }
@@ -776,38 +1194,37 @@
         flex-direction: column;
         align-items: center;
         flex-shrink: 0;
         position: relative;
      }
      &__distance-top {
         font-size: 28rpx;
      &__route-badge {
         width: 44rpx;
         height: 44rpx;
         border-radius: 50%;
         display: flex;
         align-items: center;
         justify-content: center;
         font-size: 24rpx;
         font-weight: 600;
         color: #515866;
      }
         color: #ffffff;
         position: relative;
         z-index: 1;
      &__distance-unit {
         font-size: 22rpx;
         color: #939aa6;
      }
         &--take {
            background: #10B2FA;
         }
      &__route-divider {
         width: 4rpx;
         height: 42rpx;
         margin-top: 8rpx;
         border-radius: 999rpx;
         background: #d7dbe2;
         &--light {
            height: 18rpx;
            margin: 8rpx 0;
         &--send {
            background: #FA8010;
         }
      }
      &__route-pin {
         width: 18rpx;
         height: 18rpx;
         margin-top: 4rpx;
         border-radius: 50%;
         background: #888f9b;
      &__route-divider {
         position: absolute;
         top: 64rpx;
         bottom: 0;
         width: 0;
         border-left: 2rpx dashed #d7dbe2;
      }
      &__route-main {
@@ -815,6 +1232,7 @@
         display: flex;
         justify-content: space-between;
         gap: 18rpx;
         margin-left: 10rpx;
      }
      &__route-texts {
@@ -824,16 +1242,19 @@
      &__route-title {
         display: block;
         font-size: 38rpx;
         font-weight: 700;
         font-weight: 600;
         font-size: 34rpx;
         color: #222222;
         line-height: 1.3;
         color: #2b3139;
      }
      &__route-desc {
         display: block;
         margin-top: 8rpx;
         line-height: 1.5;
         font-weight: 400;
         font-size: 26rpx;
         color: #999999;
      }
      &__route-actions {
@@ -853,7 +1274,7 @@
         display: flex;
         flex-direction: column;
         align-items: center;
         padding-top: 30rpx;
         margin-top: 30rpx;
      }
      &__qrcode-box {
@@ -871,14 +1292,17 @@
      }
      &__qrcode-value {
         margin-top: 16rpx;
         font-size: 40rpx;
         font-weight: 700;
         color: #303640;
         margin-top: 32rpx;
         font-weight: 600;
         font-size: 36rpx;
         color: #222222;
      }
      &__qrcode-label {
         margin-top: 6rpx;
         margin-top: 12rpx;
         font-weight: 400;
         font-size: 26rpx;
         color: #999999;
      }
      &__section-title {
@@ -968,14 +1392,23 @@
      &__photos {
         display: flex;
         gap: 16rpx;
         margin-top: 18rpx;
         flex-wrap: wrap;
         gap: 20rpx;
         margin-top: 30rpx;
         padding-bottom: 30rpx;
         box-sizing: border-box;
      }
      &__photo {
         width: 92rpx;
         height: 92rpx;
         border-radius: 10rpx;
         width: 120rpx;
         height: 120rpx;
         border-radius: 8rpx;
         overflow: hidden;
         image {
            width: 100%;
            height: 100%;
         }
      }
      &__detail-label {
@@ -992,7 +1425,7 @@
         right: 0;
         bottom: 0;
         display: flex;
         justify-content: flex-end;
         justify-content: space-between;
         gap: 20rpx;
         padding: 14rpx 20rpx calc(env(safe-area-inset-bottom) + 14rpx);
         background: #ffffff;
@@ -1026,6 +1459,128 @@
         &--hover {
            opacity: 0.92;
         }
      }
   }
   .track-popup {
      background: #FFFFFF;
      border-radius: 24rpx 24rpx 0 0;
      padding: 24rpx 24rpx calc(24rpx + env(safe-area-inset-bottom));
      box-sizing: border-box;
      .track-popup__header {
         height: 88rpx;
         display: flex;
         align-items: center;
         justify-content: center;
         position: relative;
      }
      .track-popup__title {
         font-weight: 600;
         font-size: 32rpx;
         color: #222222;
      }
      .track-popup__close {
         position: absolute;
         right: 0;
         top: 50%;
         transform: translateY(-50%);
         width: 56rpx;
         height: 56rpx;
         display: flex;
         align-items: center;
         justify-content: center;
         font-size: 56rpx;
         line-height: 1;
         color: #A5A7AD;
      }
      .track-popup__body {
         height: 50vh;
      }
      .track-empty {
         line-height: 50vh;
         text-align: center;
         font-size: 28rpx;
         color: #999999;
      }
      .track-list {
         padding: 12rpx 0 0;
      }
      .track-item {
         position: relative;
      }
      .track-item__rail {
         width: 34rpx;
         flex-shrink: 0;
         display: flex;
         flex-direction: column;
         align-items: center;
         align-self: stretch;
         position: relative;
      }
      .track-item__dot {
         width: 16rpx;
         height: 16rpx;
         flex-shrink: 0;
         position: relative;
         z-index: 1;
         border-radius: 50%;
         background: #CCCCCC;
         margin-top: 12rpx;
      }
      .track-item__dot--active {
         width: 32rpx;
         height: 32rpx;
         flex-shrink: 0;
         display: block;
         position: relative;
         z-index: 1;
         margin-top: 4rpx;
         background: transparent;
      }
      .track-item__line {
         width: 2rpx;
         position: absolute;
         left: 50%;
         top: 28rpx;
         bottom: -30rpx;
         transform: translateX(-50%);
         background: #E6EAF0;
      }
      .track-item__content {
         display: flex;
         align-items: flex-start;
         column-gap: 22rpx;
         padding: 0 0 20rpx;
      }
      .track-item__body {
         flex: 1;
         min-width: 0;
      }
      .track-item__time {
         display: block;
         font-weight: 400;
         font-size: 26rpx;
         color: #666666;
         line-height: 1.4;
      }
      .track-item__desc {
         display: block;
         margin-top: 22rpx;
         font-weight: 500;
         font-size: 30rpx;
         color: #666666;
      }
      .track-item__images {
         display: flex;
         flex-wrap: wrap;
         gap: 18rpx;
         margin-top: 24rpx;
         image {
            width: 130rpx;
            height: 130rpx;
            border-radius: 12rpx;
            background: #F3F5F8;
         }
      }
   }
@@ -1103,9 +1658,8 @@
      &__upload-card,
      &__preview-card {
         position: relative;
         width: 160rpx;
         height: 160rpx;
         border-radius: 8rpx;
         width: 144rpx;
         height: 144rpx;
         overflow: hidden;
      }
@@ -1114,7 +1668,6 @@
         flex-direction: column;
         align-items: center;
         justify-content: center;
         border: 2rpx solid #ebedf2;
         background: #f8f9fb;
      }
@@ -1189,4 +1742,117 @@
         }
      }
   }
   .timeline-popup {
      padding: 26rpx 28rpx calc(env(safe-area-inset-bottom) + 28rpx);
      background: #ffffff;
      border-top-left-radius: 20rpx;
      border-top-right-radius: 20rpx;
      &__header {
         display: flex;
         align-items: center;
         justify-content: space-between;
      }
      &__placeholder {
         width: 52rpx;
         height: 52rpx;
         flex-shrink: 0;
         opacity: 0;
      }
      &__title {
         font-size: 40rpx;
         font-weight: 700;
         color: #222222;
      }
      &__scroll {
         max-height: 50vh;
         margin-top: 30rpx;
      }
      &__list {
         padding-bottom: 12rpx;
      }
      &__item {
         display: flex;
         align-items: stretch;
      }
      &__axis {
         width: 40rpx;
         display: flex;
         flex-direction: column;
         align-items: center;
         flex-shrink: 0;
      }
      &__dot {
         width: 18rpx;
         height: 18rpx;
         margin-top: 8rpx;
         border-radius: 50%;
         background: #D5D9E2;
         box-shadow: 0 0 0 8rpx #ffffff;
         z-index: 1;
         &--active {
            background: #2F80FF;
            box-shadow: 0 0 0 8rpx rgba(47, 128, 255, 0.14);
         }
      }
      &__line {
         flex: 1;
         width: 2rpx;
         margin-top: 8rpx;
         background: #E5EAF2;
      }
      &__content {
         flex: 1;
         padding: 0 0 40rpx 16rpx;
      }
      &__time {
         display: block;
         font-size: 26rpx;
         font-weight: 400;
         color: #808692;
         line-height: 1.4;
      }
      &__desc {
         display: block;
         margin-top: 18rpx;
         font-size: 40rpx;
         font-weight: 600;
         color: #4A4A4A;
         line-height: 1.45;
      }
      &__images {
         display: flex;
         flex-wrap: wrap;
         gap: 16rpx;
         margin-top: 22rpx;
      }
      &__image {
         width: 124rpx;
         height: 124rpx;
         border-radius: 12rpx;
         background: #F2F4F7;
      }
      &__empty {
         line-height: 50vh;
         text-align: center;
         font-size: 28rpx;
         color: #A0A6B0;
      }
   }
</style>