order-list.vue 7.77 KB
<template>
  <view class="page-shell order-list-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>

      <scroll-view class="status-scroll" scroll-x>
        <view class="status-row">
          <view v-for="item in statuses" :key="item.key" class="status-chip" :class="{ active: activeStatus === item.key }" @tap="selectStatus(item.key)">
            {{ item.label }}
          </view>
        </view>
      </scroll-view>

      <view v-for="item in orders" :key="item.id" class="order-card glass-card" @tap="goDetail(item)">
        <view class="order-top">
          <text class="order-status">{{ statusText(item.orderStatus) }}</text>
          <text class="order-time">{{ formatTime(item.createTime) }}</text>
        </view>
        <view class="order-main">
          <image v-if="firstImg(item)" class="order-thumb" :src="firstImg(item)" mode="aspectFill" />
          <view v-else class="order-art">
            <view class="order-art-ring"></view>
            <text class="order-art-label">{{ shortLabel(firstName(item)) }}</text>
          </view>
          <view class="order-copy">
            <text class="order-name">{{ firstName(item) }}</text>
            <text class="order-sku">{{ itemsSummary(item) }}</text>
            <text class="order-store">{{ item.storeName }} · 门店自提</text>
            <text class="order-amount">¥{{ item.payAmount }}</text>
          </view>
        </view>
      </view>

      <view v-if="!loading && !orders.length" class="empty-tip">暂无相关订单</view>
      <view v-if="loading" class="empty-tip">正在加载…</view>
    </view>
  </view>
</template>

<script>
import { getMyOrders } from '@/api/mall'
import { orderStatusText } from '@/utils/orderStatus'

export default {
  data() {
    return {
      orders: [],
      activeStatus: 0,
      statuses: [
        { key: 0, label: '全部' },
        { key: 1, label: '待付款' },
        { key: 2, label: '待自提' },
        { key: 3, label: '已完成' },
        { key: 4, label: '已取消' }
      ],
      currentPage: 1,
      pageSize: 10,
      total: 0,
      loading: false,
      finished: false
    }
  },
  onLoad(options) {
    if (options.status !== undefined && options.status !== '') {
      this.activeStatus = Number(options.status) || 0
    }
    this.resetAndLoad()
  },
  onReachBottom() {
    this.loadList()
  },
  methods: {
    resetAndLoad() {
      this.currentPage = 1
      this.orders = []
      this.total = 0
      this.finished = false
      this.loadList()
    },
    loadList() {
      if (this.loading || this.finished) return
      this.loading = true
      const params = { currentPage: this.currentPage, pageSize: this.pageSize }
      if (this.activeStatus) params.OrderStatus = this.activeStatus
      getMyOrders(params)
        .then((res) => {
          const list = (res && res.list) || []
          this.orders = this.orders.concat(list)
          const pagination = (res && res.pagination) || {}
          this.total = pagination.total || this.orders.length
          if (list.length < this.pageSize || this.orders.length >= this.total) {
            this.finished = true
          } else {
            this.currentPage += 1
          }
          this.loading = false
        })
        .catch(() => {
          this.loading = false
        })
    },
    selectStatus(key) {
      if (this.activeStatus === key) return
      this.activeStatus = key
      this.resetAndLoad()
    },
    statusText(status) {
      return orderStatusText(status)
    },
    formatTime(time) {
      return time ? String(time).replace('T', ' ').slice(0, 16) : ''
    },
    firstItem(item) {
      const items = item.items || []
      return items[0] || {}
    },
    firstName(item) {
      return this.firstItem(item).productName || '绿纤商品'
    },
    firstImg(item) {
      return this.firstItem(item).productImg || ''
    },
    itemsSummary(item) {
      const items = item.items || []
      if (!items.length) return ''
      const first = items[0]
      const qty = items.reduce((sum, it) => sum + (it.quantity || 0), 0)
      const skuText = first.skuName ? `${first.skuName} · ` : ''
      return items.length > 1 ? `${skuText}共 ${qty} 件` : `${skuText}× ${first.quantity}`
    },
    shortLabel(title) {
      return String(title || '绿纤').replace(/\s+/g, '').slice(0, 2)
    },
    goBack() {
      uni.navigateBack({ fail: () => uni.redirectTo({ url: '/pages/profile/profile' }) })
    },
    goDetail(item) {
      uni.navigateTo({ url: `/packageProfile/order-detail/order-detail?id=${item.id}` })
    }
  }
}
</script>

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

.status-scroll {
  white-space: nowrap;
  margin-bottom: 24rpx;
}

.status-row {
  display: inline-flex;
  padding-bottom: 6rpx;
}

.status-chip {
  @include selection-chip;
  min-width: 138rpx;
  padding: 22rpx 28rpx;
  margin-right: 16rpx;
  text-align: center;
  font-size: 24rpx;
  color: $text-secondary;
  transition: transform 240ms ease, box-shadow 240ms ease, background 240ms ease, color 240ms ease;
}

.status-chip:active {
  transform: translateY(-2rpx) scale(0.98);
}

.status-chip.active {
  @include selection-chip-active;
}

.order-card {
  padding: 30rpx;
  margin-bottom: 22rpx;
  transition: transform 340ms cubic-bezier(0.22, 1, 0.36, 1), box-shadow 340ms ease;
  animation: lfRevealUp 760ms cubic-bezier(0.22, 1, 0.36, 1) both;
}

.order-card:nth-of-type(1) { animation-delay: 0.08s; }
.order-card:nth-of-type(2) { animation-delay: 0.16s; }
.order-card:nth-of-type(3) { animation-delay: 0.24s; }
.order-card:nth-of-type(4) { animation-delay: 0.32s; }

.order-card:active {
  transform: translateY(-4rpx);
}

.order-top,
.order-main {
  display: flex;
}

.order-top {
  justify-content: space-between;
  align-items: center;
}

.order-status {
  @include chip-soft;
}

.order-time {
  font-size: 22rpx;
  color: $text-tertiary;
}

.order-main {
  margin-top: 22rpx;
}

.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);
  animation: lfPulseSoft 5.2s ease-in-out infinite;
}

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

.order-art::before {
  content: '';
  position: absolute;
  right: -30rpx;
  top: -30rpx;
  width: 120rpx;
  height: 120rpx;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(210, 191, 156, 0.22), rgba(210, 191, 156, 0));
  animation: lfFloatSoft 9.8s ease-in-out infinite;
}

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

.order-name,
.order-sku,
.order-store,
.order-amount {
  display: block;
  margin-top: 14rpx;
}

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

.order-sku,
.order-store {
  font-size: 24rpx;
  color: $text-secondary;
}

.order-amount {
  font-size: 34rpx;
  color: $brand-primary-deep;
  font-weight: 700;
}
</style>