1501 lines
44 KiB
Plaintext
1501 lines
44 KiB
Plaintext
<!-- 消费者端 - 商品详情页 -->
|
||
<template>
|
||
<view class="product-detail-page">
|
||
<!-- 商品图片轮播 -->
|
||
<view class="product-images">
|
||
<swiper class="image-swiper" :indicator-dots="true" :autoplay="false" @change="onSwiperChange">
|
||
<swiper-item v-for="(image, index) in product.images" :key="index">
|
||
<image :src="image" class="product-image" mode="aspectFit" />
|
||
</swiper-item>
|
||
</swiper>
|
||
<view class="image-indicator">{{ currentImageIndex + 1 }} / {{ product.images.length }}</view>
|
||
</view>
|
||
|
||
<!-- 商品基本信息 -->
|
||
<view class="product-info">
|
||
<view class="price-section">
|
||
<text class="current-price">¥{{ product.price }}</text>
|
||
<text v-if="product.original_price" class="original-price">¥{{ product.original_price }}</text>
|
||
</view>
|
||
<text class="product-name">{{ product.name }}</text>
|
||
<text class="sales-info">已售{{ product.sales }}件 · 库存{{ product.stock }}件</text>
|
||
</view>
|
||
|
||
<!-- 店铺信息 -->
|
||
<view class="shop-info" @click="goToShop">
|
||
<image :src="merchant.shop_logo || '/static/default-shop.png'" class="shop-logo" />
|
||
<view class="shop-details">
|
||
<text class="shop-name" @click.stop="goToShop">{{ merchant.shop_name }}</text>
|
||
<view class="shop-stats-row">
|
||
<text class="rating-text">评分: {{ merchant.rating.toFixed(1) }}</text>
|
||
<text class="sales-text">销量: {{ merchant.total_sales }}</text>
|
||
</view>
|
||
</view>
|
||
<text class="enter-shop" @click.stop="goToShop">进店 ></text>
|
||
</view>
|
||
|
||
<!-- 功能主治(药品功能) -->
|
||
<view class="function-section" v-if="product.usage">
|
||
<text class="function-title">功能主治</text>
|
||
<text class="function-content">{{ product.usage }}</text>
|
||
</view>
|
||
|
||
<!-- 商品参数 -->
|
||
<view class="params-section" @click="showParamsModal">
|
||
<text class="params-title">商品参数</text>
|
||
<view class="params-summary">
|
||
<text class="params-item" v-if="product.specification">规格: {{ product.specification }}</text>
|
||
<text class="params-item" v-if="product.expiry_date">有效期: {{ product.expiry_date }}</text>
|
||
<text class="params-item" v-if="product.approval_number">批准文号: {{ product.approval_number }}</text>
|
||
</view>
|
||
<text class="params-arrow">></text>
|
||
</view>
|
||
|
||
<!-- 规格选择 -->
|
||
<view class="spec-section" @click="showSpecModal">
|
||
<text class="spec-title">规格</text>
|
||
<text class="spec-selected">{{ selectedSpec || '请选择规格' }}</text>
|
||
<text class="spec-arrow">></text>
|
||
</view>
|
||
|
||
<!-- 数量选择 -->
|
||
<view class="quantity-section">
|
||
<text class="quantity-title">数量</text>
|
||
<view class="quantity-selector">
|
||
<view class="quantity-btn minus" @click="decreaseQuantity">
|
||
<text class="quantity-btn-text">-</text>
|
||
</view>
|
||
<input class="quantity-input"
|
||
type="number"
|
||
v-model="quantity"
|
||
:min="1"
|
||
:max="getMaxQuantity()"
|
||
@input="validateQuantity" />
|
||
<view class="quantity-btn plus" @click="increaseQuantity">
|
||
<text class="quantity-btn-text">+</text>
|
||
</view>
|
||
</view>
|
||
<text class="quantity-stock">库存{{ getAvailableStock() }}件</text>
|
||
</view>
|
||
|
||
<!-- 商品详情 -->
|
||
<view class="product-description">
|
||
<view class="section-title">商品详情</view>
|
||
<text class="description-text">{{ product.description || '暂无详细描述' }}</text>
|
||
<!-- 商品详情图片 -->
|
||
<view class="detail-images" v-if="product.images && product.images.length > 0">
|
||
<image v-for="(img, index) in product.images"
|
||
:key="index"
|
||
:src="img"
|
||
class="detail-image"
|
||
mode="widthFix"
|
||
@click="previewImage(index)" />
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部操作栏 -->
|
||
<view class="bottom-actions">
|
||
<view class="action-buttons">
|
||
<view class="action-btn" @click="goToCart">
|
||
<text class="action-icon">🛒</text>
|
||
<text class="action-text">购物车</text>
|
||
</view>
|
||
<view class="action-btn" @click="toggleFavorite">
|
||
<text class="action-icon">{{ isFavorite ? '❤️' : '🤍' }}</text>
|
||
<text class="action-text">{{ isFavorite ? '已收藏' : '收藏' }}</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 class="spec-header">
|
||
<text class="spec-title">选择规格</text>
|
||
<text class="close-btn" @click="hideSpecModal">×</text>
|
||
</view>
|
||
<view class="spec-list">
|
||
<view v-for="sku in productSkus" :key="sku.id"
|
||
class="spec-item"
|
||
:class="{ active: selectedSkuId === sku.id }"
|
||
@click="selectSku(sku)">
|
||
<text class="spec-name">{{ getSkuSpecText(sku) }}</text>
|
||
<text class="spec-price">¥{{ sku.price }}</text>
|
||
<text class="spec-stock">库存{{ sku.stock }}</text>
|
||
</view>
|
||
</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>
|
||
<view class="params-list">
|
||
<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 && product.tags.length > 0">
|
||
<text class="params-label">标签</text>
|
||
<text class="params-value">{{ product.tags.join(', ') }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { ProductType, MerchantType, ProductSkuType } from '@/types/mall-types.uts'
|
||
import { supabaseService } from '@/utils/supabaseService.uts'
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
product: {
|
||
id: '',
|
||
merchant_id: '',
|
||
category_id: '',
|
||
name: '',
|
||
description: '',
|
||
images: [] as Array<string>,
|
||
price: 0,
|
||
original_price: 0,
|
||
stock: 0,
|
||
sales: 0,
|
||
status: 0,
|
||
created_at: ''
|
||
} 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,
|
||
isFavorite: false,
|
||
showParams: false
|
||
}
|
||
},
|
||
onLoad(options: any) {
|
||
const productId = options.productId as string || options.id as string
|
||
const productPrice = options.price ? parseFloat(options.price) : null
|
||
const productOriginalPrice = options.originalPrice ? parseFloat(options.originalPrice) : null
|
||
|
||
// 处理商品名称:如果是编码的则解码,否则直接使用
|
||
let productName = options.name as string
|
||
if (productName) {
|
||
try {
|
||
// 尝试解码,如果失败(不是有效的URI组件)则使用原值
|
||
productName = decodeURIComponent(productName)
|
||
} catch (e) {
|
||
console.warn('ProductName decode failed, using original:', productName)
|
||
}
|
||
}
|
||
|
||
let productImage = options.image as string
|
||
if (productImage) {
|
||
try {
|
||
productImage = decodeURIComponent(productImage)
|
||
} catch (e) {
|
||
console.warn('ProductImage decode failed, using original:', productImage)
|
||
}
|
||
}
|
||
|
||
if (productId) {
|
||
this.loadProductDetail(productId, {
|
||
price: productPrice,
|
||
originalPrice: productOriginalPrice,
|
||
name: productName,
|
||
image: productImage
|
||
})
|
||
this.checkFavoriteStatus(productId)
|
||
this.saveFootprint(productId)
|
||
|
||
// 设置导航栏标题为商品名称
|
||
if (productName) {
|
||
uni.setNavigationBarTitle({
|
||
title: productName
|
||
})
|
||
}
|
||
}
|
||
},
|
||
computed: {
|
||
displayPrice(): number {
|
||
if (this.selectedSkuId) {
|
||
const sku = this.productSkus.find(s => s.id === this.selectedSkuId)
|
||
if (sku) return sku.price
|
||
}
|
||
return this.product.price
|
||
}
|
||
},
|
||
methods: {
|
||
saveFootprint(productId: string) {
|
||
const footprintData = uni.getStorageSync('footprints')
|
||
let footprints: any[] = []
|
||
|
||
if (footprintData) {
|
||
try {
|
||
footprints = JSON.parse(footprintData as string) as any[]
|
||
} catch (e) {
|
||
console.error('Failed to parse footprints', e)
|
||
}
|
||
}
|
||
|
||
// 移除已存在的相同商品(为了将其移到最新位置)
|
||
footprints = footprints.filter(item => item.id !== productId)
|
||
|
||
// 添加到头部
|
||
footprints.unshift({
|
||
id: this.product.id,
|
||
name: this.product.name,
|
||
price: this.product.price,
|
||
original_price: this.product.original_price, // 添加原价
|
||
image: this.product.images[0],
|
||
sales: this.product.sales,
|
||
shopId: this.merchant.id,
|
||
shopName: this.merchant.shop_name,
|
||
viewTime: Date.now()
|
||
})
|
||
|
||
// 限制数量,例如最近50条
|
||
if (footprints.length > 50) {
|
||
footprints = footprints.slice(0, 50)
|
||
}
|
||
uni.setStorageSync('footprints', JSON.stringify(footprints))
|
||
},
|
||
|
||
async loadProductDetail(productId: string, options: any = {}) {
|
||
// 尝试从数据库加载
|
||
let dbProductRaw = null
|
||
try {
|
||
console.log('正在尝试从数据库加载商品详情:', productId)
|
||
dbProductRaw = await supabaseService.getProductById(productId)
|
||
console.log('数据库返回的商品详情 (原始数据):', dbProductRaw)
|
||
|
||
// 调试:打印数据库返回的所有字段
|
||
if (dbProductRaw) {
|
||
console.log('数据库返回字段详情:')
|
||
if (Array.isArray(dbProductRaw)) {
|
||
console.log('返回数据是数组,长度:', dbProductRaw.length)
|
||
if (dbProductRaw.length > 0) {
|
||
const firstItem = dbProductRaw[0]
|
||
console.log('数组第一个元素:', firstItem)
|
||
for (const key in firstItem) {
|
||
console.log(` ${key}:`, firstItem[key], typeof firstItem[key])
|
||
}
|
||
}
|
||
} else {
|
||
console.log('返回数据是对象')
|
||
for (const key in dbProductRaw) {
|
||
console.log(` ${key}:`, dbProductRaw[key], typeof dbProductRaw[key])
|
||
}
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.error('Failed to load product from DB', e)
|
||
}
|
||
|
||
// 处理数据库返回数据:可能是数组或对象
|
||
let dbProduct = null
|
||
if (dbProductRaw) {
|
||
if (Array.isArray(dbProductRaw)) {
|
||
if (dbProductRaw.length > 0) {
|
||
dbProduct = dbProductRaw[0] // 取数组第一个元素
|
||
} else {
|
||
console.warn('数据库返回空数组')
|
||
}
|
||
} else {
|
||
dbProduct = dbProductRaw // 已经是对象
|
||
}
|
||
}
|
||
|
||
if (dbProduct) {
|
||
console.log('使用数据库数据渲染页面')
|
||
|
||
// 调试:打印dbProduct的详细结构和类型
|
||
console.log('dbProduct类型:', typeof dbProduct)
|
||
console.log('dbProduct原型:', Object.getPrototypeOf(dbProduct))
|
||
console.log('dbProduct的键:')
|
||
for (let key in dbProduct) {
|
||
console.log(' ', key, ':', dbProduct[key], '类型:', typeof dbProduct[key])
|
||
}
|
||
|
||
// 验证必要字段,如果关键字段缺失则使用模拟数据
|
||
// 注意:数据库返回的字段可能与本地ProductType不完全匹配
|
||
console.log('验证必要字段,dbProduct:', dbProduct)
|
||
|
||
// 尝试多种方式访问属性
|
||
const idValue = dbProduct.id !== undefined ? dbProduct.id : (dbProduct['id'] !== undefined ? dbProduct['id'] : undefined)
|
||
const nameValue = dbProduct.name !== undefined ? dbProduct.name : (dbProduct['name'] !== undefined ? dbProduct['name'] : undefined)
|
||
|
||
// 价格字段兼容性处理:优先查找 price,其次查找 base_price
|
||
let priceValue = dbProduct.price
|
||
if (priceValue === undefined || priceValue === null) {
|
||
priceValue = dbProduct.base_price
|
||
}
|
||
if (priceValue === undefined || priceValue === null) {
|
||
priceValue = dbProduct['price']
|
||
}
|
||
if (priceValue === undefined || priceValue === null) {
|
||
priceValue = dbProduct['base_price']
|
||
}
|
||
|
||
const hasId = idValue !== undefined && idValue !== null
|
||
const hasName = nameValue !== undefined && nameValue !== null
|
||
const hasPrice = priceValue !== undefined && priceValue !== null
|
||
|
||
const hasRequiredFields = dbProduct && hasId && hasName && hasPrice
|
||
console.log('字段检查 - id:', idValue, 'hasId:', hasId, 'name:', nameValue, 'hasName:', hasName, 'price:', priceValue, 'hasPrice:', hasPrice)
|
||
console.log('hasRequiredFields:', hasRequiredFields)
|
||
|
||
if (!hasRequiredFields) {
|
||
console.warn('数据库返回数据缺少必要字段,使用模拟数据')
|
||
// 继续执行,会进入下面的else分支
|
||
dbProduct = null
|
||
} else {
|
||
// 更新dbProduct的字段为实际值,确保后续使用正确的属性访问
|
||
if (dbProduct.id === undefined && idValue !== undefined) dbProduct.id = idValue
|
||
if (dbProduct.name === undefined && nameValue !== undefined) dbProduct.name = nameValue
|
||
if (dbProduct.price === undefined && priceValue !== undefined) dbProduct.price = priceValue
|
||
// 使用数据库数据 - 处理字段映射
|
||
// 数据库Product接口和本地ProductType接口字段可能不同
|
||
const images = [] as Array<string>
|
||
|
||
// 处理图片字段:优先使用image_urls字段,其次使用main_image_url
|
||
console.log('处理数据库图片字段')
|
||
|
||
// 尝试从数据库的image_urls字段获取图片(JSON字符串或对象)
|
||
if (dbProduct.image_urls) {
|
||
let imagesArray: any[] = []
|
||
if (typeof dbProduct.image_urls === 'string') {
|
||
try {
|
||
imagesArray = JSON.parse(dbProduct.image_urls)
|
||
} catch (e) {
|
||
console.error('解析image_urls字段失败:', e, dbProduct.image_urls)
|
||
// 尝试逗号分割
|
||
if (dbProduct.image_urls.includes(',')) {
|
||
imagesArray = dbProduct.image_urls.split(',').map((img: string) => img.trim())
|
||
}
|
||
}
|
||
} else if (Array.isArray(dbProduct.image_urls)) {
|
||
imagesArray = dbProduct.image_urls
|
||
}
|
||
|
||
if (imagesArray.length > 0) {
|
||
for (const img of imagesArray) {
|
||
if (typeof img === 'string' && img) {
|
||
images.push(img)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果没有获取到相册图,但有主图,放入相册
|
||
if (dbProduct.main_image_url) {
|
||
// 如果相册里没有这张图,把它加到第一位
|
||
if (!images.includes(dbProduct.main_image_url)) {
|
||
images.unshift(dbProduct.main_image_url)
|
||
}
|
||
}
|
||
|
||
// 兼容旧字段 image
|
||
if (images.length === 0 && dbProduct.image) {
|
||
images.push(dbProduct.image)
|
||
}
|
||
|
||
// 如果仍然没有图片,使用传入的图片或默认图片
|
||
if (images.length === 0) {
|
||
if (options.image) {
|
||
images.push(decodeURIComponent(options.image as string))
|
||
} else {
|
||
images.push('/static/product1.jpg')
|
||
}
|
||
}
|
||
|
||
// 补充模拟图片(如果图片数量不足3张)
|
||
const needSupplementCount = 3 - images.length
|
||
if (needSupplementCount > 0) {
|
||
const supplementalImages = ['/static/product2.jpg', '/static/product3.jpg']
|
||
for (let i = 0; i < needSupplementCount && i < supplementalImages.length; i++) {
|
||
images.push(supplementalImages[i])
|
||
}
|
||
}
|
||
|
||
console.log('最终图片数组:', images)
|
||
|
||
// 映射字段:数据库shop_id对应本地merchant_id
|
||
const merchantId = dbProduct.shop_id || dbProduct.merchant_id || 'merchant_001'
|
||
|
||
// 确保数值字段有效
|
||
// 优先使用 price,不存在则使用 base_price
|
||
let productPrice = 0
|
||
if (typeof dbProduct.price === 'number') {
|
||
productPrice = dbProduct.price
|
||
} else if (typeof dbProduct.base_price === 'number') {
|
||
productPrice = dbProduct.base_price
|
||
} else if (priceValue !== undefined) {
|
||
// 使用上面校验时获取到的 priceValue
|
||
productPrice = Number(priceValue)
|
||
}
|
||
|
||
const stock = (dbProduct.stock != null && !isNaN(Number(dbProduct.stock))) ? Math.floor(Number(dbProduct.stock)) : ((dbProduct.total_stock != null && !isNaN(Number(dbProduct.total_stock))) ? Math.floor(Number(dbProduct.total_stock)) : 100)
|
||
const sales = (dbProduct.sales != null && !isNaN(Number(dbProduct.sales))) ? Math.floor(Number(dbProduct.sales)) : ((dbProduct.sale_count != null && !isNaN(Number(dbProduct.sale_count))) ? Math.floor(Number(dbProduct.sale_count)) : 50)
|
||
|
||
// 解析 attributes
|
||
let attributes: any = {}
|
||
if (dbProduct.attributes) {
|
||
try {
|
||
if (typeof dbProduct.attributes === 'string') {
|
||
attributes = JSON.parse(dbProduct.attributes)
|
||
} else {
|
||
attributes = dbProduct.attributes
|
||
}
|
||
} catch (e) {
|
||
console.error('解析 attributes 失败', e)
|
||
}
|
||
}
|
||
|
||
this.product = {
|
||
id: dbProduct.id || productId,
|
||
merchant_id: merchantId,
|
||
category_id: dbProduct.category_id || 'cat_001',
|
||
name: dbProduct.name || '商品名称',
|
||
description: dbProduct.description || '这是一个高品质的商品,具有优秀的性能和优美的外观设计。采用环保材料,经过严格质检,保证用户的使用体验。',
|
||
images: images,
|
||
price: productPrice,
|
||
original_price: (dbProduct.original_price != null && !isNaN(Number(dbProduct.original_price))) ? Number(dbProduct.original_price) : ((dbProduct.market_price != null && !isNaN(Number(dbProduct.market_price))) ? Number(dbProduct.market_price) : null),
|
||
stock: stock,
|
||
sales: sales,
|
||
status: 1,
|
||
created_at: dbProduct.created_at || '2024-01-01',
|
||
// 药品相关字段
|
||
specification: attributes.specification || dbProduct.specification || null,
|
||
usage: attributes.usage || dbProduct.usage || null,
|
||
side_effects: attributes.side_effects || dbProduct.side_effects || null,
|
||
precautions: attributes.precautions || dbProduct.precautions || null,
|
||
expiry_date: attributes.expiry_date || dbProduct.expiry_date || null,
|
||
storage_conditions: attributes.storage_conditions || dbProduct.storage_conditions || null,
|
||
approval_number: attributes.approval_number || dbProduct.approval_number || null,
|
||
tags: dbProduct.tags ? (typeof dbProduct.tags === 'string' ? JSON.parse(dbProduct.tags) : dbProduct.tags) : []
|
||
} as ProductType
|
||
console.log('页面 product 对象已更新:', this.product)
|
||
console.log('商品图片数组:', this.product.images)
|
||
console.log('商品价格:', this.product.price, '库存:', this.product.stock, '销量:', this.product.sales)
|
||
}
|
||
} else {
|
||
console.log('数据库无数据或加载失败,使用模拟数据')
|
||
// 数据库无数据时,使用原有模拟逻辑
|
||
const generatePriceFromId = (id: string): number => {
|
||
let hash = 0
|
||
for (let i = 0; i < id.length; i++) {
|
||
hash = (hash << 5) - hash + id.charCodeAt(i)
|
||
hash |= 0
|
||
}
|
||
const price = 50 + Math.abs(hash % 450)
|
||
return parseFloat(price.toFixed(2))
|
||
}
|
||
|
||
const basePrice = options.price ? parseFloat(options.price) : generatePriceFromId(productId)
|
||
const originalPrice = options.originalPrice ? parseFloat(options.originalPrice) : parseFloat((basePrice * 1.2).toFixed(2))
|
||
|
||
const productName = options.name ? decodeURIComponent(options.name) : (() => {
|
||
const productNames = ['高品质运动休闲鞋', '时尚简约双肩背包', '多功能智能手环', '便携式蓝牙音箱', '全自动雨伞', '抗菌防螨床上四件套', '不锈钢保温杯', '无线充电器', '高清行车记录仪', '智能体脂秤']
|
||
const nameIndex = Math.abs(productId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % productNames.length
|
||
return productNames[nameIndex]
|
||
})()
|
||
|
||
const productImage = options.image ? decodeURIComponent(options.image) : '/static/product1.jpg'
|
||
|
||
const sales = 1000 + Math.abs(productId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % 5000
|
||
const stock = 50 + Math.abs(productId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % 200
|
||
|
||
this.product = {
|
||
id: productId,
|
||
merchant_id: 'merchant_' + (Math.abs(productId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % 5 + 1).toString().padStart(3, '0'),
|
||
category_id: 'cat_' + (Math.abs(productId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % 10 + 1).toString().padStart(3, '0'),
|
||
name: productName,
|
||
description: '这是一个高品质的商品,具有优秀的性能和优美的外观设计。采用环保材料,经过严格质检,保证用户的使用体验。',
|
||
images: [productImage, '/static/product2.jpg', '/static/product3.jpg'],
|
||
price: basePrice,
|
||
original_price: originalPrice,
|
||
stock: stock,
|
||
sales: sales,
|
||
status: 1,
|
||
created_at: '2024-01-15'
|
||
}
|
||
}
|
||
|
||
// 尝试加载真实商户信息
|
||
let realMerchantLoaded = false
|
||
// 只有当 ID 是 UUID 格式(包含-)或者是真实数据时才尝试查询
|
||
if (this.product.merchant_id && (this.product.merchant_id.includes('-') || !this.product.merchant_id.startsWith('merchant_'))) {
|
||
console.log('尝试加载商户信息:', this.product.merchant_id)
|
||
try {
|
||
const shop = await supabaseService.getShopByMerchantId(this.product.merchant_id)
|
||
if (shop) {
|
||
console.log('加载到商户信息:', shop.shop_name)
|
||
|
||
// 确保字段存在,避免 undefined 导致构造失败
|
||
this.merchant = {
|
||
id: shop.id || '',
|
||
user_id: shop.merchant_id || '',
|
||
shop_name: shop.shop_name || '未命名店铺',
|
||
shop_logo: shop.shop_logo || '/static/default-shop.png',
|
||
shop_banner: shop.shop_banner || '/static/default-banner.png',
|
||
shop_description: shop.description || '',
|
||
contact_name: shop.contact_name || '店主',
|
||
contact_phone: shop.contact_phone || '',
|
||
shop_status: 1,
|
||
// 优先使用 avg_rating,没有则使用默认值
|
||
rating: shop.rating_avg !== undefined && shop.rating_avg !== null ? shop.rating_avg : 4.8,
|
||
// 使用 order_count 或 product_count 作为销量/活跃度指标,如果没有则默认 0
|
||
total_sales: shop.total_sales !== undefined ? shop.total_sales : (shop.order_count !== undefined ? shop.order_count : 0),
|
||
created_at: shop.created_at || new Date().toISOString()
|
||
} as MerchantType
|
||
realMerchantLoaded = true
|
||
}
|
||
} catch (e) {
|
||
console.error('加载商户信息失败', e)
|
||
}
|
||
}
|
||
|
||
if (!realMerchantLoaded) {
|
||
// 根据商家ID生成不同的商家信息
|
||
const merchantIndex = Math.abs(this.product.merchant_id.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % 5
|
||
const shopNames = ['优质好店', '品牌直营店', '官方旗舰店', '专卖店', '精品小店']
|
||
const shopDescriptions = [
|
||
'专注品质生活',
|
||
'品牌官方直营,正品保障',
|
||
'厂家直销,价格优惠',
|
||
'专注本领域十年老店',
|
||
'用心服务每一位顾客'
|
||
]
|
||
const contactNames = ['店主小王', '店长小李', '经理小张', '客服小赵', '老板小钱']
|
||
|
||
this.merchant = {
|
||
id: this.product.merchant_id,
|
||
user_id: 'user_' + (merchantIndex + 1).toString().padStart(3, '0'),
|
||
shop_name: shopNames[merchantIndex],
|
||
shop_logo: '/static/shop-logo.png',
|
||
shop_banner: '/static/shop-banner.png',
|
||
shop_description: shopDescriptions[merchantIndex],
|
||
contact_name: contactNames[merchantIndex],
|
||
contact_phone: '138' + (10000000 + merchantIndex * 1111111).toString().substring(0, 8),
|
||
shop_status: 1,
|
||
rating: 4.5 + (merchantIndex * 0.1),
|
||
total_sales: 10000 + merchantIndex * 5000,
|
||
created_at: '2023-06-01'
|
||
}
|
||
}
|
||
|
||
this.loadProductSkus(productId)
|
||
},
|
||
|
||
async loadProductSkus(productId: string) {
|
||
// 尝试从数据库加载SKU
|
||
try {
|
||
const skus = await supabaseService.getProductSkus(productId)
|
||
if (skus.length > 0) {
|
||
console.log('加载到商品SKU:', skus.length)
|
||
this.productSkus = skus.map((sku): ProductSkuType => {
|
||
let specs: UTSJSONObject = {}
|
||
if (sku.specifications) {
|
||
try {
|
||
if (typeof sku.specifications === 'string') {
|
||
specs = JSON.parse(sku.specifications) as UTSJSONObject
|
||
} else {
|
||
// 假设已经是对象
|
||
specs = sku.specifications as unknown as UTSJSONObject
|
||
}
|
||
} catch(e) {
|
||
console.error('解析SKU规格失败', e)
|
||
}
|
||
}
|
||
return {
|
||
id: sku.id,
|
||
product_id: sku.product_id,
|
||
sku_code: sku.sku_code,
|
||
specifications: specs,
|
||
price: sku.price,
|
||
stock: sku.stock !== undefined ? sku.stock : 0,
|
||
image_url: sku.image_url || '',
|
||
status: sku.status !== undefined ? sku.status : 1
|
||
} as ProductSkuType
|
||
})
|
||
return
|
||
}
|
||
} catch (e) {
|
||
console.error('Fetch SKUs error', e)
|
||
}
|
||
|
||
// 模拟加载商品SKU数据
|
||
const basePrice = this.product.price
|
||
|
||
// 使用 productId 作为前缀生成唯一的 SKU ID,防止不同商品的 SKU ID 冲突
|
||
this.productSkus = [
|
||
{
|
||
id: `${productId}_sku_001`,
|
||
product_id: productId,
|
||
sku_code: 'SKU001',
|
||
specifications: { color: '红色', size: 'M' },
|
||
price: basePrice,
|
||
stock: 50,
|
||
image_url: '/static/sku1.jpg',
|
||
status: 1
|
||
},
|
||
{
|
||
id: `${productId}_sku_002`,
|
||
product_id: productId,
|
||
sku_code: 'SKU002',
|
||
specifications: { color: '蓝色', size: 'L' },
|
||
price: parseFloat((basePrice * 1.1).toFixed(2)),
|
||
stock: 30,
|
||
image_url: '/static/sku2.jpg',
|
||
status: 1
|
||
}
|
||
]
|
||
},
|
||
|
||
onSwiperChange(e: any) {
|
||
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)
|
||
this.hideSpecModal()
|
||
},
|
||
|
||
getSkuSpecText(sku: ProductSkuType): string {
|
||
if (sku.specifications) {
|
||
const specs: any = sku.specifications
|
||
return Object.keys(specs).map(key => `${key}: ${specs[key]}`).join(', ')
|
||
}
|
||
return sku.sku_code
|
||
},
|
||
|
||
async addToCart() {
|
||
if (!this.selectedSkuId) {
|
||
uni.showToast({
|
||
title: '请选择规格',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
// 显示加载中
|
||
uni.showLoading({
|
||
title: '添加中...'
|
||
})
|
||
|
||
try {
|
||
// 调用 Supabase 服务添加到购物车
|
||
// 传递 productId, quantity, skuId
|
||
const success = await supabaseService.addToCart(
|
||
this.product.id,
|
||
this.quantity,
|
||
this.selectedSkuId
|
||
)
|
||
|
||
uni.hideLoading()
|
||
|
||
if (success) {
|
||
uni.showToast({
|
||
title: '已添加到购物车',
|
||
icon: 'success'
|
||
})
|
||
} else {
|
||
console.error('添加购物车返回失败')
|
||
uni.showToast({
|
||
title: '添加失败,请登录重试',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
} catch (e) {
|
||
uni.hideLoading()
|
||
console.error('添加购物车异常', e)
|
||
uni.showToast({
|
||
title: '添加异常',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
},
|
||
|
||
buyNow() {
|
||
if (!this.selectedSkuId) {
|
||
uni.showToast({
|
||
title: '请选择规格',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
const sku = this.productSkus.find(s => s.id === this.selectedSkuId)
|
||
|
||
// 调试:打印价格信息
|
||
console.log('立即购买 - 商品价格信息:')
|
||
console.log('SKU价格:', sku ? sku.price : '无SKU')
|
||
console.log('商品价格:', this.product.price)
|
||
console.log('选择的价格:', (sku ? sku.price : this.product.price))
|
||
console.log('数量:', this.quantity)
|
||
|
||
const selectedItem = {
|
||
id: this.selectedSkuId,
|
||
product_id: this.product.id,
|
||
sku_id: this.selectedSkuId,
|
||
product_name: this.product.name,
|
||
product_image: (sku && sku.image_url) ? sku.image_url : this.product.images[0],
|
||
sku_specifications: sku ? sku.specifications : {},
|
||
price: Number(parseFloat((sku ? sku.price : this.product.price).toString()).toFixed(2)),
|
||
quantity: Number(this.quantity)
|
||
}
|
||
|
||
// 调试:打印最终传递的数据
|
||
console.log('立即购买 - 传递的商品数据:', selectedItem)
|
||
|
||
// 使用Storage传递数据,避免EventChannel可能的问题
|
||
uni.setStorageSync('checkout_type', 'buy_now')
|
||
uni.setStorageSync('checkout_items', JSON.stringify([selectedItem]))
|
||
|
||
// 跳转到订单确认页
|
||
uni.navigateTo({
|
||
url: '/pages/mall/consumer/checkout',
|
||
success: (res) => {
|
||
res.eventChannel.emit('acceptData', {
|
||
selectedItems: [selectedItem]
|
||
})
|
||
}
|
||
})
|
||
},
|
||
|
||
goToShop() {
|
||
uni.navigateTo({
|
||
url: `/pages/mall/consumer/shop-detail?merchantId=${this.merchant.id}`
|
||
})
|
||
},
|
||
|
||
checkFavoriteStatus(id: string) {
|
||
const storedFavorites = uni.getStorageSync('favorites')
|
||
if (storedFavorites) {
|
||
try {
|
||
const favorites = JSON.parse(storedFavorites as string) as any[]
|
||
this.isFavorite = favorites.some(item => item.id === id)
|
||
} catch (e) {
|
||
console.error('Failed to parse favorites', e)
|
||
}
|
||
}
|
||
},
|
||
|
||
toggleFavorite() {
|
||
const storedFavorites = uni.getStorageSync('favorites')
|
||
let favorites: any[] = []
|
||
|
||
if (storedFavorites) {
|
||
try {
|
||
favorites = JSON.parse(storedFavorites as string) as any[]
|
||
} catch (e) {
|
||
console.error('Failed to parse favorites', e)
|
||
}
|
||
}
|
||
|
||
if (this.isFavorite) {
|
||
// 取消收藏
|
||
favorites = favorites.filter(item => item.id !== this.product.id)
|
||
uni.showToast({
|
||
title: '已取消收藏',
|
||
icon: 'none'
|
||
})
|
||
} else {
|
||
// 添加收藏
|
||
favorites.push({
|
||
id: this.product.id,
|
||
name: this.product.name,
|
||
price: this.product.price,
|
||
original_price: this.product.original_price, // 保存原价
|
||
image: this.product.images[0],
|
||
sales: this.product.sales,
|
||
shopId: this.merchant.id,
|
||
shopName: this.merchant.shop_name
|
||
})
|
||
uni.showToast({
|
||
title: '收藏成功',
|
||
icon: 'success'
|
||
})
|
||
}
|
||
|
||
uni.setStorageSync('favorites', JSON.stringify(favorites))
|
||
this.isFavorite = !this.isFavorite
|
||
},
|
||
|
||
goToHome() {
|
||
uni.switchTab({
|
||
url: '/pages/mall/consumer/home'
|
||
})
|
||
},
|
||
|
||
goToShop() {
|
||
if (this.merchant.user_id) {
|
||
uni.navigateTo({
|
||
url: `/pages/mall/consumer/shop-detail?merchantId=${this.merchant.user_id}`
|
||
})
|
||
}
|
||
},
|
||
|
||
goToCart() {
|
||
uni.switchTab({
|
||
url: '/pages/mall/consumer/cart'
|
||
})
|
||
},
|
||
|
||
// 数量选择相关方法
|
||
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() {
|
||
// 确保数量是数字
|
||
let num = parseInt(this.quantity)
|
||
if (isNaN(num)) {
|
||
num = 1
|
||
}
|
||
|
||
// 限制在1和最大库存之间
|
||
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() {
|
||
// 如果有选择SKU,使用SKU的库存,否则使用商品总库存
|
||
if (this.selectedSkuId) {
|
||
const sku = this.productSkus.find(s => s.id === this.selectedSkuId)
|
||
if (sku) return sku.stock
|
||
}
|
||
return this.product.stock
|
||
},
|
||
|
||
getAvailableStock() {
|
||
return this.getMaxQuantity()
|
||
},
|
||
|
||
previewImage(index: number) {
|
||
uni.previewImage({
|
||
current: index,
|
||
urls: this.product.images
|
||
})
|
||
},
|
||
|
||
showParamsModal() {
|
||
this.showParams = true
|
||
},
|
||
|
||
hideParamsModal() {
|
||
this.showParams = false
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
.product-detail-page {
|
||
background-color: #f5f5f5;
|
||
min-height: 100vh;
|
||
padding-bottom: 120rpx;
|
||
}
|
||
|
||
.product-images {
|
||
position: relative;
|
||
height: 750rpx;
|
||
background-color: #fff;
|
||
}
|
||
|
||
.image-swiper {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.product-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.image-indicator {
|
||
position: absolute;
|
||
bottom: 20rpx;
|
||
right: 20rpx;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
color: #fff;
|
||
padding: 10rpx 20rpx;
|
||
border-radius: 20rpx;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.product-info {
|
||
background-color: #fff;
|
||
padding: 30rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.price-section {
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.current-price {
|
||
font-size: 48rpx;
|
||
font-weight: bold;
|
||
color: #ff4444;
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.original-price {
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
text-decoration: line-through;
|
||
}
|
||
|
||
.product-name {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
line-height: 1.4;
|
||
margin-bottom: 15rpx;
|
||
}
|
||
|
||
.sales-info {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.shop-info {
|
||
background-color: #fff;
|
||
padding: 30rpx;
|
||
margin-bottom: 20rpx;
|
||
display: flex;
|
||
flex-direction: row; /* 显式横向排列 */
|
||
align-items: center;
|
||
}
|
||
|
||
.shop-logo {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 10rpx;
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.shop-details {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column; /* 内部信息保持纵向,或者根据需要改为横向 */
|
||
justify-content: center;
|
||
}
|
||
|
||
.shop-name {
|
||
font-size: 30rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 10rpx;
|
||
}
|
||
|
||
.shop-stats-row {
|
||
display: flex;
|
||
flex-direction: row; /* 显式横向排列 */
|
||
align-items: center;
|
||
}
|
||
|
||
.rating-text, .sales-text {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
margin-right: 30rpx;
|
||
}
|
||
|
||
.enter-shop {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.spec-section {
|
||
background-color: #fff;
|
||
padding: 30rpx;
|
||
margin-bottom: 20rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.spec-title {
|
||
font-size: 30rpx;
|
||
color: #333;
|
||
width: 120rpx;
|
||
}
|
||
|
||
.spec-selected {
|
||
flex: 1;
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.spec-arrow {
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.quantity-section {
|
||
background-color: #fff;
|
||
padding: 30rpx;
|
||
margin-bottom: 20rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.quantity-title {
|
||
font-size: 30rpx;
|
||
color: #333;
|
||
width: 120rpx;
|
||
}
|
||
|
||
.quantity-selector {
|
||
display: flex;
|
||
align-items: center;
|
||
border: 1rpx solid #e5e5e5;
|
||
border-radius: 8rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.quantity-btn {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.quantity-btn.minus {
|
||
border-right: 1rpx solid #e5e5e5;
|
||
}
|
||
|
||
.quantity-btn.plus {
|
||
border-left: 1rpx solid #e5e5e5;
|
||
}
|
||
|
||
.quantity-btn-text {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.quantity-input {
|
||
width: 80rpx;
|
||
height: 60rpx;
|
||
text-align: center;
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
border: none;
|
||
background-color: #fff;
|
||
}
|
||
|
||
.quantity-stock {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.product-description {
|
||
background-color: #fff;
|
||
padding: 30rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.description-text {
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.bottom-actions {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
background-color: #fff;
|
||
padding: 10rpx 20rpx;
|
||
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
|
||
display: flex;
|
||
flex-direction: row; /* 显式设置横向排列 */
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.action-buttons {
|
||
display: flex;
|
||
flex-direction: row; /* 显式设置横向排列 */
|
||
align-items: center;
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.action-btn {
|
||
display: flex;
|
||
flex-direction: column; /* 图标文字保持纵向 */
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 20rpx;
|
||
min-width: 80rpx;
|
||
}
|
||
|
||
.action-icon {
|
||
font-size: 40rpx;
|
||
margin-bottom: 4rpx;
|
||
}
|
||
|
||
.action-text {
|
||
font-size: 20rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.btn-group {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: row; /* 显式设置横向排列 */
|
||
align-items: center;
|
||
}
|
||
|
||
.cart-btn, .buy-btn {
|
||
flex: 1;
|
||
height: 72rpx;
|
||
line-height: 72rpx;
|
||
border-radius: 36rpx;
|
||
font-size: 26rpx;
|
||
border: none;
|
||
margin: 0 10rpx;
|
||
}
|
||
|
||
.cart-btn {
|
||
background-color: #ffa726;
|
||
color: #fff;
|
||
}
|
||
|
||
.buy-btn {
|
||
background-color: #ff4444;
|
||
color: #fff;
|
||
}
|
||
|
||
.spec-modal {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
align-items: flex-end;
|
||
z-index: 999;
|
||
}
|
||
|
||
.spec-content {
|
||
background-color: #fff;
|
||
width: 100%;
|
||
max-height: 80vh;
|
||
border-radius: 20rpx 20rpx 0 0;
|
||
padding: 30rpx;
|
||
}
|
||
|
||
.spec-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 30rpx;
|
||
padding-bottom: 20rpx;
|
||
border-bottom: 1rpx solid #eee;
|
||
}
|
||
|
||
.spec-title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.close-btn {
|
||
font-size: 48rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.spec-list {
|
||
max-height: 60vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.spec-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 25rpx 0;
|
||
border-bottom: 1rpx solid #f5f5f5;
|
||
}
|
||
|
||
.spec-item.active {
|
||
background-color: #fff3e0;
|
||
}
|
||
|
||
.spec-name {
|
||
flex: 1;
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.spec-price {
|
||
font-size: 26rpx;
|
||
color: #ff4444;
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.spec-stock {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
width: 100rpx;
|
||
text-align: right;
|
||
}
|
||
|
||
/* 功能主治样式 */
|
||
.function-section {
|
||
background-color: #fff;
|
||
padding: 30rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.function-title {
|
||
font-size: 30rpx;
|
||
color: #333;
|
||
font-weight: bold;
|
||
margin-bottom: 15rpx;
|
||
display: block;
|
||
}
|
||
|
||
.function-content {
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
/* 商品参数样式 */
|
||
.params-section {
|
||
background-color: #fff;
|
||
padding: 30rpx;
|
||
margin-bottom: 20rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.params-title {
|
||
font-size: 30rpx;
|
||
color: #333;
|
||
width: 120rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.params-summary {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
}
|
||
|
||
.params-item {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
line-height: 1.5;
|
||
margin-right: 20rpx;
|
||
margin-bottom: 5rpx;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.params-arrow {
|
||
font-size: 28rpx;
|
||
color: #999;
|
||
flex-shrink: 0;
|
||
margin-left: 10rpx;
|
||
}
|
||
|
||
/* 商品参数弹窗样式 */
|
||
.params-modal {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
align-items: flex-end;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.params-content {
|
||
background-color: #fff;
|
||
width: 100%;
|
||
max-height: 80vh;
|
||
border-radius: 20rpx 20rpx 0 0;
|
||
padding: 30rpx;
|
||
}
|
||
|
||
.params-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 30rpx;
|
||
padding-bottom: 20rpx;
|
||
border-bottom: 1rpx solid #eee;
|
||
}
|
||
|
||
.params-title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
width: auto;
|
||
}
|
||
|
||
.params-list {
|
||
max-height: 60vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.params-item {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
padding: 20rpx 0;
|
||
border-bottom: 1rpx solid #f5f5f5;
|
||
}
|
||
|
||
.params-label {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
font-weight: bold;
|
||
width: 150rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.params-value {
|
||
flex: 1;
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
/* 商品详情图片样式 */
|
||
.detail-images {
|
||
margin-top: 30rpx;
|
||
}
|
||
|
||
.detail-image {
|
||
width: 100%;
|
||
margin-bottom: 20rpx;
|
||
border-radius: 10rpx;
|
||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
/* 电脑端适配 */
|
||
@media (min-width: 768px) {
|
||
.params-section {
|
||
padding: 20rpx 30rpx;
|
||
}
|
||
|
||
.params-summary {
|
||
flex-wrap: nowrap;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.params-item {
|
||
flex: 1;
|
||
margin-right: 0;
|
||
text-align: center;
|
||
white-space: normal;
|
||
word-break: break-word;
|
||
padding: 0 10rpx;
|
||
}
|
||
|
||
.params-arrow {
|
||
margin-left: 20rpx;
|
||
}
|
||
}
|
||
</style>
|