继续完善

This commit is contained in:
2026-01-30 17:29:02 +08:00
parent ab038ec029
commit d57592ca7d
14 changed files with 8003 additions and 407 deletions

View File

@@ -3,7 +3,7 @@
<view class="product-detail-page">
<!-- 商品图片轮播 -->
<view class="product-images">
<swiper class="image-swiper" :indicator-dots="true" :autoplay="false">
<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>
@@ -34,6 +34,23 @@
<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>
@@ -65,6 +82,15 @@
<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>
<!-- 底部操作栏 -->
@@ -104,6 +130,50 @@
</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>
@@ -148,7 +218,8 @@ export default {
selectedSkuId: '',
selectedSpec: '',
quantity: 1,
isFavorite: false
isFavorite: false,
showParams: false
}
},
onLoad(options: any) {
@@ -241,42 +312,186 @@ export default {
async loadProductDetail(productId: string, options: any = {}) {
// 尝试从数据库加载
let dbProduct = null
let dbProductRaw = null
try {
console.log('正在尝试从数据库加载商品详情:', productId)
dbProduct = await supabaseService.getProductById(productId)
console.log('数据库返回的商品详情:', dbProduct)
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('使用数据库数据渲染页面')
// 使用数据库数据
const images = [] as Array<string>
if (dbProduct.image) images.push(dbProduct.image)
else if (options.image) images.push(decodeURIComponent(options.image as string))
else images.push('/static/product1.jpg')
// 补充模拟图片
images.push('/static/product2.jpg')
images.push('/static/product3.jpg')
// 调试打印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)
const priceValue = dbProduct.price !== undefined ? dbProduct.price : (dbProduct['price'] !== undefined ? dbProduct['price'] : undefined)
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>
// 处理图片字段优先使用images字段其次使用image字段
console.log('处理数据库图片字段:')
console.log('dbProduct.images:', dbProduct.images, '类型:', typeof dbProduct.images)
console.log('dbProduct.image:', dbProduct.image, '类型:', typeof dbProduct.image)
// 尝试从数据库的images字段获取图片可能是字符串或数组
if (dbProduct.images) {
let imagesArray: any[] = []
if (typeof dbProduct.images === 'string') {
try {
imagesArray = JSON.parse(dbProduct.images)
console.log('解析images字符串成功:', imagesArray)
} catch (e) {
console.error('解析images字段失败:', e, dbProduct.images)
// 如果不是JSON尝试按逗号分割
if (dbProduct.images.includes(',')) {
imagesArray = dbProduct.images.split(',').map((img: string) => img.trim())
} else if (dbProduct.images) {
imagesArray = [dbProduct.images]
}
}
} else if (Array.isArray(dbProduct.images)) {
imagesArray = dbProduct.images
}
if (imagesArray.length > 0) {
console.log('从数据库images字段获取图片数组:', imagesArray)
for (const img of imagesArray) {
if (typeof img === 'string' && img) {
images.push(img)
}
}
}
}
// 如果没有从images字段获取到图片尝试使用image字段
if (images.length === 0 && dbProduct.image) {
console.log('使用单张图片字段:', 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'
// 确保数值字段有效
const price = typeof dbProduct.price === 'number' ? dbProduct.price : 0
const stock = (dbProduct.stock != null && !isNaN(Number(dbProduct.stock))) ? Math.floor(Number(dbProduct.stock)) : 100
const sales = (dbProduct.sales != null && !isNaN(Number(dbProduct.sales))) ? Math.floor(Number(dbProduct.sales)) : 50
this.product = {
id: dbProduct.id,
merchant_id: dbProduct.shop_id || 'merchant_001',
category_id: dbProduct.category_id,
name: dbProduct.name,
description: dbProduct.description || '这是一个高品质的商品,具有优秀的性能和优美的外观设计。采用环保材料,经过严格质检,保证用户的使用体验。',
images: images,
price: dbProduct.price,
original_price: dbProduct.original_price || null,
stock: dbProduct.stock !== undefined ? dbProduct.stock : 0, // 确保使用数据库中的库存
sales: dbProduct.sales !== undefined ? dbProduct.sales : 0, // 确保使用数据库中的销量
status: 1,
created_at: dbProduct.created_at || '2024-01-01'
} as ProductType
console.log('页面 product 对象已更新:', this.product)
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: price,
original_price: (dbProduct.original_price != null && !isNaN(Number(dbProduct.original_price))) ? Number(dbProduct.original_price) : null,
stock: stock,
sales: sales,
status: 1,
created_at: dbProduct.created_at || '2024-01-01',
// 药品相关字段
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: 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('数据库无数据或加载失败,使用模拟数据')
// 数据库无数据时,使用原有模拟逻辑
@@ -378,6 +593,10 @@ export default {
}
]
},
onSwiperChange(e: any) {
this.currentImageIndex = e.detail.current
},
showSpecModal() {
this.showSpec = true
@@ -626,6 +845,21 @@ export default {
getAvailableStock() {
return this.getMaxQuantity()
},
previewImage(index: number) {
uni.previewImage({
current: index,
urls: this.product.images
})
},
showParamsModal() {
this.showParams = true
},
hideParamsModal() {
this.showParams = false
}
}
}
@@ -993,4 +1227,166 @@ export default {
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>