certLogistics.vue 7.02 KB
<template>
  <view class="logistics-page">
    <!-- 物流信息卡片 -->
    <view class="logistics-info-card">
      <view class="info-header">
        <uni-icons type=" truck" size="20" color="#AD181F"></uni-icons>
        <text class="header-title">物流信息</text>
      </view>
      <view class="info-content">
        <view class="info-row">
          <text class="label">运单号</text>
          <text class="value tracking-number" @click="copyTracking">{{ currentItem.postCode || '-' }}
            <text class="copy-btn">复制</text>
          </text>
        </view>
        <view class="info-row">
          <text class="label">当前状态</text>
          <text class="value status-sended" v-if="currentItem.postStatus == 1">已邮寄</text>
          <text class="value status-pending" v-else>未邮寄</text>
        </view>
        <view class="info-row">
          <text class="label">邮寄时间</text>
          <text class="value">{{ currentItem.submitTime ? formatDateTime(currentItem.submitTime) : '-' }}</text>
        </view>
      </view>
    </view>

    <!-- 物流时间线 -->
    <view class="timeline-section">
      <view class="section-header">
        <uni-icons type="location-filled" size="18" color="#AD181F"></uni-icons>
        <text class="section-title">运输轨迹</text>
      </view>

      <view class="loading-wrap" v-if="loading">
        <uni-load-more status="loading"></uni-load-more>
      </view>

      <view class="timeline-wrap" v-else>
        <view class="timeline-list" v-if="trackingNodes.length > 0">
          <view
            class="timeline-item"
            :class="{ first: idx === 0 }"
            v-for="(node, idx) in trackingNodes"
            :key="idx"
          >
            <view class="timeline-left">
              <view class="timeline-dot">
                <uni-icons v-if="idx === 0" type="check" size="12" color="#fff"></uni-icons>
              </view>
              <view class="timeline-line" v-if="idx < trackingNodes.length - 1"></view>
            </view>
            <view class="timeline-right">
              <view class="timeline-content">
                <view class="timeline-title">{{ node.categoryName }}</view>
                <view class="timeline-time">{{ node.operationTime }}</view>
                <view class="timeline-desc">{{ node.operationRemark }}</view>
              </view>
            </view>
          </view>
        </view>

        <view class="no-logistics" v-else>
          <image mode="aspectFit" :src="config.baseUrl_api + '/fs/static/nodata.png'"></image>
          <text>暂无物流信息</text>
        </view>
      </view>
    </view>
  </view>
</template>

<script setup>
import * as api from '@/common/api.js'
import config from '@/config.js'
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'

const loading = ref(false)
const payId = ref('')
const currentItem = ref({})
const trackingNodes = ref([])

onLoad((options) => {
  if (options.payId) {
    payId.value = options.payId
    if (options.postCode) currentItem.value.postCode = options.postCode
    if (options.postStatus) currentItem.value.postStatus = parseInt(options.postStatus)
    if (options.submitTime) currentItem.value.submitTime = options.submitTime
    getLogisticsInfo()
  }
})

function getLogisticsInfo() {
  if (!payId.value) return

  loading.value = true
  api.queryTrace(payId.value).then(res => {
    loading.value = false
    trackingNodes.value = res.data || []
  }).catch(err => {
    loading.value = false
    console.error('获取物流信息失败', err)
    uni.showToast({ title: '获取物流信息失败', icon: 'none' })
  })
}

function copyTracking() {
  if (!currentItem.value.postCode) return
  uni.setClipboardData({
    data: currentItem.value.postCode,
    success: () => {
      uni.showToast({ title: '已复制', icon: 'success' })
    }
  })
}

function formatDateTime(dateStr) {
  if (!dateStr) return '-'
  return dateStr.replace('T', ' ').substring(0, 19)
}
</script>

<style lang="scss" scoped>
.logistics-page {
  min-height: 100vh;
  background-color: #f5f5f5;
  padding: 20rpx;
  padding-bottom: 40rpx;
}

/* 物流信息卡片 */
.logistics-info-card {
  background-color: #fff;
  border-radius: 16rpx;
  padding: 30rpx;
  margin-bottom: 20rpx;
}

.info-header {
  display: flex;
  align-items: center;
  margin-bottom: 24rpx;

  .header-title {
    font-size: 30rpx;
    font-weight: 600;
    color: #333;
    margin-left: 10rpx;
  }
}

.info-content {
  .info-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 16rpx 0;
    border-bottom: 1px solid #f0f0f0;

    &:last-child {
      border-bottom: none;
    }

    .label {
      font-size: 26rpx;
      color: #999;
    }

    .value {
      font-size: 26rpx;
      color: #333;

      &.status-sended {
        color: #4caf50;
      }

      &.status-pending {
        color: #ff9800;
      }

      &.tracking-number {
        display: flex;
        align-items: center;
        gap: 10rpx;
      }
    }

    .copy-btn {
      font-size: 22rpx;
      color: #AD181F;
      background-color: #fef0f0;
      padding: 4rpx 12rpx;
      border-radius: 16rpx;
    }
  }
}

/* 时间线 */
.timeline-section {
  background-color: #fff;
  border-radius: 16rpx;
  padding: 30rpx;
}

.section-header {
  display: flex;
  align-items: center;
  margin-bottom: 30rpx;

  .section-title {
    font-size: 30rpx;
    font-weight: 600;
    color: #333;
    margin-left: 10rpx;
  }
}

.loading-wrap {
  padding: 60rpx 0;
  text-align: center;
}

.timeline-wrap {
  .timeline-list {
    padding-left: 10rpx;
  }

  .timeline-item {
    display: flex;
    position: relative;

    &.first {
      .timeline-dot {
        background-color: #AD181F;
        border-color: #AD181F;
      }

      .timeline-title {
        color: #AD181F;
      }
    }
  }

  .timeline-left {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin-right: 20rpx;
  }

  .timeline-dot {
    width: 28rpx;
    height: 28rpx;
    border-radius: 50%;
    border: 2px solid #ccc;
    background-color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    z-index: 1;
  }

  .timeline-line {
    width: 2rpx;
    flex: 1;
    background-color: #e0e0e0;
    margin: 8rpx 0;
    min-height: 60rpx;
  }

  .timeline-right {
    flex: 1;
    padding-bottom: 40rpx;
  }

  .timeline-content {
    .timeline-title {
      font-size: 28rpx;
      color: #333;
      font-weight: 500;
    }

    .timeline-time {
      font-size: 24rpx;
      color: #999;
      margin-top: 8rpx;
    }

    .timeline-desc {
      font-size: 26rpx;
      color: #666;
      margin-top: 12rpx;
      line-height: 1.6;
      background-color: #fafafa;
      padding: 16rpx;
      border-radius: 8rpx;
    }
  }

  .no-logistics {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 80rpx 0;

    image {
      width: 200rpx;
      height: 200rpx;
    }

    text {
      font-size: 28rpx;
      color: #999;
      margin-top: 20rpx;
    }
  }
}
</style>