<!-- 消费者端 - 商品详情页 -->
<template>
  <view class="product-detail-page">
    <scroll-view class="page-scroll" direction="vertical">
      <view class="hero-section">
        <swiper class="hero-swiper" :indicator-dots="false" :autoplay="false" @change="onSwiperChange">
          <swiper-item v-for="(image, index) in getProductImages()" :key="index">
            <image :src="image" class="hero-image" mode="aspectFill" @error="handleProductImageError(index)" @click="previewImage(index)" />
          </swiper-item>
        </swiper>
        <view class="hero-indicator">{{ currentImageIndex + 1 }}/{{ getProductImages().length }}</view>

        <view class="floating-nav left-nav" :style="getNavLeftStyle()">
          <view class="floating-btn" @click="goBack">
            <text class="floating-btn-text">‹</text>
          </view>
          <view class="floating-btn" @click="goToSearch">
            <text class="floating-btn-text">⌕</text>
          </view>
        </view>

        <view class="floating-nav right-nav" :style="getNavRightStyle()">
          <view class="floating-btn" @click="openActionPanel">
            <text class="floating-btn-text">...</text>
          </view>
        </view>
      </view>

      <view class="content-wrap">
        <view class="info-card price-sales-card">
          <view v-if="memberDiscount > 0 && memberPrice > 0 && memberPrice < product.price" class="vip-row">
            <view class="vip-badge">
              <text class="vip-badge-text">VIP</text>
            </view>
            <text class="vip-discount-text">{{ memberDiscount }}折专享</text>
          </view>

          <view class="price-sales-row">
            <view class="price-main-wrap">
              <text class="price-symbol">¥</text>
              <text class="price-value">{{ getDisplayPriceText() }}</text>
              <text v-if="getOriginalPriceText() != ''" class="price-original">¥{{ getOriginalPriceText() }}</text>
            </view>
            <text class="sales-inline">{{ getDisplaySalesText() }}</text>
          </view>

          <view class="price-sub-row">
            <text v-if="memberPrice > 0 && memberPrice < product.price" class="save-text">已省 ¥{{ (product.price - memberPrice).toFixed(2) }}</text>
            <text class="stock-text">{{ getDisplayStockText() }}</text>
          </view>
        </view>

        <view v-if="coupons.length > 0" class="info-card coupon-card" @click="showCouponModal">
          <text class="coupon-label">优惠</text>
          <view class="coupon-tags-wrap">
            <text v-for="(coupon, index) in coupons.slice(0, 3)" :key="'coupon-' + index" class="coupon-chip">{{ coupon.name }}</text>
          </view>
          <text class="coupon-arrow">领券 ></text>
        </view>

        <view class="info-card title-action-card">
          <view v-if="getCardTags().length > 0" class="title-tag-row">
            <text v-for="(tag, index) in getCardTags()" :key="'tag-' + index" class="detail-card-tag">{{ tag }}</text>
          </view>

          <view class="title-body-row">
            <view class="title-main-wrap">
              <text class="product-title">{{ getDisplayTitle() }}</text>
              <text v-if="getHighlightText() != ''" class="product-highlight-text">{{ getHighlightText() }}</text>
              <text v-else-if="getShortDescriptionText() != ''" class="product-highlight-text">{{ getShortDescriptionText() }}</text>
            </view>

            <view class="title-side-actions">
              <view class="side-action-btn" @click="toggleFavorite">
                <image :src="isFavorite ? '/static/icons/favorite-active.png' : '/static/icons/favorite.png'" class="side-action-icon" />
                <text class="side-action-text">{{ isFavorite ? '已收藏' : '收藏' }}</text>
              </view>
              <view class="side-action-btn" @click="showSharePopup">
                <text class="side-action-symbol">↗</text>
                <text class="side-action-text">分享</text>
              </view>
            </view>
          </view>

          <view v-if="getServiceTags().length > 0" class="detail-service-row">
            <text v-for="(tag, index) in getServiceTags()" :key="'service-tag-' + index" class="detail-service-tag">{{ tag }}</text>
          </view>

          <view class="meta-link-row">
            <text class="meta-link-text" @click="showSpecModal">已选: {{ getSelectedSpecLabel() }}</text>
            <text class="meta-link-divider">|</text>
            <text class="meta-link-text" @click="showParamsModal">参数: {{ getParamsSummary() }}</text>
          </view>

          <view v-if="product.selling_points != null && product.selling_points.length > 0" class="selling-points-panel">
            <view v-for="(point, index) in product.selling_points" :key="'selling-point-' + index" class="selling-point-item">
              <text class="selling-point-dot"></text>
              <text class="selling-point-text">{{ point }}</text>
            </view>
          </view>
        </view>

        <view class="info-card guarantee-card" @click="showGuaranteeIntro">
          <view class="card-header-row">
            <text class="card-title">商品保障</text>
            <text class="card-link-text">查看说明 ></text>
          </view>
          <view class="guarantee-list">
            <view v-for="item in getVisibleGuarantees()" :key="item.id" class="guarantee-chip">
              <text class="guarantee-dot"></text>
              <text class="guarantee-text">{{ item.title }}</text>
            </view>
          </view>
        </view>

        <view class="info-card shop-card" @click="goToShop">
          <image :src="getMerchantLogo()" class="shop-logo" @error="handleShopLogoError" />
          <view class="shop-details">
            <text class="shop-name">{{ getMerchantName() }}</text>
            <view class="shop-stats-row">
              <text class="shop-stat-text">评分 {{ formatRating(merchant.rating) }}</text>
              <text class="shop-stat-text">销量 {{ merchant.total_sales }}</text>
            </view>
            <text class="shop-desc">{{ getMerchantDescription() }}</text>
          </view>
          <text class="enter-shop">进店 ></text>
        </view>

        <view class="info-card review-card">
          <view class="card-header-row">
            <text class="card-title">商品评论</text>
            <text class="card-link-text" @click="viewAllReviews">查看全部 ></text>
          </view>

          <view class="review-summary-row">
            <text class="review-score">{{ formatRating(reviewRating) }}</text>
            <text class="review-score-label">综合评分</text>
            <text class="review-count">{{ getReviewCountLabel() }}</text>
          </view>

          <view v-for="review in reviewPreview" :key="review.id" class="review-item">
            <view class="review-meta-row">
              <text class="review-user">{{ review.userName }}</text>
              <text class="review-stars">{{ getReviewStars(review.rating) }}</text>
            </view>
            <text class="review-content">{{ review.content }}</text>
            <view v-if="review.images.length > 0" class="review-image-row">
              <image v-for="(img, index) in review.images.slice(0, 3)" :key="review.id + '-img-' + index" :src="img" class="review-image" mode="aspectFill" @click="previewReviewImages(review.images, index)" />
            </view>
            <text class="review-date">{{ formatDate(review.createdAt) }}</text>
          </view>
        </view>

        <view class="info-card detail-card">
          <view class="card-header-row">
            <text class="card-title">商品详情</text>
            <text class="card-link-text" @click="showParamsModal">商品参数 ></text>
          </view>

          <view v-if="product.usage != null && product.usage != ''" class="detail-text-block">
            <text class="detail-block-title">功能主治</text>
            <text class="detail-block-text">{{ product.usage }}</text>
          </view>

          <view class="detail-images" v-if="getDetailImages().length > 0">
            <image v-for="(img, index) in getDetailImages()" :key="'detail-' + index" :src="img" class="detail-image" mode="widthFix" @error="handleDetailImageError(index)" @click="previewDetailImage(index)" />
          </view>

          <text v-else-if="getDetailDescription() != ''" class="description-text">{{ getDetailDescription() }}</text>

          <view v-else class="empty-detail-state">
            <text class="empty-detail-text">暂无商品详情</text>
          </view>
        </view>
      </view>
    </scroll-view>

    <view class="bottom-actions">
      <view class="bottom-left-actions">
        <view class="bottom-action-item" @click="handleContactService">
          <image src="/static/icons/customer-service.png" class="bottom-action-icon" />
          <text class="bottom-action-text">客服</text>
        </view>
        <view class="bottom-action-item cart-action-item" @click="goToCart">
          <view class="cart-icon-wrap">
            <image src="/static/tabbar/cart.png" class="bottom-action-icon" />
            <text v-if="cartBadgeCount > 0" class="cart-badge">{{ getCartBadgeText() }}</text>
          </view>
          <text class="bottom-action-text">购物车</text>
        </view>
        <view class="bottom-action-item shop-action-item" @click="goToShop">
          <text class="bottom-action-shop-icon">店</text>
          <text class="bottom-action-text">店铺</text>
        </view>
      </view>

      <view class="btn-group">
        <button class="cart-btn" @click="addToCart">加入购物车</button>
        <button class="buy-btn" @click="buyNow">立即购买</button>
      </view>
    </view>

    <view v-if="showSpec" class="spec-modal" @click="hideSpecModal">
      <view class="spec-content" @click.stop>
        <view v-if="productSkus.length > 0 && selectedSkuId == ''" class="spec-error-tip">
          <text class="error-tip-text">请选择规格</text>
        </view>
        <view class="spec-header-jd">
          <image :src="getSelectedSkuImage()" class="spec-product-img" mode="aspectFill" />
          <view class="spec-info-jd">
            <view class="spec-price-row">
              <text class="price-symbol">¥</text>
              <text class="price-value">{{ getSelectedSkuPrice() }}</text>
            </view>
            <text class="spec-stock-jd">库存: {{ getSelectedSkuStock() }}件</text>
            <text class="spec-choosed-jd">已选: {{ getSelectedSpecLabel() }}</text>
          </view>
          <text class="close-btn-jd" @click="hideSpecModal">×</text>
        </view>

        <scroll-view class="spec-list-jd" direction="vertical">
          <view v-if="productSkus.length > 0" class="spec-group">
            <text class="group-title">规格</text>
            <view class="group-tags">
              <view v-for="sku in productSkus" :key="sku.id" class="spec-tag" :class="{ active: selectedSkuId === sku.id }" @click="selectSku(sku)">
                <text class="tag-text">{{ getSkuSpecText(sku) }}</text>
              </view>
            </view>
          </view>

          <view class="spec-quantity-row">
            <text class="group-title">数量</text>
            <view class="quantity-selector-jd">
              <view class="q-btn" @click="decreaseQuantity">
                <text class="q-btn-text">-</text>
              </view>
              <input class="q-input" type="number" :value="quantity.toString()" @input="validateQuantity" />
              <view class="q-btn" @click="increaseQuantity">
                <text class="q-btn-text">+</text>
              </view>
            </view>
          </view>
        </scroll-view>

        <view class="spec-footer-jd">
          <button class="footer-btn cart" @click="addToCart">加入购物车</button>
          <button class="footer-btn buy" @click="buyNow">立即购买</button>
        </view>
      </view>
    </view>

    <view v-if="showParams" class="params-modal" @click="hideParamsModal">
      <view class="params-content" @click.stop>
        <view class="params-header">
          <text class="params-title">商品参数</text>
          <text class="close-btn" @click="hideParamsModal">×</text>
        </view>
        <scroll-view class="params-list" direction="vertical">
          <view class="params-item" v-if="product.specification">
            <text class="params-label">规格</text>
            <text class="params-value">{{ product.specification }}</text>
          </view>
          <view class="params-item" v-if="product.usage">
            <text class="params-label">功能主治</text>
            <text class="params-value">{{ product.usage }}</text>
          </view>
          <view class="params-item" v-if="product.side_effects">
            <text class="params-label">副作用</text>
            <text class="params-value">{{ product.side_effects }}</text>
          </view>
          <view class="params-item" v-if="product.precautions">
            <text class="params-label">注意事项</text>
            <text class="params-value">{{ product.precautions }}</text>
          </view>
          <view class="params-item" v-if="product.expiry_date">
            <text class="params-label">有效期</text>
            <text class="params-value">{{ product.expiry_date }}</text>
          </view>
          <view class="params-item" v-if="product.storage_conditions">
            <text class="params-label">储存条件</text>
            <text class="params-value">{{ product.storage_conditions }}</text>
          </view>
          <view class="params-item" v-if="product.approval_number">
            <text class="params-label">批准文号</text>
            <text class="params-value">{{ product.approval_number }}</text>
          </view>
          <view class="params-item" v-if="product.tags != null && product.tags.length > 0">
            <text class="params-label">标签</text>
            <text class="params-value">{{ product.tags!.join(', ') }}</text>
          </view>
        </scroll-view>
      </view>
    </view>

    <view v-if="showCoupons" class="popup-mask" @click="hideCouponModal">
      <view class="popup-content" @click.stop>
        <view class="popup-header">
          <text class="popup-title">优惠券</text>
          <text class="close-btn" @click="hideCouponModal">×</text>
        </view>
        <scroll-view direction="vertical" class="coupon-list-scroll">
          <view v-for="coupon in coupons" :key="coupon.id" class="coupon-item">
            <view class="coupon-left">
              <text class="coupon-amount"><text class="symbol">¥</text>{{ coupon.discount_value }}</text>
              <text class="coupon-cond">满{{ coupon.min_order_amount }}可用</text>
            </view>
            <view class="coupon-right">
              <view class="coupon-info-text">
                <text class="coupon-name">{{ coupon.name }}</text>
                <text class="coupon-time">{{ formatDate(coupon.start_time) }}-{{ formatDate(coupon.end_time) }}</text>
              </view>
              <button class="coupon-btn" @click="claimCoupon(coupon)">领取</button>
            </view>
          </view>
        </scroll-view>
      </view>
    </view>

    <view v-if="showShare" class="share-popup-mask" @click="hideSharePopup">
      <view class="share-popup-content" @click.stop>
        <view class="share-popup-header">
          <text class="share-popup-title">分享至</text>
          <text class="close-btn" @click="hideSharePopup">×</text>
        </view>

        <view class="share-options">
          <view class="share-option" @click="shareToWechat">
            <view class="share-icon-wrapper wechat">
              <text class="share-option-icon">微</text>
            </view>
            <text class="share-option-text">微信好友</text>
          </view>
          <view class="share-option" @click="shareToMoments">
            <view class="share-icon-wrapper moments">
              <text class="share-option-icon">圈</text>
            </view>
            <text class="share-option-text">朋友圈</text>
          </view>
          <view class="share-option" @click="shareToQQ">
            <view class="share-icon-wrapper qq">
              <text class="share-option-icon">Q</text>
            </view>
            <text class="share-option-text">QQ</text>
          </view>
          <view class="share-option" @click="copyLink">
            <view class="share-icon-wrapper link">
              <text class="share-option-icon">链</text>
            </view>
            <text class="share-option-text">复制链接</text>
          </view>
          <view class="share-option" @click="saveImage">
            <view class="share-icon-wrapper image">
              <text class="share-option-icon">图</text>
            </view>
            <text class="share-option-text">保存图片</text>
          </view>
          <view class="share-option" @click="generatePoster">
            <view class="share-icon-wrapper poster">
              <text class="share-option-icon">报</text>
            </view>
            <text class="share-option-text">生成海报</text>
          </view>
        </view>

        <view class="share-cancel-btn" @click="hideSharePopup">
          <text class="cancel-text">取消</text>
        </view>
      </view>
    </view>
  </view>
