my-appointments.vue 6.85 KB
<template>
  <view class="page-shell appt-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 list" :key="item.id" class="appt-card glass-card">
        <view class="appt-top">
          <text class="appt-status" :class="'st-' + item.status">{{ statusText(item.status) }}</text>
          <text class="appt-time">{{ formatTime(item.createTime) }}</text>
        </view>
        <view class="appt-body">
          <view class="appt-row">
            <text class="appt-label">预约门店</text>
            <text class="appt-value">{{ item.storeName || '无' }}</text>
          </view>
          <view class="appt-row">
            <text class="appt-label">期望到店</text>
            <text class="appt-value">{{ formatDate(item.appointDate) }} {{ item.timeSlot || '' }}</text>
          </view>
          <view class="appt-row">
            <text class="appt-label">需求</text>
            <text class="appt-value">{{ item.projectDesc || '无' }}</text>
          </view>
          <view class="appt-row">
            <text class="appt-label">联系电话</text>
            <text class="appt-value">{{ item.memberPhone || '无' }}</text>
          </view>
          <view v-if="item.status === 2 && item.confirmRemark" class="appt-row">
            <text class="appt-label">门店回复</text>
            <text class="appt-value">{{ item.confirmRemark }}</text>
          </view>
        </view>
        <view v-if="item.status === 1" class="appt-foot">
          <view class="ghost-button" @tap="cancel(item)">取消预约</view>
        </view>
      </view>

      <view v-if="!loading && !list.length" class="empty-tip">暂无预约记录</view>
      <view v-if="loading" class="empty-tip">正在加载…</view>
      <view class="safe-bottom-space"></view>
    </view>
  </view>
</template>

<script>
import { getMyAppointments, cancelMyAppointment } from '@/api/mall'

export default {
  data() {
    return {
      list: [],
      activeStatus: 0,
      statuses: [
        { key: 0, label: '全部' },
        { key: 1, label: '待确认' },
        { key: 2, label: '已确认' },
        { key: 3, label: '已取消' }
      ],
      currentPage: 1,
      pageSize: 10,
      total: 0,
      loading: false,
      finished: false
    }
  },
  onLoad() {
    this.resetAndLoad()
  },
  onShow() {
    // 从预约页提交后返回时刷新
    if (this._loadedOnce) this.resetAndLoad()
    this._loadedOnce = true
  },
  onReachBottom() {
    this.loadList()
  },
  methods: {
    resetAndLoad() {
      this.currentPage = 1
      this.list = []
      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.Status = this.activeStatus
      getMyAppointments(params)
        .then((res) => {
          const rows = (res && res.list) || []
          this.list = this.list.concat(rows)
          const pagination = (res && res.pagination) || {}
          this.total = pagination.total || this.list.length
          if (rows.length < this.pageSize || this.list.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()
    },
    cancel(item) {
      uni.showModal({
        title: '取消预约',
        content: '确定要取消该预约吗?',
        success: (r) => {
          if (!r.confirm) return
          cancelMyAppointment(item.id)
            .then(() => {
              uni.showToast({ title: '已取消', icon: 'none' })
              this.resetAndLoad()
            })
            .catch(() => {})
        }
      })
    },
    statusText(status) {
      return { 1: '待确认', 2: '已确认', 3: '已取消' }[Number(status)] || '无'
    },
    formatTime(time) {
      return time ? String(time).replace('T', ' ').slice(0, 16) : ''
    },
    formatDate(val) {
      if (!val) return '无'
      return String(val).replace('T', ' ').slice(0, 10)
    },
    goBack() {
      uni.navigateBack({ fail: () => uni.reLaunch({ url: '/pages/home/home' }) })
    }
  }
}
</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;
}

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

.appt-card {
  padding: 30rpx;
  margin-bottom: 22rpx;
  animation: lfRevealUp 760ms cubic-bezier(0.22, 1, 0.36, 1) both;
}

.appt-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 18rpx;
}

.appt-status {
  @include chip-soft;
  font-weight: 600;
}

.appt-status.st-1 {
  color: #b7791f;
}

.appt-status.st-2 {
  color: $brand-primary-deep;
}

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

.appt-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  padding: 12rpx 0;
  border-bottom: 1rpx solid rgba(100, 140, 118, 0.08);
}

.appt-row:last-child {
  border-bottom: 0;
}

.appt-label {
  font-size: 24rpx;
  color: $text-secondary;
  flex-shrink: 0;
}

.appt-value {
  width: 66%;
  text-align: right;
  font-size: 26rpx;
  line-height: 1.6;
  color: $text-primary;
}

.appt-foot {
  display: flex;
  justify-content: flex-end;
  margin-top: 18rpx;
}

.ghost-button {
  padding: 14rpx 32rpx;
  border-radius: 999rpx;
  font-size: 24rpx;
  color: $text-secondary;
  background: rgba(255, 255, 255, 0.8);
  border: 1rpx solid rgba(210, 191, 156, 0.24);
}

.empty-tip {
  margin-top: 40rpx;
  text-align: center;
  font-size: 24rpx;
  color: $text-tertiary;
}
</style>