order-detail.vue 9.86 KB
<template>
  <view class="page-shell order-detail-page">
    <view class="page-blur"></view>
    <view class="page-blur-secondary"></view>
    <view class="page-blur-tertiary"></view>

    <view class="page-content subpage-content">
      <view class="subpage-nav">
        <view class="subpage-back" @tap="goBack">‹</view>
        <view class="subpage-title-wrap">
          <text class="subpage-title">订单详情</text>
          <text class="subpage-subtitle">商品、支付与自提状态,一页为你清晰整理</text>
        </view>
      </view>

      <view class="subpage-hero glass-card">
        <text class="hero-kicker">{{ statusText }}</text>
        <text class="hero-title">{{ mainItemName }}</text>
        <text class="hero-desc">订单编号 {{ order.orderNo }}</text>
      </view>

      <view v-for="item in items" :key="item.id" class="order-item-card glass-card">
        <image v-if="item.productImg" class="order-thumb" :src="item.productImg" mode="aspectFill" />
        <view v-else class="order-art">
          <view class="order-art-ring"></view>
          <text class="order-art-label">{{ shortLabel(item.productName) }}</text>
        </view>
        <view class="order-copy">
          <text class="order-name">{{ item.productName }}</text>
          <text class="order-sku">{{ item.skuName || '默认规格' }} × {{ item.quantity }}</text>
          <text class="order-amount">¥{{ item.amount }}</text>
        </view>
      </view>

      <lf-pickup-code
        v-if="order.orderStatus === 2 && order.pickupCode"
        :code="order.pickupCode"
        title="门店自提取件码"
        desc="到自提门店出示取件码,门店核销后即可完成自提。"
      />

      <view v-else-if="order.orderStatus === 1" class="subpage-card wait-card">
        <text class="wait-title">支付完成后自动生成取件码</text>
        <text class="wait-desc">当前订单尚未完成支付,支付成功后将自动生成取件码与自提信息。</text>
      </view>

      <lf-pickup-complete
        v-else-if="order.orderStatus === 3"
        title="该订单已完成核销"
        desc="门店已完成核销与自提,本页不再展示取件码,可在下方查看核销时间与订单状态。"
        :status="statusText"
        :time="order.pickupTime"
      />

      <view class="subpage-card">
        <view class="section-head">
          <text class="section-title">订单信息</text>
          <text class="section-link">门店自提</text>
        </view>
        <view class="info-list">
          <view class="info-row">
            <text class="info-label">订单编号</text>
            <text class="info-value">{{ order.orderNo }}</text>
          </view>
          <view class="info-row">
            <text class="info-label">自提门店</text>
            <text class="info-value">{{ order.storeName || '—' }}</text>
          </view>
          <view v-if="order.pickupCode" class="info-row">
            <text class="info-label">取件码</text>
            <text class="info-value">{{ order.pickupCode }}</text>
          </view>
          <view class="info-row">
            <text class="info-label">下单时间</text>
            <text class="info-value">{{ formatTime(order.createTime) }}</text>
          </view>
          <view v-if="order.payTime" class="info-row">
            <text class="info-label">支付时间</text>
            <text class="info-value">{{ formatTime(order.payTime) }}</text>
          </view>
          <view v-if="order.pickupTime" class="info-row">
            <text class="info-label">核销时间</text>
            <text class="info-value">{{ formatTime(order.pickupTime) }}</text>
          </view>
          <view class="info-row">
            <text class="info-label">实付金额</text>
            <text class="info-value">¥{{ order.payAmount }}</text>
          </view>
          <view v-if="order.remark" class="info-row">
            <text class="info-label">订单备注</text>
            <text class="info-value">{{ order.remark }}</text>
          </view>
          <view class="info-row">
            <text class="info-label">订单状态</text>
            <text class="info-value">{{ statusText }}</text>
          </view>
        </view>
      </view>

      <view class="cta-row">
        <view class="secondary-button" @tap="secondaryAction">{{ secondaryLabel }}</view>
        <view class="primary-button" @tap="primaryAction">{{ primaryLabel }}</view>
      </view>
    </view>
  </view>
</template>

<script>
import { getOrderDetail, cancelOrder, prepay, mockPay } from '@/api/mall'
import { orderStatusText } from '@/utils/orderStatus'

