Files
medical-mall/pages/mall/consumer/product-detail.uvue

711 lines
16 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 消费者端 - 商品详情页 -->
<template>
<view class="product-detail-page">
<!-- 商品图片轮播 -->
<view class="product-images">
<swiper class="image-swiper" :indicator-dots="true" :autoplay="false">
<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="spec-section" @click="showSpecModal">
<text class="spec-title">规格</text>
<text class="spec-selected">{{ selectedSpec || '请选择规格' }}</text>
<text class="spec-arrow">></text>
</view>
<!-- 商品详情 -->
<view class="product-description">
<view class="section-title">商品详情</view>
<text class="description-text">{{ product.description || '暂无详细描述' }}</text>
</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>
</template>
<script>
import { ProductType, MerchantType, ProductSkuType } from '@/types/mall-types.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
}
},
onLoad(options: any) {
const productId = options.productId as string
if (productId) {
this.loadProductDetail(productId)
this.checkFavoriteStatus(productId)
this.saveFootprint(productId)
}
},
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))
},
loadProductDetail(productId: string) {
// 模拟加载商品详情数据
this.product = {
id: productId,
merchant_id: 'merchant_001',
category_id: 'cat_001',
name: '精选好物商品',
description: '这是一个高品质的商品,具有优秀的性能和优美的外观设计。',
images: [
'/static/product1.jpg',
'/static/product2.jpg',
'/static/product3.jpg'
],
price: 199.99,
original_price: 299.99,
stock: 100,
sales: 1256,
status: 1,
created_at: '2024-01-15'
}
this.merchant = {
id: 'merchant_001',
user_id: 'user_001',
shop_name: '优质好店',
shop_logo: '/static/shop-logo.png',
shop_banner: '/static/shop-banner.png',
shop_description: '专注品质生活',
contact_name: '店主小王',
contact_phone: '13800138000',
shop_status: 1,
rating: 4.8,
total_sales: 15680,
created_at: '2023-06-01'
}
this.loadProductSkus(productId)
},
loadProductSkus(productId: string) {
// 模拟加载商品SKU数据
this.productSkus = [
{
id: 'sku_001',
product_id: productId,
sku_code: 'SKU001',
specifications: { color: '红色', size: 'M' },
price: 199.99,
stock: 50,
image_url: '/static/sku1.jpg',
status: 1
},
{
id: 'sku_002',
product_id: productId,
sku_code: 'SKU002',
specifications: { color: '蓝色', size: 'L' },
price: 219.99,
stock: 30,
image_url: '/static/sku2.jpg',
status: 1
}
]
},
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
},
addToCart() {
if (!this.selectedSkuId) {
uni.showToast({
title: '请选择规格',
icon: 'none'
})
return
}
// 获取现有购物车数据
const cartData = uni.getStorageSync('cart')
let cartItems: any[] = []
if (cartData) {
try {
cartItems = JSON.parse(cartData as string) as any[]
} catch (e) {
console.error('解析购物车数据失败', e)
}
}
// 检查商品是否已存在 (同一SKU)
const existingItem = cartItems.find((item: any) => item.id === this.selectedSkuId)
if (existingItem) {
existingItem.quantity += this.quantity
} else {
// 查找SKU信息
const sku = this.productSkus.find(s => s.id === this.selectedSkuId)
// 添加新商品
cartItems.push({
id: this.selectedSkuId, // 使用SKU ID作为购物车条目ID
productId: this.product.id,
shopId: this.merchant.id,
shopName: this.merchant.shop_name,
name: this.product.name,
price: sku ? sku.price : this.product.price,
image: (sku && sku.image_url) ? sku.image_url : this.product.images[0],
spec: this.selectedSpec,
quantity: this.quantity,
selected: true
})
}
// 保存回存储
uni.setStorageSync('cart', JSON.stringify(cartItems))
// 模拟添加到购物车
uni.showToast({
title: '已添加到购物车',
icon: 'success'
})
},
buyNow() {
if (!this.selectedSkuId) {
uni.showToast({
title: '请选择规格',
icon: 'none'
})
return
}
// 跳转到订单确认页
uni.navigateTo({
url: `/pages/mall/consumer/order-confirm?productId=${this.product.id}&skuId=${this.selectedSkuId}&quantity=${this.quantity}`
})
},
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,
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'
})
},
goToCart() {
uni.switchTab({
url: '/pages/mall/consumer/cart'
})
}
}
}
</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;
}
.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;
}
</style>