</template>

<script>
import { ProductType, MerchantType, ProductSkuType, CouponTemplateType, FootprintItemType, ProductGuaranteeType, ProductReviewPreviewType } from '@/types/mall-types.uts'
import { supabaseService } from '@/utils/supabaseService.uts'
import { goToLogin } from '@/utils/utils.uts'

export default {
  data() {
    return {
      defaultImage: '/static/images/default.png',
      product: {
        id: '',
        merchant_id: '',
        category_id: '',
        name: '',
        short_title: '',
        subtitle: '',
        description: '',
        images: [] as Array<string>,
        price: 0,
        original_price: 0,
        stock: 0,
        sales: 0,
        status: 0,
        created_at: '',
        card_tags: [] as Array<string>,
        service_tags: [] as Array<string>,
        selling_points: [] as Array<string>,
        display_sales_text: '',
        detail_images: [] as Array<string>,
        guarantees: [] as Array<UTSJSONObject>
      } as ProductType,
      merchant: {
        id: '',
        user_id: '',
        shop_name: '',
        shop_logo: '',
        shop_banner: '',
        shop_description: '',
        contact_name: '',
        contact_phone: '',
        shop_status: 0,
        rating: 0,
        total_sales: 0,
        created_at: ''
      } as MerchantType,
      productSkus: [] as Array<ProductSkuType>,
      currentImageIndex: 0,
      showSpec: false,
      selectedSkuId: '',
      selectedSpec: '',
      quantity: 1 as number,
      isFavorite: false,
      showParams: false,
      coupons: [] as Array<CouponTemplateType>,
      showCoupons: false,
      showShare: false,
      memberPrice: 0 as number,
      memberDiscount: 0 as number,
      memberLevelName: '' as string,
      statusBarHeight: 0 as number,
      capsuleTop: 0 as number,
      capsuleHeight: 32 as number,
      navButtonTop: 0 as number,
      navButtonHeight: 32 as number,
      navRightReserve: 16 as number,
      cartBadgeCount: 0 as number,
      reviewRating: 5 as number,
      reviewCount: 0 as number,
      reviewPreview: [] as Array<ProductReviewPreviewType>,
      reviewLoading: false,
      productGuarantees: [] as Array<ProductGuaranteeType>,
      detailImages: [] as Array<string>
    }
  },
  onLoad(options: any) {
    this.initImmersiveLayout()

    const opts = options as UTSJSONObject
    const productId = (opts['productId'] ?? opts['id']) as string | null
    const priceStr = opts['price'] as string | null
    const productPrice = priceStr != null ? parseFloat(priceStr) : null
    const originalPriceStr = opts['originalPrice'] as string | null
    const productOriginalPrice = originalPriceStr != null ? parseFloat(originalPriceStr) : null

    let productName = opts['name'] as string | null
    if (productName != null) {
      try {
        productName = decodeURIComponent(productName)
      } catch (e) {
        console.warn('ProductName decode failed, using original:', productName)
      }
    }

    let productImage = opts['image'] as string | null
    if (productImage != null) {
      try {
        productImage = decodeURIComponent(productImage)
      } catch (e) {
        console.warn('ProductImage decode failed, using original:', productImage)
      }
    }

    this.productGuarantees = this.getDefaultGuarantees()
    this.reviewPreview = this.getMockReviews()

    if (productId != null) {
      this.loadProductDetail(productId, {
        price: productPrice,
        originalPrice: productOriginalPrice,
        name: productName,
        image: productImage
      })
      this.checkFavoriteStatus(productId)
      this.saveFootprint(productId)
    }
  },
  onShow() {
    this.refreshCartBadgeCount()
  },
  computed: {
    displayPrice(): number {
      if (this.selectedSkuId != null && this.selectedSkuId !== '') {
        const sku = this.productSkus.find(s => s.id === this.selectedSkuId)
        if (sku != null) return sku.price
      }
      return this.product.price
    }
  },
  methods: {
    readNumericField(item: any, key: string): number {
      if (item == null) return 0
      try {
        if (item instanceof UTSJSONObject) {
          return item.getNumber(key) ?? 0
        }
      } catch (error) {}

      try {
        const rawObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
        const rawValue = rawObj.get(key)
        if (rawValue == null) return 0
        if (typeof rawValue == 'number') {
          return rawValue as number
        }
        const textValue = rawValue.toString()
        if (textValue == '') return 0
        const parsedValue = parseInt(textValue)
        return isNaN(parsedValue) ? 0 : parsedValue
      } catch (error) {
        return 0
      }
    },

    extractCartItemQuantity(item: any): number {
      if (item == null) return 1
      try {
        let quantity = this.readNumericField(item, 'quantity')
        if (quantity <= 0) {
          quantity = this.readNumericField(item, 'count')
        }
        if (quantity <= 0) {
          quantity = this.readNumericField(item, 'num')
        }
        return quantity > 0 ? quantity : 1
      } catch (error) {
        console.error('提取购物车数量失败', error)
        return 1
      }
    },

    readCartCountFromStorage(): number {
      try {
        const cartRaw = uni.getStorageSync('cart')
        if (cartRaw == null || cartRaw === '') {
          return 0
        }
        const parsed = JSON.parse(cartRaw.toString()) as Array<UTSJSONObject>
        if (!Array.isArray(parsed)) {
          return 0
        }

        let total = 0
        for (let i = 0; i < parsed.length; i++) {
          total += this.extractCartItemQuantity(parsed[i])
        }
        return total
      } catch (error) {
        console.error('读取本地购物车数量失败', error)
        return 0
      }
    },

    async refreshCartBadgeCount() {
      try {
        const userId = supabaseService.getCurrentUserId()
        if (userId == null || userId === '') {
          this.cartBadgeCount = this.readCartCountFromStorage()
          return
        }

        const cartItems = await supabaseService.getCartItems()
        let total = 0
        for (let i = 0; i < cartItems.length; i++) {
          total += this.extractCartItemQuantity(cartItems[i])
        }
        this.cartBadgeCount = total
      } catch (error) {
        console.error('刷新购物车角标失败', error)
        this.cartBadgeCount = this.readCartCountFromStorage()
      }
    },

    getCartBadgeText(): string {
      if (this.cartBadgeCount <= 0) return ''
      if (this.cartBadgeCount > 99) return '99+'
      return this.cartBadgeCount.toString()
    },

    initImmersiveLayout() {
      const systemInfo = uni.getSystemInfoSync()
      this.statusBarHeight = systemInfo.statusBarHeight ?? 0
      this.capsuleTop = this.statusBarHeight > 0 ? this.statusBarHeight : 12
      this.capsuleHeight = 32
      this.navButtonTop = this.capsuleTop
      this.navButtonHeight = this.capsuleHeight
      this.navRightReserve = 16

      // #ifdef MP-WEIXIN
      try {
        const menuBtn = uni.getMenuButtonBoundingClientRect()
        if (menuBtn != null && menuBtn.top != null && menuBtn.top > 0) {
          this.capsuleTop = menuBtn.top
          this.capsuleHeight = menuBtn.height > 0 ? menuBtn.height : 32
          this.navButtonTop = menuBtn.top
          this.navButtonHeight = this.capsuleHeight
          const screenWidth = systemInfo.screenWidth ?? systemInfo.windowWidth
          this.navRightReserve = (screenWidth - menuBtn.left) + 12
        }
      } catch (e) {
        this.navButtonTop = this.statusBarHeight > 0 ? this.statusBarHeight + 6 : 16
        this.navButtonHeight = 32
        this.navRightReserve = 16
      }
      // #endif

      if (this.navButtonTop < this.statusBarHeight) {
        this.navButtonTop = this.statusBarHeight
      }
      if (this.navButtonHeight < 32) {
        this.navButtonHeight = 32
      }
      if (this.navRightReserve < 16) {
        this.navRightReserve = 16
      }
    },

    getNavLeftStyle(): string {
      return `top:${this.navButtonTop}px;height:${this.navButtonHeight}px;`
    },

    getNavRightStyle(): string {
      return `top:${this.navButtonTop}px;height:${this.navButtonHeight}px;right:${this.navRightReserve}px;`
    },

    goBack() {
      try {
        const pages = getCurrentPages()
        if (pages != null && pages.length > 1) {
          uni.navigateBack()
          return
        }
      } catch (e) {}
      uni.switchTab({ url: '/pages/main/index' })
    },

    goToSearch() {
      uni.navigateTo({ url: '/pages/mall/consumer/search' })
    },

    openActionPanel() {
      uni.showActionSheet({
        itemList: ['首页', '分享', '客服'],
		success: (res: ShowActionSheetSuccess) => {
          if (res.tapIndex === 0) {
            this.goToHome()
          } else if (res.tapIndex === 1) {
            this.showSharePopup()
          } else if (res.tapIndex === 2) {
            this.contactMerchant()
          }
        }
      })
    },

    saveFootprint(productId: string) {
      supabaseService.addFootprint(productId).then((success: boolean) => {
        if (success === true) {
          console.log('足迹已同步到服务器')
        }
      })

      const footprintData = uni.getStorageSync('footprints') as string | null
      let footprints: Array<FootprintItemType> = []

      if (footprintData != null && footprintData !== '') {
        try {
          footprints = JSON.parse(footprintData) as Array<FootprintItemType>
        } catch (e) {
          console.error('Failed to parse footprints', e)
        }
      }

      const productIdStr = productId
      footprints = footprints.filter(function(item) {
        const itemObj = item as UTSJSONObject
        const itemId = itemObj.getString('id') ?? ''
        return itemId != productIdStr
      })

      const productImage = this.product.images.length > 0 ? this.product.images[0] : this.defaultImage
      footprints.unshift({
        id: this.product.id,
        name: this.product.name,
        price: this.product.price,
        original_price: this.product.original_price,
        image: productImage,
        sales: this.product.sales,
        shopId: this.merchant.id,
        shopName: this.merchant.shop_name,
        viewTime: Date.now()
      })

      if (footprints.length > 50) {
        footprints = footprints.slice(0, 50)
      }
      uni.setStorageSync('footprints', JSON.stringify(footprints))
    },

    async loadProductDetail(productId: string, options: any = {}) {
      uni.showLoading({ title: '加载中...' })
      try {
        const dbProduct = await supabaseService.getProductById(productId)

        if (dbProduct != null) {
          this.product = {
            id: dbProduct.id,
            merchant_id: dbProduct.merchant_id ?? '',
            category_id: dbProduct.category_id ?? '',
            name: dbProduct.name,
            short_title: dbProduct.short_title ?? '',
            subtitle: dbProduct.subtitle ?? '',
            description: dbProduct.description ?? '',
            images: dbProduct.images ?? [] as Array<string>,
            price: dbProduct.price ?? dbProduct.base_price ?? 0,
            original_price: dbProduct.original_price ?? dbProduct.market_price ?? 0,
            stock: dbProduct.stock ?? dbProduct.total_stock ?? 0,
            sales: dbProduct.sale_count ?? 0,
            status: dbProduct.status ?? 1,
            created_at: dbProduct.created_at ?? new Date().toISOString(),
            specification: dbProduct.specification ?? null,
            usage: dbProduct.usage ?? null,
            side_effects: dbProduct.side_effects ?? null,
            precautions: dbProduct.precautions ?? null,
            expiry_date: dbProduct.expiry_date ?? null,
            storage_conditions: dbProduct.storage_conditions ?? null,
            approval_number: dbProduct.approval_number ?? null,
            tags: [] as Array<string>,
            card_tags: dbProduct.card_tags ?? [] as Array<string>,
            service_tags: dbProduct.service_tags ?? [] as Array<string>,
            selling_points: dbProduct.selling_points ?? [] as Array<string>,
            display_sales_text: dbProduct.display_sales_text ?? '',
            detail_images: [] as Array<string>,
            guarantees: [] as Array<UTSJSONObject>
          } as ProductType

          const rawProductObj = JSON.parse(JSON.stringify(dbProduct)) as UTSJSONObject

          if (dbProduct.tags != null && dbProduct.tags != '') {
            try {
              const parsedTags = JSON.parse(dbProduct.tags)
              if (Array.isArray(parsedTags)) {
                const nextTags = [] as Array<string>
                for (let i = 0; i < parsedTags.length; i++) {
                  const tag = parsedTags[i] as string
                  if (tag != null && tag !== '') {
                    nextTags.push(tag)
                  }
                }
                this.product.tags = nextTags
              }
            } catch (e) {}
          }

          this.product.images = this.sanitizeProductImages(this.product.images)
          if (this.product.images.length == 0 && dbProduct.main_image_url != null && dbProduct.main_image_url != '') {
            this.product.images.push(this.normalizeImageUrl(dbProduct.main_image_url))
          }
          if (this.product.images.length == 0) {
            this.product.images.push(this.defaultImage)
          }

          this.detailImages = this.resolveDetailImages(rawProductObj)
          this.productGuarantees = this.resolveGuarantees(rawProductObj)

          console.log('商品详情加载成功:', this.product.name, '库存:', this.product.stock, '销量:', this.product.sales)
        } else {
          throw new Error('No product found')
        }
      } catch (e) {
        console.error('Failed to load product detail:', e)
        this.product.id = productId
        const opts = options as UTSJSONObject
        const nameOpt = opts['name']
        this.product.name = (nameOpt != null && nameOpt != '') ? decodeURIComponent(nameOpt as string) ?? '未知商品' : '未知商品'
        this.product.short_title = this.product.name

        const priceOpt = opts['price']
        if (typeof priceOpt == 'number') {
          this.product.price = priceOpt as number
        } else if (typeof priceOpt == 'string') {
          this.product.price = parseFloat(priceOpt as string)
        } else {
          this.product.price = 0
        }

        const imageOpt = opts['image']
        const decodedImage = (imageOpt != null && imageOpt != '') ? decodeURIComponent(imageOpt as string) : null
        this.product.images = decodedImage != null ? this.sanitizeProductImages([decodedImage]) : [this.defaultImage]
        this.detailImages = [] as Array<string>
        this.productGuarantees = this.getDefaultGuarantees()
      }

      if (this.product.merchant_id != null && this.product.merchant_id !== '') {
        await this.loadMerchantInfo(this.product.merchant_id)
        this.loadCoupons()
      }
      if (this.product.id != null && this.product.id !== '') {
        this.loadProductSkus(this.product.id)
        this.loadReviewPreview(this.product.id)
      }

      this.loadMemberPrice()
      uni.hideLoading()
    },

    async loadMerchantInfo(merchantId: string) {
      let realMerchantLoaded = false
      if (merchantId.includes('-') || !merchantId.startsWith('merchant_')) {
        try {
          const shopResponse = await supabaseService.getShopByMerchantId(merchantId)
          if (shopResponse != null) {
            this.merchant = {
              id: shopResponse.id,
              user_id: shopResponse.merchant_id,
              shop_name: shopResponse.shop_name,
              shop_logo: this.normalizeImageUrl(shopResponse.shop_logo),
              shop_banner: this.normalizeImageUrl(shopResponse.shop_banner),
              shop_description: shopResponse.description ?? '',
              contact_name: shopResponse.contact_name ?? '店主',
              contact_phone: shopResponse.contact_phone ?? '',
              shop_status: 1,
              rating: shopResponse.rating_avg ?? 5.0,
              total_sales: shopResponse.total_sales ?? 0,
              created_at: shopResponse.created_at ?? new Date().toISOString()
            } as MerchantType
            realMerchantLoaded = true
          }
        } catch (e) {
          console.error('Load shop failed', e)
        }
      }

      if (!realMerchantLoaded) {
        let charSum = 0
        for (let i = 0; i < merchantId.length; i++) {
          const charCode = merchantId.charCodeAt(i)
          if (charCode != null) {
            charSum += charCode
          }
        }
        const merchantIndex = Math.abs(charSum) % 5
        const shopNames = ['优质好店', '品牌直营店', '官方旗舰店', '专卖店', '精品小店']

        this.merchant = {
          id: merchantId,
          user_id: 'user_mock_' + merchantIndex,
          shop_name: shopNames[merchantIndex],
          shop_logo: this.defaultImage,
          shop_banner: this.defaultImage,
          shop_description: '优质服务，正品保障',
          contact_name: '店主',
          contact_phone: '',
          shop_status: 1,
          rating: 4.8,
          total_sales: 999,
          created_at: '2023-01-01'
        } as MerchantType
      }
    },

    async loadProductSkus(productId: string) {
      try {
        const skus = await supabaseService.getProductSkus(productId)
        if (skus.length > 0) {
          this.productSkus = [] as Array<ProductSkuType>
          for (let i = 0; i < skus.length; i++) {
            const skuData = skus[i]
            let specs: UTSJSONObject = {}
            if (skuData.specifications != null && skuData.specifications != '') {
              try {
                specs = JSON.parse(skuData.specifications) as UTSJSONObject
              } catch (e) {
                console.error('解析SKU规格失败', e)
              }
            }
            const sku: ProductSkuType = {
              id: skuData.id,
              product_id: skuData.product_id,
              sku_code: skuData.sku_code,
              specifications: specs,
              price: skuData.price,
              stock: skuData.stock ?? 0,
              image_url: skuData.image_url ?? '',
              status: skuData.status ?? 1
            }
            this.productSkus.push(sku)
          }
        }
      } catch (e) {
        console.error('Fetch SKUs error', e)
      }
    },

    async loadMemberPrice() {
      try {
        const memberInfo = await supabaseService.getUserMemberInfo()
        const levelNameRaw = memberInfo.get('level_name')
        const discountRaw = memberInfo.get('discount')

        if (levelNameRaw != null) {
          this.memberLevelName = levelNameRaw as string
        }

        if (discountRaw != null) {
          const discountRate = discountRaw as number
          if (discountRate > 0 && discountRate < 1) {
            this.memberDiscount = Math.round(discountRate * 10) / 10 * 10
            this.memberPrice = Math.round(this.product.price * discountRate * 100) / 100
          }
        }
      } catch (e) {
        console.log('获取会员信息失败，可能未登录或非会员:', e)
      }
    },

    async loadCoupons() {
      if (this.product.merchant_id == '') return
      try {
        const couponData = await supabaseService.fetchShopCoupons(this.product.merchant_id)
        this.coupons = [] as Array<CouponTemplateType>
        if (couponData != null && couponData.length > 0) {
          for (let i = 0; i < couponData.length; i++) {
            const item = couponData[i]
            const couponObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject

            const getSafeString = (key: string): string => {
              const val = couponObj.get(key)
              if (val == null) return ''
              if (typeof val == 'string') return val
              return ''
            }

            const getSafeNumber = (key: string): number => {
              const val = couponObj.get(key)
              if (val == null) return 0
              if (typeof val == 'number') return val
              return 0
            }

            const coupon: CouponTemplateType = {
              id: getSafeString('id'),
              name: getSafeString('name'),
              description: getSafeString('description'),
              coupon_type: getSafeNumber('coupon_type'),
              discount_type: getSafeNumber('discount_type'),
              discount_value: getSafeNumber('discount_value'),
              min_order_amount: getSafeNumber('min_order_amount'),
              max_discount_amount: getSafeNumber('max_discount_amount'),
              total_quantity: getSafeNumber('total_quantity'),
              per_user_limit: getSafeNumber('per_user_limit'),
              usage_limit: getSafeNumber('usage_limit'),
              merchant_id: getSafeString('merchant_id'),
              category_ids: [] as Array<string>,
              product_ids: [] as Array<string>,
              user_type_limit: getSafeNumber('user_type_limit'),
              start_time: getSafeString('start_time'),
              end_time: getSafeString('end_time'),
              status: getSafeNumber('status'),
              created_at: getSafeString('created_at')
            }
            this.coupons.push(coupon)
          }
        }
      } catch (e) {
        console.warn('SupabaseService coupon methods not available:', e)
      }
    },

    async loadReviewPreview(productId: string) {
      this.reviewLoading = true
      try {
        const stats = await supabaseService.getReviewStats(productId)
        this.reviewCount = stats.getNumber('total_count') ?? 0
        const avgRating = stats.getNumber('avg_rating') ?? 0
        this.reviewRating = avgRating > 0 ? avgRating : 5

        const reviewResult = await supabaseService.getProductReviews(productId, 1, 2, 0, false)
        const dataRaw = reviewResult.get('data')
        const parsedReviews = dataRaw != null
          ? this.parseReviews(dataRaw)
          : [] as Array<ProductReviewPreviewType>
        if (parsedReviews.length > 0) {
          this.reviewPreview = parsedReviews
          if (this.reviewCount <= 0) {
            this.reviewCount = parsedReviews.length
          }
        } else {
          this.reviewPreview = this.getMockReviews()
          if (this.reviewCount <= 0) {
            this.reviewCount = this.reviewPreview.length
          }
          if (this.reviewRating <= 0) {
            this.reviewRating = 5
          }
        }
      } catch (e) {
        console.error('加载商品评论失败:', e)
        this.reviewPreview = this.getMockReviews()
        if (this.reviewCount <= 0) {
          this.reviewCount = this.reviewPreview.length
        }
        if (this.reviewRating <= 0) {
          this.reviewRating = 5
        }
      } finally {
        this.reviewLoading = false
      }
    },

    parseReviews(rawData: any): Array<ProductReviewPreviewType> {
      const result = [] as Array<ProductReviewPreviewType>
      if (rawData == null) return result
      try {
        const rawList = JSON.parse(JSON.stringify(rawData)) as Array<UTSJSONObject>
        if (!Array.isArray(rawList)) return result

        for (let i = 0; i < rawList.length; i++) {
          const item = rawList[i] as UTSJSONObject
          const reviewImagesRaw = item.get('images')
          const review: ProductReviewPreviewType = {
            id: item.getString('id') ?? ('mock-review-' + i),
            userName: item.getString('user_name') ?? '匿名用户',
            rating: item.getNumber('rating') ?? 5,
            content: item.getString('content') ?? '体验不错，包装完整，物流也很快。',
            createdAt: item.getString('created_at') ?? new Date().toISOString(),
            images: reviewImagesRaw != null ? this.readStringArrayFromRaw(reviewImagesRaw) : [] as Array<string>
          }
          if (review.images.length == 0) {
            review.images = [] as Array<string>
          }
          result.push(review)
        }
      } catch (e) {
        console.error('解析评论失败:', e)
      }
      return result
    },

    readStringArrayFromRaw(rawValue: any): Array<string> {
      const result = [] as Array<string>
      if (rawValue == null) return result
      try {
        if (typeof rawValue == 'string') {
          const rawText = rawValue as string
          if (rawText == '') return result
          if (rawText.charAt(0) == '[') {
            const parsed = JSON.parse(rawText)
            if (Array.isArray(parsed)) {
              for (let i = 0; i < parsed.length; i++) {
                const item = parsed[i] as string
                if (item != null && item !== '') {
                  result.push(this.normalizeImageUrl(item))
                }
              }
              return result
            }
          }
          result.push(this.normalizeImageUrl(rawText))
          return result
        }

        const parsedRaw = JSON.parse(JSON.stringify(rawValue))
        if (Array.isArray(parsedRaw)) {
          for (let i = 0; i < parsedRaw.length; i++) {
            const item = parsedRaw[i] as string
            if (item != null && item !== '') {
              result.push(this.normalizeImageUrl(item))
            }
          }
        }
      } catch (e) {}
      return result
    },

    resolveDetailImages(rawProductObj: UTSJSONObject): Array<string> {
      const detailImagesRaw = rawProductObj.get('detail_images')
      const images = detailImagesRaw != null ? this.readStringArrayFromRaw(detailImagesRaw) : [] as Array<string>
      if (images.length > 0) return images
      const backupImagesRaw = rawProductObj.get('detail_image_urls')
      const backupImages = backupImagesRaw != null ? this.readStringArrayFromRaw(backupImagesRaw) : [] as Array<string>
      if (backupImages.length > 0) return backupImages
      return [] as Array<string>
    },

    resolveGuarantees(rawProductObj: UTSJSONObject): Array<ProductGuaranteeType> {
      const defaults = this.getDefaultGuarantees()
      const rawGuarantees = rawProductObj.get('guarantees')
      if (rawGuarantees != null) {
        try {
          const parsedRaw = JSON.parse(JSON.stringify(rawGuarantees))
          if (Array.isArray(parsedRaw)) {
            const result = [] as Array<ProductGuaranteeType>
            for (let i = 0; i < parsedRaw.length; i++) {
              const item = parsedRaw[i]
              if (typeof item == 'string') {
                const title = item as string
                if (title != null && title !== '') {
                  result.push({ id: 'guarantee-' + i, title: title, desc: title, enabled: true } as ProductGuaranteeType)
                }
              } else {
                const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
                const title = itemObj.getString('title') ?? ''
                if (title != '') {
                  result.push({
                    id: itemObj.getString('id') ?? ('guarantee-' + i),
                    title: title,
                    desc: itemObj.getString('desc') ?? title,
                    enabled: itemObj.getBoolean('enabled') ?? true
                  } as ProductGuaranteeType)
                }
              }
            }
            if (result.length > 0) return result
          }
        } catch (e) {}
      }

      const tagGuarantees = this.resolveGuaranteesFromServiceTags()
      if (tagGuarantees.length > 0) {
        return tagGuarantees
      }
      return defaults
    },

    resolveGuaranteesFromServiceTags(): Array<ProductGuaranteeType> {
      const defaults = this.getDefaultGuarantees()
      const serviceTags = this.getServiceTags()
      if (serviceTags.length == 0) return [] as Array<ProductGuaranteeType>

      const result = [] as Array<ProductGuaranteeType>
      for (let i = 0; i < defaults.length; i++) {
        const item = defaults[i]
        let enabled = item.enabled
        for (let j = 0; j < serviceTags.length; j++) {
          const tag = serviceTags[j]
          if (tag.indexOf(item.title) !== -1 || item.title.indexOf(tag) !== -1) {
            enabled = true
          }
        }
        if (enabled) {
          result.push({ id: item.id, title: item.title, desc: item.desc, enabled: true } as ProductGuaranteeType)
        }
      }
      return result
    },

    getDefaultGuarantees(): Array<ProductGuaranteeType> {
      return [
        { id: 'shipping', title: '发货保障', desc: '下单后尽快发货，异常可协商处理', enabled: true },
        { id: 'refund', title: '极速退款', desc: '符合条件的订单支持快速退款', enabled: true },
        { id: 'auth', title: '正品保障', desc: '商家承诺所售商品来源正规', enabled: true },
        { id: 'return', title: '7天无理由退货', desc: '支持七天无理由退货，特殊商品除外', enabled: true },
        { id: 'local', title: '本地配送', desc: '部分区域支持本地配送', enabled: false },
        { id: 'promise', title: '商家承诺', desc: '服务时效和售后说明以商家承诺为准', enabled: true }
      ] as Array<ProductGuaranteeType>
    },

    getMockReviews(): Array<ProductReviewPreviewType> {
      return [
        {
          id: 'mock-review-1',
          userName: '风和日暖',
          rating: 5,
          content: '包装完整，和页面描述一致，发货速度快，整体体验很好。',
          createdAt: '2026-05-01T10:30:00',
          images: [] as Array<string>
        },
        {
          id: 'mock-review-2',
          userName: '木子青',
          rating: 4,
          content: '价格合适，客服回复及时，已经回购一次，比较放心。',
          createdAt: '2026-04-26T16:20:00',
          images: [] as Array<string>
        }
      ] as Array<ProductReviewPreviewType>
    },

    normalizeImageUrl(url: string | null): string {
      if (url == null) return this.defaultImage
      const trimmed = url.trim()
      if (trimmed == '') return this.defaultImage
      if (trimmed.indexOf('https://picsum.photos/') == 0) return this.defaultImage
      if (trimmed.indexOf('http://picsum.photos/') == 0) return this.defaultImage
      if (trimmed.indexOf('blob:') == 0) return this.defaultImage
      return trimmed
    },

    sanitizeProductImages(images: Array<string>): Array<string> {
      const normalized = [] as Array<string>
      for (let i = 0; i < images.length; i++) {
        const fixed = this.normalizeImageUrl(images[i])
        if (fixed != '') {
          normalized.push(fixed)
        }
      }
      if (normalized.length == 0) {
        normalized.push(this.defaultImage)
      }
      return normalized
    },

    getProductImages(): Array<string> {
      if (this.product.images.length > 0) {
        return this.product.images
      }
      return [this.defaultImage]
    },

    getDetailImages(): Array<string> {
      if (this.detailImages.length > 0) {
        return this.detailImages
      }
      return [] as Array<string>
    },

    handleProductImageError(index: number) {
      const currentImages = this.getProductImages()
      const nextImages = [] as Array<string>
      for (let i = 0; i < currentImages.length; i++) {
        nextImages.push(i == index ? this.defaultImage : currentImages[i])
      }
      this.product.images = nextImages
    },

    handleDetailImageError(index: number) {
      if (index < 0 || index >= this.detailImages.length) return
      const nextImages = [] as Array<string>
      for (let i = 0; i < this.detailImages.length; i++) {
        nextImages.push(i == index ? this.defaultImage : this.detailImages[i])
      }
      this.detailImages = nextImages
    },

    getMerchantLogo(): string {
      return this.normalizeImageUrl(this.merchant.shop_logo)
    },

    getMerchantName(): string {
      if (this.merchant.shop_name != null && this.merchant.shop_name !== '') {
        return this.merchant.shop_name
      }
      return '精选店铺'
    },

    getMerchantDescription(): string {
      if (this.merchant.shop_description != null && this.merchant.shop_description !== '') {
        return this.merchant.shop_description
      }
      return '服务以商家实际承诺为准'
    },

    handleShopLogoError() {
      this.merchant.shop_logo = this.defaultImage
    },

    contactMerchant() {
      if (supabaseService.getCurrentUserId() == '') {
        uni.navigateTo({ url: '/pages/auth/login' })
        return
      }
      const merchId = this.merchant.user_id != null && this.merchant.user_id !== ''
        ? this.merchant.user_id.toString()
        : (this.merchant.id != null && this.merchant.id !== ''
          ? this.merchant.id.toString()
          : (this.product.merchant_id != null ? this.product.merchant_id.toString() : ''))
      const merchName = this.merchant.shop_name != null ? this.merchant.shop_name.toString() : ''
      uni.navigateTo({
        url: `/pages/mall/consumer/chat?merchantId=${encodeURIComponent(merchId)}&merchantName=${encodeURIComponent(merchName)}`,
      fail: (error: any) => {
          console.error('打开客服页面失败', error)
          uni.showToast({ title: '页面打开失败', icon: 'none' })
        }
      })
    },

    handleContactService() {
      const merchantUserId = this.merchant.user_id != null ? this.merchant.user_id.toString() : ''
      const merchantId = this.merchant.id != null ? this.merchant.id.toString() : ''
      const productMerchantId = this.product.merchant_id != null ? this.product.merchant_id.toString() : ''
      const targetMerchantId = merchantUserId !== '' ? merchantUserId : (merchantId !== '' ? merchantId : productMerchantId)
      if (targetMerchantId === '') {
        uni.showToast({ title: '联系客服功能开发中', icon: 'none' })
        return
      }
      this.contactMerchant()
    },

    showCouponModal() {
      this.showCoupons = true
    },

    hideCouponModal() {
      this.showCoupons = false
    },

    async claimCoupon(coupon: CouponTemplateType) {
      const userId = supabaseService.getCurrentUserId() ?? ''
      if (userId == '') {
        uni.navigateTo({ url: '/pages/auth/login' })
        return
      }
      uni.showLoading({ title: '领取中' })

      let success = false
      const couponId = coupon.id != null ? coupon.id : ''
      if (couponId === '') {
      uni.hideLoading()
      uni.showToast({ title: '优惠券信息缺失', icon: 'none' })
      return
      }
      try {
        // @ts-ignore
        success = await supabaseService.claimShopCoupon(couponId, userId)
      } catch (e) {
        try {
          // @ts-ignore
          success = await supabaseService.claimCoupon(couponId, userId)
        } catch (e2) {
          console.warn('claimCoupon method missing:', e2)
        }
      }

      uni.hideLoading()
      if (success === true) {
        uni.showToast({ title: '领取成功', icon: 'success' })
      } else {
        uni.showToast({ title: '领取失败或已领取', icon: 'none' })
      }
    },

    getSelectedSkuImage(): string {
      if (this.selectedSkuId != '') {
        const sku = this.productSkus.find(s => s.id === this.selectedSkuId)
        if (sku != null && sku.image_url != null && sku.image_url != '') {
          return this.normalizeImageUrl(sku.image_url as string)
        }
      }
      return this.product.images.length > 0 ? this.product.images[0] : this.defaultImage
    },

    getSelectedSkuPrice(): string {
      if (this.selectedSkuId != '') {
        const sku = this.productSkus.find(s => s.id === this.selectedSkuId)
        if (sku != null) return sku.price.toFixed(2)
      }
      return this.getDisplayPriceText()
    },

    getSelectedSkuStock(): number {
      if (this.selectedSkuId != '') {
        const sku = this.productSkus.find(s => s.id === this.selectedSkuId)
        if (sku != null) return sku.stock
      }
      return this.product.stock > 0 ? this.product.stock : 0
    },

    formatDate(dateStr: string): string {
      if (dateStr == '') return ''
      const date = new Date(dateStr)
      return `${date.getFullYear()}.${date.getMonth() + 1}.${date.getDate()}`
    },

    formatRating(value: number): string {
      if (value <= 0) return '5.0'
      return value.toFixed(1)
    },

    getReviewCountLabel(): string {
      if (this.reviewCount <= 0) return '暂无评价'
      return `${this.reviewCount}条评价`
    },

    getReviewStars(rating: number): string {
      let result = ''
      const safeRating = rating > 0 ? rating : 5
      for (let i = 0; i < 5; i++) {
        result += i < safeRating ? '★' : '☆'
      }
      return result
    },

    viewAllReviews() {
      uni.showToast({ title: '查看全部评价开发中', icon: 'none' })
    },

    previewReviewImages(images: Array<string>, index: number) {
      if (images.length == 0) return
      uni.previewImage({ current: index, urls: images })
    },

    previewDetailImage(index: number) {
      if (this.detailImages.length == 0) return
      uni.previewImage({ current: index, urls: this.detailImages })
    },

    showGuaranteeIntro() {
      uni.showToast({ title: '保障说明以后续商家配置为准', icon: 'none' })
    },

    onSwiperChange(e: UniSwiperChangeEvent) {
      this.currentImageIndex = e.detail.current
    },

    showSpecModal() {
      this.showSpec = true
    },

    hideSpecModal() {
      this.showSpec = false
    },

    selectSku(sku: ProductSkuType) {
      this.selectedSkuId = sku.id
      this.selectedSpec = this.getSkuSpecText(sku)
    },

    getSkuSpecText(sku: ProductSkuType): string {
      if (sku.specifications != null) {
        const specs = sku.specifications as UTSJSONObject
        let specStr = ''
        for (const key in specs) {
          const val = specs[key]
          if (val != null) {
            specStr += (specStr === '' ? '' : ' ') + val.toString()
          }
        }
        if (specStr !== '') {
          return specStr
        }
      }
      return sku.sku_code ?? ''
    },

    getSelectedSpecLabel(): string {
      if (this.selectedSpec != '') return this.selectedSpec
      if (this.productSkus.length > 0) return '请选择规格'
      return '默认规格'
    },

    async addToCart() {
      const userId = supabaseService.getCurrentUserId()
      if (userId == null || userId === '') {
        goToLogin(`/pages/mall/consumer/product-detail?id=${this.product.id}`)
        return
      }
      if (this.productSkus.length > 0 && (this.selectedSkuId == null || this.selectedSkuId === '')) {
        if (!this.showSpec) {
          this.showSpecModal()
        }
        return
      }

      uni.showLoading({ title: '添加中...' })

      try {
        const success = await supabaseService.addToCart(
          this.product.id,
          this.quantity,
          this.selectedSkuId,
          this.product.merchant_id
        )
        uni.hideLoading()

        if (success === true) {
          uni.showToast({ title: '已添加到购物车', icon: 'success' })
          this.hideSpecModal()
          this.refreshCartBadgeCount()
        } else {
          uni.showToast({ title: '添加失败，请登录重试', icon: 'none' })
        }
      } catch (e) {
        uni.hideLoading()
        console.error('添加购物车异常', e)
        uni.showToast({ title: '添加异常', icon: 'none' })
      }
    },

    buyNow() {
      const userId = supabaseService.getCurrentUserId()
      if (userId == null || userId === '') {
        goToLogin(`/pages/mall/consumer/product-detail?id=${this.product.id}`)
        return
      }
      if (this.productSkus.length > 0 && (this.selectedSkuId == null || this.selectedSkuId === '')) {
        if (!this.showSpec) {
          this.showSpecModal()
        }
        return
      }

      const sku = (this.selectedSkuId != null && this.selectedSkuId !== '') ? this.productSkus.find(s => s.id === this.selectedSkuId) : null

      const selectedItem = {
        id: this.selectedSkuId,
        product_id: this.product.id,
        sku_id: this.selectedSkuId,
        product_name: this.product.name,
        product_image: (sku != null && sku.image_url != null && sku.image_url != '') ? sku.image_url : this.product.images[0],
        sku_specifications: sku != null ? sku.specifications : {},
        price: parseFloat((sku != null ? sku.price : this.product.price).toString()).toFixed(2) as string,
        quantity: this.quantity as number,
        shop_id: this.merchant.id,
        shop_name: this.merchant.shop_name,
        merchant_id: this.merchant.user_id ?? this.product.merchant_id
      }

      uni.setStorageSync('checkout_type', 'buy_now')
      uni.setStorageSync('checkout_items', JSON.stringify([selectedItem]))
      uni.navigateTo({ url: '/pages/mall/consumer/checkout' })
    },

    checkFavoriteStatus(id: string) {
      this.checkFavorite(id)
    },

    async checkFavorite(id: string) {
      const isFav = await supabaseService.checkFavorite(id)
      this.isFavorite = isFav
    },

    async toggleFavorite() {
      if (this.product.id == '') return
      const userId = supabaseService.getCurrentUserId()
      if (userId == null || userId === '') {
        goToLogin(`/pages/mall/consumer/product-detail?id=${this.product.id}`)
        return
      }
      uni.showLoading({ title: '处理中' })

      try {
        const wasFavorite = this.isFavorite
        const isNowFavorite = await supabaseService.toggleFavorite(this.product.id)
        uni.hideLoading()

        if (isNowFavorite !== wasFavorite) {
          this.isFavorite = isNowFavorite
          uni.showToast({ title: isNowFavorite ? '收藏成功' : '已取消收藏', icon: 'success' })
        } else {
          uni.showToast({ title: '操作失败', icon: 'none' })
          this.checkFavoriteStatus(this.product.id)
        }
      } catch (e) {
        uni.hideLoading()
        console.error('Toggle favorite failed', e)
        uni.showToast({ title: '操作异常', icon: 'none' })
      }
    },

    goToHome() {
      uni.switchTab({ url: '/pages/main/index' })
    },

    goToShop() {
      const shopId = this.merchant.id != null ? this.merchant.id.toString() : ''
      const merchantId = this.product.merchant_id != null ? this.product.merchant_id.toString() : ''
      const targetId = shopId !== '' ? shopId : merchantId
      if (targetId != '') {
        uni.navigateTo({
          url: `/pages/mall/consumer/shop-detail?merchantId=${encodeURIComponent(targetId)}`,
        fail: (error: any) => {
            console.error('打开店铺页面失败', error)
            uni.showToast({ title: '页面打开失败', icon: 'none' })
          }
        })
      } else {
        uni.showToast({ title: '店铺信息缺失', icon: 'none' })
      }
    },

    goToCart() {
      uni.switchTab({
        url: '/pages/main/cart',
      fail: (error: any) => {
          console.error('打开购物车页面失败', error)
          uni.showToast({ title: '页面打开失败', icon: 'none' })
        }
      })
    },

    decreaseQuantity() {
      if (this.quantity > 1) {
        this.quantity--
      }
    },

    increaseQuantity() {
      const maxQuantity = this.getMaxQuantity()
      if (this.quantity < maxQuantity) {
        this.quantity++
      } else {
        uni.showToast({ title: `最多只能购买${maxQuantity}件`, icon: 'none' })
      }
    },

    validateQuantity(e: UniInputEvent) {
      let num = this.quantity
      try {
        const value = e.detail.value as string
        const parsed = parseInt(value)
        if (!isNaN(parsed)) {
          num = parsed
        }
      } catch (err) {}

      const maxQuantity = this.getMaxQuantity()
      if (num < 1) {
        num = 1
      } else if (num > maxQuantity) {
        num = maxQuantity
        uni.showToast({ title: `最多只能购买${maxQuantity}件`, icon: 'none' })
      }
      this.quantity = num
    },

    getMaxQuantity(): number {
      if (this.selectedSkuId != null && this.selectedSkuId !== '') {
        const sku = this.productSkus.find(s => s.id === this.selectedSkuId)
        if (sku != null && sku.stock > 0) return sku.stock
      }
      if (this.product.stock > 0) return this.product.stock
      return 99
    },

    getAvailableStock(): number {
      return this.getMaxQuantity()
    },

    getDisplayStockText(): string {
      const stock = this.getAvailableStock()
      if (stock > 0 && stock < 99) {
        return `库存${stock}件`
      }
      if (stock > 0) {
        return '库存充足'
      }
      return '库存以页面结算为准'
    },

    previewImage(index: number) {
      uni.previewImage({ current: index, urls: this.getProductImages() })
    },

    showParamsModal() {
      this.showParams = true
    },

    hideParamsModal() {
      this.showParams = false
    },

    getParamsSummary(): string {
      let summary = ''
      if (this.product.specification != null && (this.product.specification as string) != '') summary += '规格 '
      if (this.product.expiry_date != null && (this.product.expiry_date as string) != '') summary += '有效期 '
      if (this.product.approval_number != null && (this.product.approval_number as string) != '') summary += '批准文号 '
      const finalSummary = summary.trim()
      return finalSummary != '' ? finalSummary : '查看详情'
    },

    getDisplayTitle(): string {
      if (this.product.short_title != null && this.product.short_title !== '') {
        return this.product.short_title
      }
      if (this.product.name != null && this.product.name !== '') {
        return this.product.name
      }
      return '未命名商品'
    },

    getHighlightText(): string {
      if (this.product.selling_points != null && this.product.selling_points.length > 0) {
        const point = this.product.selling_points[0]
        if (point != null && point !== '') {
          return point
        }
      }
      if (this.product.subtitle != null && this.product.subtitle !== '') {
        return this.product.subtitle
      }
      return ''
    },

    getShortDescriptionText(): string {
      if (this.product.subtitle != null && this.product.subtitle !== '') {
        return this.product.subtitle
      }
      if (this.product.description != null && this.product.description !== '') {
        return this.product.description as string
      }
      return ''
    },

    getCardTags(): Array<string> {
      if (this.product.card_tags != null && this.product.card_tags.length > 0) {
        return this.product.card_tags.slice(0, 2)
      }
      const fallbackTags = [] as Array<string>
      if (this.product.tags != null && this.product.tags.length > 0) {
        for (let i = 0; i < this.product.tags.length && i < 2; i++) {
          const tag = this.product.tags[i]
          if (tag != null && tag !== '') {
            fallbackTags.push(tag)
          }
        }
      }
      return fallbackTags
    },

    getServiceTags(): Array<string> {
      if (this.product.service_tags != null && this.product.service_tags.length > 0) {
        return this.product.service_tags.slice(0, 3)
      }
      return [] as Array<string>
    },

    getVisibleGuarantees(): Array<ProductGuaranteeType> {
      const result = [] as Array<ProductGuaranteeType>
      for (let i = 0; i < this.productGuarantees.length; i++) {
        const item = this.productGuarantees[i]
        if (item.enabled === true) {
          result.push(item)
        }
      }
      return result
    },

    getDisplaySalesText(): string {
      if (this.product.display_sales_text != null && this.product.display_sales_text !== '') {
        return this.product.display_sales_text
      }
      const sales = this.product.sales > 0 ? this.product.sales : 0
      if (sales >= 100000) return '已售10万+'
      if (sales >= 10000) return '已售' + (sales / 10000).toFixed(1) + '万件'
      return '已售' + sales + '件'
    },

    getDisplayPriceText(): string {
      const price = (this.memberPrice > 0 && this.memberPrice < this.product.price) ? this.memberPrice : this.displayPrice
      if (price > 0) {
        return price.toFixed(2)
      }
      return '0.00'
    },

    getOriginalPriceText(): string {
      if (this.memberPrice > 0 && this.memberPrice < this.product.price) {
        return this.product.price > 0 ? this.product.price.toFixed(2) : ''
      }
      if (this.product.original_price != null && this.product.original_price > this.displayPrice) {
        return this.product.original_price.toFixed(2)
      }
      return ''
    },

    getDetailDescription(): string {
      if (this.product.description != null && this.product.description !== '') {
        return this.product.description as string
      }
      if (this.product.subtitle != null && this.product.subtitle !== '') {
        return this.product.subtitle as string
      }
      return ''
    },

    showSharePopup() {
      this.showShare = true
    },

    hideSharePopup() {
      this.showShare = false
    },

    shareToWechat() {
      this.hideSharePopup()
      // #ifdef MP-WEIXIN
      uni.share({
        provider: 'weixin',
        scene: 'WXSceneSession',
        type: 0,
        title: this.product.name,
        summary: `¥${this.product.price} - ${this.product.description ?? '精选好物'}`,
        imageUrl: this.product.images.length > 0 ? this.product.images[0] : '',
        success: () => {
          uni.showToast({ title: '分享成功', icon: 'success' })
        },
        fail: (err) => {
          console.error('分享失败', err)
          uni.showToast({ title: '分享失败', icon: 'none' })
        }
      })
      // #endif
      // #ifndef MP-WEIXIN
      uni.showToast({ title: '请在微信中打开分享', icon: 'none' })
      // #endif
    },

    shareToMoments() {
      this.hideSharePopup()
      // #ifdef MP-WEIXIN
      uni.share({
        provider: 'weixin',
        scene: 'WXSceneTimeline',
        type: 0,
        title: this.product.name,
        summary: `¥${this.product.price} - ${this.product.description ?? '精选好物'}`,
        imageUrl: this.product.images.length > 0 ? this.product.images[0] : '',
        success: () => {
          uni.showToast({ title: '分享成功', icon: 'success' })
        },
        fail: (err) => {
          console.error('分享失败', err)
          uni.showToast({ title: '分享失败', icon: 'none' })
        }
      })
      // #endif
      // #ifndef MP-WEIXIN
      uni.showToast({ title: '请在微信中打开分享', icon: 'none' })
      // #endif
    },

    shareToQQ() {
      this.hideSharePopup()
      uni.showToast({ title: 'QQ分享开发中', icon: 'none' })
    },

    copyLink() {
      this.hideSharePopup()
      const shareLink = `pages/mall/consumer/product-detail?id=${this.product.id}`
      uni.setClipboardData({
        data: shareLink,
        success: () => {
          uni.showToast({ title: '链接已复制', icon: 'success' })
        }
      })
    },

    saveImage() {
      this.hideSharePopup()
      if (this.product.images.length > 0) {
        uni.showLoading({ title: '保存中...' })
        uni.downloadFile({
          url: this.product.images[0],
          success: (res) => {
            const resObj = JSON.parse(JSON.stringify(res)) as UTSJSONObject
            const tempFilePath = resObj.getString('tempFilePath') ?? ''
            uni.saveImageToPhotosAlbum({
              filePath: tempFilePath,
              success: () => {
                uni.hideLoading()
                uni.showToast({ title: '已保存到相册', icon: 'success' })
              },
              fail: () => {
                uni.hideLoading()
                uni.showToast({ title: '保存失败', icon: 'none' })
              }
            })
          },
          fail: () => {
            uni.hideLoading()
            uni.showToast({ title: '下载失败', icon: 'none' })
          }
        })
      } else {
        uni.showToast({ title: '暂无图片可保存', icon: 'none' })
      }
    },

    generatePoster() {
      this.hideSharePopup()
      uni.showToast({ title: '海报生成功能开发中', icon: 'none' })
    }
  }
}
</script>