export default {
  data() {
    return {
      orderId: '',
      order: {}
    }
  },
  computed: {
    items() {
      return this.order.items || []
    },
    statusText() {
      return orderStatusText(this.order.orderStatus)
    },
    mainItemName() {
      const items = this.items
      if (!items.length) return '绿纤商品'
      return items.length > 1 ? `${items[0].productName} 等 ${items.length} 件` : items[0].productName
    },
    primaryLabel() {
      switch (this.order.orderStatus) {
        case 1:
          return '立即支付'
        case 2:
        case 3:
          return '继续选购'
        default:
          return '返回订单'
      }
    },
    secondaryLabel() {
      if (this.order.orderStatus === 1) return '取消订单'
      if (this.order.orderStatus === 2 && this.order.pickupCode) return '复制取件码'
      return '联系客服'
    }
  },
  onLoad(options) {
    this.orderId = options.id
    this.loadOrder()
  },
  onShow() {
    if (this.orderId) this.loadOrder()
  },
  methods: {
    loadOrder() {
      getOrderDetail(this.orderId)
        .then((data) => {
          this.order = data || {}
        })
        .catch(() => {})
    },
    formatTime(time) {
      return time ? String(time).replace('T', ' ').slice(0, 16) : ''
    },
    shortLabel(title) {
      return String(title || '绿纤').replace(/\s+/g, '').slice(0, 2)
    },
    goBack() {
      uni.navigateBack({ fail: () => uni.redirectTo({ url: '/packageProfile/order-list/order-list' }) })
    },
    primaryAction() {
      if (this.order.orderStatus === 1) {
        this.payOrder()
        return
      }
      if (this.order.orderStatus === 2 || this.order.orderStatus === 3) {
        uni.reLaunch({ url: '/packageMall/main/main' })
        return
      }
      uni.redirectTo({ url: '/packageProfile/order-list/order-list' })
    },
    secondaryAction() {
      if (this.order.orderStatus === 1) {
        this.doCancel()
        return
      }
      if (this.order.orderStatus === 2 && this.order.pickupCode) {
        uni.setClipboardData({
          data: this.order.pickupCode,
          success: () => uni.showToast({ title: '取件码已复制', icon: 'none' })
        })
        return
      }
      uni.showToast({ title: '已为您接入专属客服', icon: 'none' })
    },
    doCancel() {
      uni.showModal({
        title: '取消订单',
        content: '确认取消该订单吗?',
        success: (res) => {
          if (!res.confirm) return
          cancelOrder(this.orderId)
            .then(() => {
              uni.showToast({ title: '订单已取消', icon: 'none' })
              this.loadOrder()
            })
            .catch(() => {})
        }
      })
    },
    payOrder() {
      uni.showLoading({ title: '支付中', mask: true })
      prepay({ OrderId: this.orderId })
        .then((pay) => {
          if (pay && pay.mock === true) {
            return mockPay({ OrderId: this.orderId }).then(() => {
              uni.hideLoading()
              uni.showToast({ title: '支付成功', icon: 'none' })
              this.loadOrder()
            })
          }
          const params = (pay && pay.prepayParams) || {}
          uni.hideLoading()
          uni.requestPayment({
            provider: 'wxpay',
            timeStamp: params.timeStamp,
            nonceStr: params.nonceStr,
            package: params.package,
            signType: params.signType,
            paySign: params.paySign,
            success: () => {
              // TODO: 真实支付以微信回调 PayNotify 为准,此处刷新订单状态
              this.loadOrder()
            },
            fail: () => uni.showToast({ title: '支付未完成', icon: 'none' })
          })
        })
        .catch(() => {
          uni.hideLoading()
        })
    }
  }
}
</script>

<style lang="scss" scoped>
@import '@/styles/mixins.scss';
@import '@/styles/subpage.scss';

.order-item-card {
  display: flex;
  padding: 28rpx;
  margin-bottom: 24rpx;
}

.order-thumb {
  width: 180rpx;
  height: 180rpx;
  border-radius: 28rpx;
  flex-shrink: 0;
}

.order-art {
  width: 180rpx;
  height: 180rpx;
  border-radius: 28rpx;
  flex-shrink: 0;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(241, 246, 241, 0.96);
  border: 1rpx solid rgba(120, 146, 121, 0.14);
  box-shadow: inset 0 1rpx 0 rgba(255, 255, 255, 0.65);
  overflow: hidden;
}

.order-art-ring {
  width: 96rpx;
  height: 96rpx;
  border-radius: 50%;
  border: 1rpx solid rgba(120, 146, 121, 0.18);
  background: rgba(255, 255, 255, 0.45);
}

.order-art-label {
  position: absolute;
  font-size: 24rpx;
  letter-spacing: 4rpx;
  color: $brand-primary-deep;
  font-weight: 600;
}

.order-copy {
  flex: 1;
  margin-left: 20rpx;
}

.order-name,
.order-sku,
.order-amount,
.wait-title,
.wait-desc {
  display: block;
}

.order-name {
  font-size: 30rpx;
  line-height: 1.5;
  font-weight: 700;
}

.order-sku {
  margin-top: 16rpx;
  font-size: 24rpx;
  color: $text-secondary;
}

.order-amount {
  margin-top: 18rpx;
  font-size: 34rpx;
  font-weight: 700;
  color: $brand-primary-deep;
}

.wait-card {
  margin-bottom: 24rpx;
}

.wait-title {
  font-size: 30rpx;
  font-weight: 700;
  color: $text-primary;
}

.wait-desc {
  margin-top: 14rpx;
  font-size: 24rpx;
  line-height: 1.8;
  color: $text-secondary;
}
</style>