product-detail.vue 7.31 KB
<template>
  <view class="page-shell product-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="product-hero glass-card">
        <image class="product-image" :src="product.image" mode="aspectFill" />
        <view class="product-image-mask"></view>
        <view class="product-image-glow"></view>
        <text class="product-hero-badge">{{ product.badge }}</text>
      </view>

      <view class="subpage-card">
        <text class="product-badge">{{ product.badge }}</text>
        <text class="product-title">{{ product.name }}</text>
        <text class="product-price">¥{{ product.price }}</text>
        <text class="product-desc">{{ product.desc }}</text>
        <view class="meta-grid">
          <text v-for="tag in product.tags" :key="tag" class="meta-pill">{{ tag }}</text>
        </view>
      </view>

      <view class="subpage-card">
        <view class="section-head">
          <text class="section-title">产品说明</text>
          <text class="section-link">居家护理</text>
        </view>
        <text class="detail-copy">{{ product.detail }}</text>
        <view class="soft-panel spec-panel">
          <text v-for="item in product.specs" :key="item" class="spec-item">{{ item }}</text>
        </view>
      </view>

      <view class="subpage-card">
        <view class="section-head">
          <text class="section-title">购买流程</text>
          <text class="section-link">门店自提</text>
        </view>
        <view class="flow-list">
          <view v-for="(item, index) in purchaseSteps" :key="item.title" class="flow-item">
            <view class="flow-index">{{ index + 1 }}</view>
            <view class="flow-copy">
              <text class="flow-title">{{ item.title }}</text>
              <text class="flow-desc">{{ item.desc }}</text>
            </view>
          </view>
        </view>
        <view class="soft-panel flow-note">
          <text class="flow-note-text">支付完成后会自动生成核销二维码,用户到店出示二维码,门店核销后即可完成自提。</text>
        </view>
      </view>

      <view class="cta-row">
        <view class="secondary-button" @tap="addWishlist">{{ isWishlisted ? '查看心愿单' : '加入心愿单' }}</view>
        <view class="primary-button" @tap="goOrder">立即购买</view>
      </view>
    </view>
  </view>
</template>

<script>
import { findProductById } from '@/packageMall/data/products'

export default {
  data() {
    return {
      product: findProductById(1),
      isWishlisted: false,
      purchaseSteps: [
        { title: '下单支付', desc: '确认商品后完成支付,订单会立即进入自提流程。' },
        { title: '生成核销码', desc: '支付成功后,小程序会自动生成专属核销二维码。' },
        { title: '到店出示', desc: '任意时间前往任意门店,出示二维码即可。' },
        { title: '门店核销', desc: '门店完成扫码核销,订单状态会同步更新。' },
        { title: '完成自提', desc: '核销完成后即可当场自提商品,流程更轻更快。' }
      ]
    }
  },
  onLoad(options) {
    this.product = findProductById(options.id)
    this.loadWishlistState()
  },
  onShow() {
    this.loadWishlistState()
  },
  methods: {
    loadWishlistState() {
      const list = uni.getStorageSync('lf_wishlist') || []
      this.isWishlisted = list.indexOf(this.product.id) > -1
    },
    goBack() {
      uni.navigateBack({
        fail: () => {
          uni.redirectTo({ url: '/packageMall/main/main' })
        }
      })
    },
    addWishlist() {
      if (this.isWishlisted) {
        uni.navigateTo({ url: '/packageMall/wishlist/wishlist' })
        return
      }
      const list = uni.getStorageSync('lf_wishlist') || []
      if (list.indexOf(this.product.id) === -1) {
        list.push(this.product.id)
        uni.setStorageSync('lf_wishlist', list)
      }
      this.isWishlisted = true
      uni.showToast({ title: '已加入心愿单', icon: 'none' })
      setTimeout(() => {
        uni.navigateTo({ url: '/packageMall/wishlist/wishlist' })
      }, 280)
    },
    goOrder() {
      uni.navigateTo({ url: `/packageMall/order-confirm/order-confirm?productId=${this.product.id}` })
    }
  }
}
</script>

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

.product-hero {
  position: relative;
  padding: 0;
  overflow: hidden;
  margin-bottom: 28rpx;
  min-height: 620rpx;
}

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

.product-image {
  width: 100%;
  height: 620rpx;
  display: block;
  transition: transform 1.2s ease;
}

.product-image-mask,
.product-image-glow,
.product-hero-badge {
  position: absolute;
}

.product-image-mask {
  inset: 0;
  background: linear-gradient(180deg, rgba(32, 53, 45, 0.04), rgba(32, 53, 45, 0.12) 54%, rgba(32, 53, 45, 0.3) 100%);
}

.product-image-glow {
  right: -80rpx;
  top: -20rpx;
  width: 260rpx;
  height: 260rpx;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0));
  animation: lfFloatSoft 11.8s ease-in-out infinite;
}

.product-hero-badge {
  left: 28rpx;
  top: 28rpx;
  z-index: 2;
  @include chip-inverse;
}

.product-badge,
.product-price {
  position: relative;
  z-index: 2;
}

.product-badge {
  @include chip-soft;
  transition: transform 240ms ease;
}

.product-badge:active {
  transform: scale(0.97);
}

.product-title {
  display: block;
  margin-top: 18rpx;
  font-size: 40rpx;
  line-height: 1.4;
  font-weight: 700;
}

.product-price {
  display: block;
  margin-top: 18rpx;
  font-size: 46rpx;
  font-weight: 700;
  color: $brand-primary-deep;
}

.product-desc,
.detail-copy {
  display: block;
  margin-top: 18rpx;
  font-size: 24rpx;
  line-height: 1.8;
  color: $text-secondary;
}

.spec-panel {
  margin-top: 24rpx;
}

.spec-item {
  display: block;
  font-size: 24rpx;
  line-height: 1.9;
  color: $text-primary;
  transition: transform 220ms ease;
}

.spec-item:active {
  transform: translateX(6rpx);
}

.flow-list {
  margin-top: 4rpx;
}

.flow-item {
  display: flex;
  align-items: flex-start;
  padding: 18rpx 0;
  border-bottom: 1rpx solid rgba(100, 140, 118, 0.08);
}

.flow-item:last-child {
  border-bottom: 0;
  padding-bottom: 0;
}

.flow-index {
  width: 52rpx;
  height: 52rpx;
  margin-right: 18rpx;
  flex-shrink: 0;
  border-radius: 18rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 24rpx;
  font-weight: 700;
  color: $brand-primary-deep;
  background: rgba(210, 191, 156, 0.14);
}

.flow-copy {
  flex: 1;
}

.flow-title,
.flow-desc,
.flow-note-text {
  display: block;
}

.flow-title {
  font-size: 28rpx;
  font-weight: 600;
  color: $text-primary;
}

.flow-desc {
  margin-top: 10rpx;
  font-size: 23rpx;
  line-height: 1.8;
  color: $text-secondary;
}

.flow-note {
  margin-top: 24rpx;
}

.flow-note-text {
  font-size: 23rpx;
  line-height: 1.8;
  color: $brand-primary-deep;
}
</style>