<style>
.product-detail-page {
  background-color: #f5f6fa;
  flex: 1;
  display: flex;
  flex-direction: column;
}

.page-scroll {
  flex: 1;
  height: 0;
  width: 100%;
}

.hero-section {
  position: relative;
  height: 700rpx;
  background: #ffffff;
}

.hero-swiper {
  width: 100%;
  height: 100%;
}

.hero-image {
  width: 100%;
  height: 100%;
  background-color: #f5f5f5;
}

.hero-indicator {
  position: absolute;
  right: 24rpx;
  bottom: 28rpx;
  background-color: rgba(0, 0, 0, 0.45);
  color: #ffffff;
  padding: 10rpx 18rpx;
  border-radius: 24rpx;
  font-size: 24rpx;
}

.floating-nav {
  position: absolute;
  display: flex;
  flex-direction: row;
  align-items: center;
  z-index: 20;
}

.left-nav {
  left: 24rpx;
}

.right-nav {
  right: 24rpx;
}

.floating-btn {
  min-width: 72rpx;
  height: 100%;
  padding: 0 20rpx;
  border-radius: 999rpx;
  background-color: rgba(255, 255, 255, 0.88);
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  margin-right: 14rpx;
  box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
}

.right-nav .floating-btn {
  margin-right: 0;
}

