store-detail.vue 5.48 KB
<template>
  <view class="page-shell store-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="detail-hero glass-card">
        <image v-if="store.cover" class="hero-image" :src="store.cover" mode="aspectFill" />
        <view class="hero-overlay"></view>
        <view class="hero-content">
          <text class="hero-kicker">{{ store.city || '门店' }}</text>
          <text class="hero-title">{{ store.name }}</text>
          <text v-if="store.description" class="hero-desc">{{ store.description }}</text>
        </view>
      </view>

      <view class="subpage-card">
        <view class="info-list">
          <view class="info-row">
            <text class="info-label">地址</text>
            <text class="info-value">{{ store.address || '无' }}</text>
          </view>
          <view class="info-row">
            <text class="info-label">营业时间</text>
            <text class="info-value">{{ store.hours || '无' }}</text>
          </view>
          <view class="info-row">
            <text class="info-label">联系电话</text>
            <text class="info-value">{{ store.phone || '无' }}</text>
          </view>
          <view v-if="store.city" class="info-row">
            <text class="info-label">所在城市</text>
            <text class="info-value">{{ store.city }}</text>
          </view>
        </view>
      </view>

      <view v-if="store.description" class="subpage-card">
        <view class="section-head">
          <text class="section-title">门店介绍</text>
        </view>
        <view class="soft-panel note-panel">
          <text class="note-desc">{{ store.description }}</text>
        </view>
      </view>

      <view class="cta-row">
        <view class="secondary-button" @tap="openMap">导航前往</view>
        <view v-if="store.phone" class="secondary-button" @tap="callStore">联系门店</view>
        <view class="primary-button" @tap="bookStore">预约到店</view>
      </view>
    </view>
  </view>
</template>

<script>
import { getPickupStores } from '@/api/mall'
import { normalizeStore } from '@/utils/store'

export default {
  data() {
    return {
      storeId: '',
      store: {
        id: '',
        name: '门店详情',
        city: '',
        address: '',
        phone: '',
        hours: '',
        description: '',
        cover: '',
        latitude: null,
        longitude: null
      }
    }
  },
  onLoad(options) {
    this.storeId = options && options.id ? String(options.id) : ''
    this.loadStore()
  },
  methods: {
    loadStore() {
      getPickupStores({})
        .then((list) => {
          const rows = Array.isArray(list) ? list : []
          const found = rows.find(item => String(item.id) === this.storeId) || rows[0]
          if (found) {
            this.store = normalizeStore(found)
          }
        })
        .catch(() => {})
    },
    goBack() {
      uni.navigateBack({
        fail: () => {
          uni.redirectTo({ url: '/packageBooking/stores/stores' })
        }
      })
    },
    callStore() {
      if (!this.store.phone) return
      uni.makePhoneCall({ phoneNumber: String(this.store.phone) })
    },
    openMap() {
      if (this.store.latitude === null || this.store.longitude === null) {
        uni.showToast({ title: '该门店暂无坐标,无法导航', icon: 'none' })
        return
      }
      uni.openLocation({
        latitude: this.store.latitude,
        longitude: this.store.longitude,
        name: this.store.name,
        address: this.store.address || ''
      })
    },
    bookStore() {
      uni.redirectTo({ url: `/pages/booking/booking?storeId=${this.store.id}` })
    }
  }
}
</script>

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

.detail-hero {
  position: relative;
  overflow: hidden;
  min-height: 360rpx;
  margin-bottom: 28rpx;
  transition: transform 360ms cubic-bezier(0.22, 1, 0.36, 1), box-shadow 360ms ease;
}

.detail-hero:active {
  transform: translateY(-4rpx);
}

.hero-image,
.hero-overlay {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.hero-overlay {
  background: linear-gradient(180deg, rgba(36, 74, 61, 0.08), rgba(36, 74, 61, 0.34));
}

.hero-image {
  transition: transform 1.2s ease;
}

.detail-hero:active .hero-image {
  transform: scale(1.05);
}

.detail-hero::after {
  content: '';
  position: absolute;
  right: -80rpx;
  top: -40rpx;
  width: 260rpx;
  height: 260rpx;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 255, 255, 0.26), rgba(255, 255, 255, 0));
  animation: lfFloatSoft 11.6s ease-in-out infinite;
}

.hero-content {
  position: relative;
  z-index: 2;
  padding: 210rpx 34rpx 34rpx;
}

.hero-kicker {
  color: rgba(255, 255, 255, 0.86);
}

.hero-title {
  color: #fff;
}

.hero-desc {
  color: rgba(255, 255, 255, 0.82);
}

.note-panel {
  margin-top: 26rpx;
}

.note-title {
  display: block;
  font-size: 28rpx;
  font-weight: 600;
  color: $brand-primary-deep;
}

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