.floating-btn-text {
  font-size: 28rpx;
  color: #1f2329;
  font-weight: 700;
}

.content-wrap {
  margin-top: -20rpx;
  padding: 0 20rpx 180rpx;
  position: relative;
  z-index: 10;
}

.info-card {
  background-color: #ffffff;
  border-radius: 26rpx;
  padding: 28rpx;
  margin-bottom: 20rpx;
  box-shadow: 0 8rpx 24rpx rgba(31, 35, 41, 0.04);
}

.price-sales-card {
  background: linear-gradient(180deg, #fff8f4 0%, #ffffff 72%);
}

.vip-row {
  display: flex;
  flex-direction: row;
  align-items: center;
  margin-bottom: 18rpx;
}

.vip-badge {
  background: linear-gradient(135deg, #ff5a1f 0%, #ff7a00 100%);
  border-radius: 10rpx;
  padding: 6rpx 14rpx;
  margin-right: 12rpx;
}

.vip-badge-text {
  font-size: 20rpx;
  color: #ffffff;
  font-weight: 700;
}

.vip-discount-text {
  font-size: 24rpx;
  color: #ff5a1f;
  font-weight: 700;
}

.price-sales-row {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: flex-end;
}

.price-main-wrap {
  display: flex;
  flex-direction: row;
  align-items: baseline;
  flex-wrap: wrap;
}

.price-symbol {
  font-size: 32rpx;
  color: #ff4d24;
  font-weight: 700;
  margin-right: 6rpx;
}

.price-value {
  font-size: 60rpx;
  color: #ff4d24;
  font-weight: 800;
  line-height: 1;
}

.price-original {
  font-size: 24rpx;
  color: #9ea4ad;
  text-decoration-line: line-through;
  margin-left: 14rpx;
}

.sales-inline {
  font-size: 24rpx;
  color: #70767f;
  margin-left: 20rpx;
  padding-bottom: 8rpx;
}

.price-sub-row {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  margin-top: 16rpx;
}

.save-text {
  font-size: 22rpx;
  color: #20a162;
  background-color: #effaf5;
  padding: 6rpx 14rpx;
  border-radius: 999rpx;
}

.stock-text {
  font-size: 22rpx;
  color: #7b8088;
}

.quick-buy-row {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  margin-top: 22rpx;
  padding-top: 22rpx;
  border-top: 1rpx solid #f3f4f7;
}

.quick-row-label {
  font-size: 26rpx;
  color: #222222;
  font-weight: 600;
}

.quantity-selector {
  display: flex;
  flex-direction: row;
  align-items: center;
  border: 1rpx solid #eceff4;
  border-radius: 999rpx;
  overflow: hidden;
  background-color: #fafbfc;
}

.quantity-btn {
  width: 68rpx;
  height: 68rpx;
  display: flex;
  align-items: center;
  justify-content: center;
}

.quantity-btn-text {
  font-size: 28rpx;
  color: #222222;
  font-weight: 700;
}

.quantity-input {
  width: 86rpx;
  height: 68rpx;
  text-align: center;
  font-size: 28rpx;
  color: #222222;
  background-color: #ffffff;
}

.coupon-card {
  display: flex;
  flex-direction: row;
  align-items: center;
}

.coupon-label {
  font-size: 28rpx;
  color: #ff4d24;
  font-weight: 700;
  margin-right: 20rpx;
}

.coupon-tags-wrap {
  flex: 1;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
}

.coupon-chip {
  padding: 8rpx 16rpx;
  border-radius: 999rpx;
  background-color: #fff1ed;
  color: #ff5a2c;
  font-size: 22rpx;
  margin-right: 12rpx;
  margin-bottom: 8rpx;
}

.coupon-arrow {
  font-size: 24rpx;
  color: #8b919a;
}

.title-tag-row {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  margin-bottom: 16rpx;
}

.detail-card-tag {
  padding: 6rpx 14rpx;
  border-radius: 999rpx;
  background: #e1251b;
  color: #fff7d1;
  font-size: 20rpx;
  font-weight: 700;
  margin-right: 10rpx;
  margin-bottom: 8rpx;
}

.title-body-row {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  justify-content: space-between;
}

.title-main-wrap {
  flex: 1;
  padding-right: 20rpx;
}

.product-title {
  font-size: 34rpx;
  font-weight: 800;
  color: #1f2329;
  line-height: 1.45;
  max-height: 100rpx;
  overflow: hidden;
}

.product-highlight-text {
  display: block;
  font-size: 24rpx;
  line-height: 36rpx;
  color: #5d6470;
  margin-top: 14rpx;
}

.title-side-actions {
  width: 120rpx;
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

.side-action-btn {
  min-height: 92rpx;
  border-radius: 18rpx;
  background-color: #f7f8fb;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin-bottom: 12rpx;
}

.side-action-btn:last-child {
  margin-bottom: 0;
}

.side-action-icon {
  width: 36rpx;
  height: 36rpx;
  margin-bottom: 8rpx;
}

.side-action-symbol {
  font-size: 30rpx;
  color: #252b33;
  margin-bottom: 6rpx;
}

.side-action-text {
  font-size: 20rpx;
  color: #646b75;
}

.detail-service-row {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  margin-top: 20rpx;
}

.detail-service-tag {
  padding: 8rpx 14rpx;
  border-radius: 999rpx;
  font-size: 20rpx;
  font-weight: 600;
  color: #12b76a;
  background: #ecfdf3;
  margin-right: 10rpx;
  margin-bottom: 10rpx;
}

.meta-link-row {
  display: flex;
  flex-direction: row;
  align-items: center;
  margin-top: 20rpx;
}

.meta-link-text {
  font-size: 24rpx;
  color: #606874;
}

.meta-link-divider {
  font-size: 22rpx;
  color: #d0d5dc;
  margin: 0 12rpx;
}

.selling-points-panel {
  margin-top: 22rpx;
  padding: 18rpx 20rpx;
  border-radius: 18rpx;
  background-color: #fff8f3;
}

.selling-point-item {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  margin-bottom: 10rpx;
}

.selling-point-item:last-child {
  margin-bottom: 0;
}

.selling-point-dot {
  width: 10rpx;
  height: 10rpx;
  border-radius: 999rpx;
  background-color: #ff5a2c;
  margin-top: 12rpx;
  margin-right: 12rpx;
}

.selling-point-text {
  flex: 1;
  font-size: 23rpx;
  line-height: 34rpx;
  color: #5b6470;
}

.card-header-row {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20rpx;
}

.card-title {
  font-size: 30rpx;
  color: #1f2329;
  font-weight: 800;
}

.card-link-text {
  font-size: 24rpx;
  color: #7b8088;
}

.guarantee-list {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
}

.guarantee-chip {
  display: flex;
  flex-direction: row;
  align-items: center;
  margin-right: 18rpx;
  margin-bottom: 12rpx;
  padding: 8rpx 0;
}

.guarantee-dot {
  width: 12rpx;
  height: 12rpx;
  border-radius: 999rpx;
  background-color: #ff5a2c;
  margin-right: 10rpx;
}

.guarantee-text {
  font-size: 24rpx;
  color: #4f5662;
}

.shop-card {
  display: flex;
  flex-direction: row;
  align-items: center;
}

.shop-logo {
  width: 92rpx;
  height: 92rpx;
  border-radius: 18rpx;
  margin-right: 20rpx;
  background-color: #f7f8fa;
}

.shop-details {
  flex: 1;
}

.shop-name {
  font-size: 30rpx;
  color: #1f2329;
  font-weight: 700;
}

.shop-stats-row {
  display: flex;
  flex-direction: row;
  align-items: center;
  margin-top: 10rpx;
}

.shop-stat-text {
  font-size: 22rpx;
  color: #6a707a;
  margin-right: 20rpx;
}

.shop-desc {
  display: block;
  font-size: 22rpx;
  color: #8d949d;
  margin-top: 10rpx;
}

.enter-shop {
  font-size: 24rpx;
  color: #ff5a2c;
  font-weight: 600;
}

.review-summary-row {
  display: flex;
  flex-direction: row;
  align-items: baseline;
  margin-bottom: 18rpx;
}

.review-score {
  font-size: 50rpx;
  color: #ff5a2c;
  font-weight: 800;
  margin-right: 12rpx;
}

.review-score-label {
  font-size: 22rpx;
  color: #6f7680;
  margin-right: 20rpx;
}

.review-count {
  font-size: 22rpx;
  color: #6f7680;
}

.review-item {
  padding: 20rpx 0;
  border-top: 1rpx solid #f2f3f6;
}

.review-meta-row {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12rpx;
}

.review-user {
  font-size: 24rpx;
  color: #2a3038;
  font-weight: 600;
}

.review-stars {
  font-size: 22rpx;
  color: #ffb400;
}

.review-content {
  font-size: 24rpx;
  color: #4f5662;
  line-height: 36rpx;
}

.review-image-row {
  display: flex;
  flex-direction: row;
  margin-top: 14rpx;
}

.review-image {
  width: 132rpx;
  height: 132rpx;
  border-radius: 16rpx;
  margin-right: 12rpx;
  background-color: #f7f8fa;
}

.review-date {
  display: block;
  font-size: 20rpx;
  color: #98a0aa;
  margin-top: 14rpx;
}

.detail-text-block {
  padding: 20rpx;
  border-radius: 18rpx;
  background-color: #fafbfc;
  margin-bottom: 20rpx;
}

.detail-block-title {
  display: block;
  font-size: 26rpx;
  color: #232931;
  font-weight: 700;
  margin-bottom: 12rpx;
}

.detail-block-text {
  font-size: 24rpx;
  color: #5c6370;
  line-height: 36rpx;
}

.description-text {
  font-size: 26rpx;
  color: #575f6c;
  line-height: 40rpx;
}

.detail-images {
  margin-top: 8rpx;
}

.detail-image {
  width: 100%;
  margin-bottom: 18rpx;
  border-radius: 18rpx;
  background-color: #f7f8fa;
}

.empty-detail-state {
  padding: 40rpx 0 20rpx;
  display: flex;
  align-items: center;
  justify-content: center;
}

.empty-detail-text {
  font-size: 24rpx;
  color: #9aa1ab;
}

.bottom-actions {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ffffff;
  box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06);
  padding: 14rpx 20rpx calc(14rpx + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  z-index: 50;
}

.bottom-left-actions {
  display: flex;
  flex-direction: row;
  align-items: center;
  width: 300rpx;
  margin-right: 16rpx;
}

.bottom-action-item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.bottom-action-icon {
  width: 42rpx;
  height: 42rpx;
  margin-bottom: 6rpx;
}

.bottom-action-shop-icon {
  width: 42rpx;
  height: 42rpx;
  line-height: 42rpx;
  text-align: center;
  font-size: 28rpx;
  color: #5f6670;
  margin-bottom: 6rpx;
  border-width: 1rpx;
  border-style: solid;
  border-color: #d9dde3;
  border-radius: 10rpx;
  box-sizing: border-box;
}

.bottom-action-text {
  font-size: 20rpx;
  color: #68707a;
}

.cart-action-item {
  position: relative;
}

.cart-icon-wrap {
  position: relative;
  width: 42rpx;
  height: 42rpx;
  margin-bottom: 6rpx;
}

.cart-badge {
  position: absolute;
  top: -10rpx;
  right: -18rpx;
  min-width: 28rpx;
  height: 28rpx;
  line-height: 28rpx;
  border-radius: 14rpx;
  background-color: #e02e24;
  color: #ffffff;
  font-size: 18rpx;
  text-align: center;
  padding-left: 6rpx;
  padding-right: 6rpx;
  box-sizing: border-box;
}

.btn-group {
  flex: 1;
  display: flex;
  flex-direction: row;
  align-items: center;
}

.cart-btn,
.buy-btn {
  flex: 1;
  height: 78rpx;
  line-height: 78rpx;
  border-radius: 999rpx;
  font-size: 28rpx;
  font-weight: 700;
  border: none;
}

.cart-btn {
  background: linear-gradient(90deg, #ffb13d 0%, #ff8f1f 100%);
  color: #ffffff;
  margin-right: 14rpx;
}

.buy-btn {
  background: linear-gradient(90deg, #ff5d2c 0%, #ff3b30 100%);
  color: #ffffff;
}

.spec-modal,
.params-modal,
.popup-mask,
.share-popup-mask {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.48);
  display: flex;
  justify-content: flex-end;
  flex-direction: column;
  z-index: 1000;
}

.spec-content,
.params-content,
.popup-content,
.share-popup-content {
  background-color: #ffffff;
  width: 100%;
  border-radius: 28rpx 28rpx 0 0;
  padding: 30rpx;
  display: flex;
  flex-direction: column;
}

.spec-content {
  max-height: 80%;
  position: relative;
}

.params-content,
.popup-content {
  max-height: 1000rpx;
}

.share-popup-content {
  padding-bottom: calc(30rpx + env(safe-area-inset-bottom));
}

.spec-header-jd {
  display: flex;
  flex-direction: row;
  align-items: flex-end;
  position: relative;
  padding-bottom: 28rpx;
  border-bottom: 1rpx solid #f1f2f5;
}

.spec-product-img {
  width: 180rpx;
  height: 180rpx;
  border-radius: 18rpx;
  margin-top: -64rpx;
  background-color: #ffffff;
  border: 4rpx solid #ffffff;
  box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
}

.spec-info-jd {
  flex: 1;
  margin-left: 22rpx;
  padding-bottom: 8rpx;
}

.spec-price-row {
  display: flex;
  flex-direction: row;
  align-items: baseline;
  margin-bottom: 8rpx;
}

.spec-stock-jd,
.spec-choosed-jd {
  display: block;
  font-size: 24rpx;
  color: #6f7680;
  margin-bottom: 8rpx;
}

.close-btn-jd,
.close-btn {
  font-size: 48rpx;
  color: #9aa1ab;
}

.close-btn-jd {
  position: absolute;
  right: -10rpx;
  top: -10rpx;
  padding: 10rpx;
}

.spec-error-tip {
  position: absolute;
  top: -76rpx;
  left: 50%;
  transform: translateX(-50%);
  background-color: rgba(0, 0, 0, 0.72);
  padding: 12rpx 28rpx;
  border-radius: 999rpx;
  z-index: 2000;
}

.error-tip-text {
  color: #ffffff;
  font-size: 22rpx;
}

.spec-list-jd {
  flex: 1;
}

.spec-group {
  padding: 28rpx 0;
}

.group-title {
  font-size: 28rpx;
  color: #1f2329;
  font-weight: 700;
  margin-bottom: 22rpx;
  display: block;
}

.group-tags {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
}

.spec-tag {
  padding: 16rpx 28rpx;
  border-radius: 999rpx;
  background-color: #f6f7fb;
  border: 2rpx solid #f6f7fb;
  margin-right: 16rpx;
  margin-bottom: 16rpx;
}

.spec-tag.active {
  background-color: #fff1ed;
  border-color: #ff5a2c;
}

.spec-tag.active .tag-text {
  color: #ff5a2c;
}

.tag-text {
  font-size: 24rpx;
  color: #2d333b;
}

.spec-quantity-row {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  padding: 24rpx 0 12rpx;
}

.quantity-selector-jd {
  display: flex;
  flex-direction: row;
  align-items: center;
  background-color: #f6f7fb;
  border-radius: 12rpx;
}

.q-btn {
  width: 64rpx;
  height: 64rpx;
  display: flex;
  align-items: center;
  justify-content: center;
}

.q-btn-text {
  font-size: 34rpx;
  color: #2d333b;
}

.q-input {
  width: 88rpx;
  height: 64rpx;
  text-align: center;
  font-size: 28rpx;
  color: #2d333b;
  background-color: #ffffff;
}

.spec-footer-jd {
  display: flex;
  flex-direction: row;
  padding-top: 20rpx;
}

.footer-btn {
  flex: 1;
  height: 82rpx;
  line-height: 82rpx;
  border-radius: 999rpx;
  font-size: 28rpx;
  font-weight: 700;
  border: none;
}

.footer-btn.cart {
  background: linear-gradient(90deg, #ffb13d 0%, #ff8f1f 100%);
  color: #ffffff;
  margin-right: 14rpx;
}

.footer-btn.buy {
  background: linear-gradient(90deg, #ff5d2c 0%, #ff3b30 100%);
  color: #ffffff;
}

.params-header,
.popup-header,
.share-popup-header {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 28rpx;
  padding-bottom: 20rpx;
  border-bottom: 1rpx solid #f0f1f4;
}

.params-title,
.popup-title,
.share-popup-title {
  font-size: 32rpx;
  color: #1f2329;
  font-weight: 800;
}

.params-list,
.coupon-list-scroll {
  flex: 1;
}

.params-item {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  padding: 20rpx 0;
  border-bottom: 1rpx solid #f4f5f8;
}

.params-label {
  width: 150rpx;
  flex-shrink: 0;
  font-size: 26rpx;
  color: #2d333b;
  font-weight: 700;
}

.params-value {
  flex: 1;
  font-size: 26rpx;
  color: #616975;
  line-height: 38rpx;
}

.coupon-item {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  background-color: #fff5f2;
  border-radius: 20rpx;
  padding: 20rpx;
  margin-bottom: 20rpx;
}

.coupon-left {
  width: 180rpx;
  border-right: 1rpx dashed #ffd0c4;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: #ff5a2c;
}

.coupon-amount {
  font-size: 42rpx;
  font-weight: 800;
}

.symbol {
  font-size: 24rpx;
}

.coupon-cond {
  font-size: 22rpx;
  margin-top: 8rpx;
}

.coupon-right {
  flex: 1;
  padding-left: 20rpx;
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
}

.coupon-info-text {
  flex: 1;
}

.coupon-name {
  display: block;
  font-size: 28rpx;
  color: #262c34;
  font-weight: 700;
  margin-bottom: 10rpx;
}

.coupon-time {
  font-size: 22rpx;
  color: #9097a1;
}

.coupon-btn {
  height: 56rpx;
  line-height: 56rpx;
  border-radius: 999rpx;
  background-color: #ff5a2c;
  color: #ffffff;
  font-size: 24rpx;
  padding: 0 24rpx;
  margin: 0;
}

.share-options {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  padding: 10rpx 0;
}

.share-option {
  width: 25%;
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-bottom: 28rpx;
}

.share-icon-wrapper {
  width: 100rpx;
  height: 100rpx;
  border-radius: 999rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 14rpx;
}

.share-icon-wrapper.wechat,
.share-icon-wrapper.moments {
  background-color: #07c160;
}

.share-icon-wrapper.qq {
  background-color: #12b7f5;
}

.share-icon-wrapper.link {
  background-color: #ff9500;
}

.share-icon-wrapper.image {
  background-color: #ff2d55;
}

.share-icon-wrapper.poster {
  background-color: #5856d6;
}

.share-option-icon {
  font-size: 34rpx;
  color: #ffffff;
  font-weight: 700;
}

.share-option-text {
  font-size: 22rpx;
  color: #2b3139;
}

.share-cancel-btn {
  width: 100%;
  height: 88rpx;
  border-radius: 999rpx;
  background-color: #f4f5f8;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 12rpx;
}

.cancel-text {
  font-size: 28rpx;
  color: #2b3139;
  font-weight: 600;
}
</style>



