consumer模块完成度95%,能编译在安卓端运行,在解决数据获取和页面布局问题
This commit is contained in:
@@ -81,9 +81,9 @@
|
||||
</view>
|
||||
<input class="quantity-input"
|
||||
type="number"
|
||||
v-model="quantity"
|
||||
:value="quantity"
|
||||
:min="1"
|
||||
:max="getMaxQuantity()"
|
||||
:max="getMaxQuantity()"
|
||||
@input="validateQuantity" />
|
||||
<view class="quantity-btn plus" @click="increaseQuantity">
|
||||
<text class="quantity-btn-text">+</text>
|
||||
@@ -97,7 +97,7 @@
|
||||
<view class="section-title">商品详情</view>
|
||||
<text class="description-text">{{ product.description ?? '暂无详细描述' }}</text>
|
||||
<!-- 商品详情图片 -->
|
||||
<view class="detail-images" v-if="product.images && product.images.length > 0">
|
||||
<view class="detail-images" v-if="product.images != null && product.images.length > 0">
|
||||
<image v-for="(img, index) in product.images"
|
||||
:key="index"
|
||||
:src="img"
|
||||
@@ -187,9 +187,9 @@
|
||||
<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">
|
||||
<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>
|
||||
<text class="params-value">{{ product.tags!.join(', ') }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
@@ -226,7 +226,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ProductType, MerchantType, ProductSkuType } from '@/types/mall-types.uts'
|
||||
import { ProductType, MerchantType, ProductSkuType, CouponTemplateType } from '@/types/mall-types.uts'
|
||||
import { supabaseService } from '@/utils/supabaseService.uts'
|
||||
|
||||
export default {
|
||||
@@ -265,40 +265,49 @@ export default {
|
||||
showSpec: false,
|
||||
selectedSkuId: '',
|
||||
selectedSpec: '',
|
||||
quantity: 1,
|
||||
quantity: 1 as number,
|
||||
isFavorite: false,
|
||||
showParams: false,
|
||||
// 新增: 优惠券相关
|
||||
coupons: [] as any[],
|
||||
coupons: [] as CouponTemplateType[],
|
||||
showCoupons: false
|
||||
}
|
||||
},
|
||||
onLoad(options: any) {
|
||||
const productId = (options['productId'] ?? options['id']) as string
|
||||
const productPrice = options.price ? parseFloat(options.price) : null
|
||||
const productOriginalPrice = options.originalPrice ? parseFloat(options.originalPrice) : null
|
||||
const opts = options as UTSJSONObject
|
||||
const productId = (opts.getString('productId') ?? opts.getString('id')) ?? ''
|
||||
const priceStr = opts.getString('price')
|
||||
const productPrice = priceStr != null ? parseFloat(priceStr) : null
|
||||
const originalPriceStr = opts.getString('originalPrice')
|
||||
const productOriginalPrice = originalPriceStr != null ? parseFloat(originalPriceStr) : null
|
||||
|
||||
// 处理商品名称:如果是编码的则解码,否则直接使用
|
||||
let productName = options.name as string
|
||||
if (productName) {
|
||||
let productName = opts.getString('name') ?? ''
|
||||
if (productName != '') {
|
||||
try {
|
||||
// 尝试解码,如果失败(不是有效的URI组件)则使用原值
|
||||
productName = decodeURIComponent(productName)
|
||||
const decoded = decodeURIComponent(productName)
|
||||
if (decoded != null) {
|
||||
productName = decoded
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('ProductName decode failed, using original:', productName)
|
||||
}
|
||||
}
|
||||
|
||||
let productImage = options.image as string
|
||||
if (productImage) {
|
||||
let productImage = opts.getString('image') ?? ''
|
||||
if (productImage != '') {
|
||||
try {
|
||||
productImage = decodeURIComponent(productImage)
|
||||
const decoded = decodeURIComponent(productImage)
|
||||
if (decoded != null) {
|
||||
productImage = decoded
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('ProductImage decode failed, using original:', productImage)
|
||||
}
|
||||
}
|
||||
|
||||
if (productId) {
|
||||
if (productId != '') {
|
||||
this.loadProductDetail(productId, {
|
||||
price: productPrice,
|
||||
originalPrice: productOriginalPrice,
|
||||
@@ -309,7 +318,7 @@ export default {
|
||||
this.saveFootprint(productId)
|
||||
|
||||
// 设置导航栏标题为商品名称
|
||||
if (productName) {
|
||||
if (productName != '') {
|
||||
uni.setNavigationBarTitle({
|
||||
title: productName
|
||||
})
|
||||
@@ -318,9 +327,9 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
displayPrice(): number {
|
||||
if (this.selectedSkuId) {
|
||||
if (this.selectedSkuId != '') {
|
||||
const sku = this.productSkus.find(s => s.id === this.selectedSkuId)
|
||||
if (sku) return sku.price
|
||||
if (sku != null) return sku.price
|
||||
}
|
||||
return this.product.price
|
||||
}
|
||||
@@ -334,10 +343,10 @@ export default {
|
||||
}
|
||||
})
|
||||
|
||||
const footprintData = uni.getStorageSync('footprints')
|
||||
const footprintData = uni.getStorageSync('footprints') as string
|
||||
let footprints: any[] = []
|
||||
|
||||
if (footprintData) {
|
||||
if (footprintData != null && footprintData !== '') {
|
||||
try {
|
||||
footprints = JSON.parse(footprintData as string) as any[]
|
||||
} catch (e) {
|
||||
@@ -346,7 +355,12 @@ export default {
|
||||
}
|
||||
|
||||
// 移除已存在的相同商品(为了将其移到最新位置)
|
||||
footprints = footprints.filter(item => item.id !== productId)
|
||||
const productIdStr = productId
|
||||
footprints = footprints.filter(function(item: any): boolean {
|
||||
const itemObj = item as UTSJSONObject
|
||||
const itemId = itemObj.getString('id') ?? ''
|
||||
return itemId != productIdStr
|
||||
})
|
||||
|
||||
// 添加到头部
|
||||
footprints.unshift({
|
||||
@@ -373,54 +387,62 @@ export default {
|
||||
try {
|
||||
const dbProductResponse = await supabaseService.getProductById(productId)
|
||||
let dbProduct: any | null = null
|
||||
if (Array.isArray(dbProductResponse) && dbProductResponse.length > 0) {
|
||||
dbProduct = dbProductResponse[0]
|
||||
} else if (dbProductResponse && !Array.isArray(dbProductResponse)) {
|
||||
if (Array.isArray(dbProductResponse) && (dbProductResponse as any[]).length! > 0) {
|
||||
dbProduct = (dbProductResponse as any[])[0]
|
||||
} else if (dbProductResponse != null) {
|
||||
dbProduct = dbProductResponse
|
||||
}
|
||||
|
||||
if (dbProduct) {
|
||||
if (dbProduct != null) {
|
||||
// Map DB product to local product
|
||||
const dbObj = dbProduct as UTSJSONObject
|
||||
this.product = {
|
||||
id: dbProduct.id,
|
||||
merchant_id: dbProduct.merchant_id ?? dbProduct.shop_id ?? '',
|
||||
category_id: dbProduct.category_id ?? '',
|
||||
name: dbProduct.name,
|
||||
description: dbProduct.description ?? '',
|
||||
id: dbObj.getString('id') ?? '',
|
||||
merchant_id: dbObj.getString('merchant_id') ?? dbObj.getString('shop_id') ?? '',
|
||||
category_id: dbObj.getString('category_id') ?? '',
|
||||
name: dbObj.getString('name') ?? '',
|
||||
description: dbObj.getString('description') ?? '',
|
||||
images: [] as string[],
|
||||
price: dbProduct.base_price ?? dbProduct.price ?? 0,
|
||||
original_price: dbProduct.market_price ?? dbProduct.original_price ?? 0,
|
||||
stock: dbProduct.available_stock ?? dbProduct.total_stock ?? dbProduct.stock ?? 0,
|
||||
sales: dbProduct.sale_count ?? dbProduct.sales ?? 0,
|
||||
status: dbProduct.status !== undefined ? dbProduct.status : 1,
|
||||
created_at: dbProduct.created_at ?? new Date().toISOString(),
|
||||
price: dbObj.getNumber('base_price') ?? dbObj.getNumber('price') ?? 0,
|
||||
original_price: dbObj.getNumber('market_price') ?? dbObj.getNumber('original_price') ?? 0,
|
||||
stock: dbObj.getNumber('available_stock') ?? dbObj.getNumber('total_stock') ?? dbObj.getNumber('stock') ?? 0,
|
||||
sales: dbObj.getNumber('sale_count') ?? dbObj.getNumber('sales') ?? 0,
|
||||
status: dbObj.getNumber('status') ?? 1,
|
||||
created_at: dbObj.getString('created_at') ?? new Date().toISOString(),
|
||||
// Attributes
|
||||
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,
|
||||
specification: dbObj.getString('specification') ?? null,
|
||||
usage: dbObj.getString('usage') ?? null,
|
||||
side_effects: dbObj.getString('side_effects') ?? null,
|
||||
precautions: dbObj.getString('precautions') ?? null,
|
||||
expiry_date: dbObj.getString('expiry_date') ?? null,
|
||||
storage_conditions: dbObj.getString('storage_conditions') ?? null,
|
||||
approval_number: dbObj.getString('approval_number') ?? null,
|
||||
tags: [] as string[]
|
||||
} as ProductType
|
||||
|
||||
// Handle Images
|
||||
if (dbProduct.image_urls) {
|
||||
const imageUrls = dbObj.get('image_urls')
|
||||
if (imageUrls != null) {
|
||||
try {
|
||||
const parsed = typeof dbProduct.image_urls === 'string' ? JSON.parse(dbProduct.image_urls) : dbProduct.image_urls
|
||||
const parsed = typeof imageUrls === 'string' ? JSON.parse(imageUrls as string) : imageUrls
|
||||
if (Array.isArray(parsed)) {
|
||||
this.product.images = parsed.map((i: any) => String(i))
|
||||
this.product.images = parsed.map((i: any): string => '' + i)
|
||||
}
|
||||
} catch (e) { console.error('Error parsing image_urls', e) }
|
||||
}
|
||||
// Fallback to main_image_url if no images found
|
||||
if (this.product.images.length === 0 && dbProduct.main_image_url) {
|
||||
this.product.images.push(dbProduct.main_image_url)
|
||||
if (this.product.images.length === 0) {
|
||||
const mainImg = dbObj.getString('main_image_url')
|
||||
if (mainImg != null && mainImg != '') {
|
||||
this.product.images.push(mainImg)
|
||||
}
|
||||
}
|
||||
// Fallback to 'image' field (legacy)
|
||||
if (this.product.images.length === 0 && dbProduct.image) {
|
||||
this.product.images.push(dbProduct.image)
|
||||
if (this.product.images.length === 0) {
|
||||
const legacyImg = dbObj.getString('image')
|
||||
if (legacyImg != null && legacyImg != '') {
|
||||
this.product.images.push(legacyImg)
|
||||
}
|
||||
}
|
||||
// Final fallback
|
||||
if (this.product.images.length === 0) {
|
||||
@@ -428,24 +450,28 @@ export default {
|
||||
}
|
||||
|
||||
// Handle Tags
|
||||
if (dbProduct.tags) {
|
||||
const tagsVal = dbObj.get('tags')
|
||||
if (tagsVal != null) {
|
||||
try {
|
||||
const parsedTags = typeof dbProduct.tags === 'string' ? JSON.parse(dbProduct.tags) : dbProduct.tags
|
||||
const parsedTags = typeof tagsVal === 'string' ? JSON.parse(tagsVal as string) : tagsVal
|
||||
if (Array.isArray(parsedTags)) {
|
||||
this.product.tags = parsedTags.map((t: any) => String(t))
|
||||
this.product.tags = parsedTags.map((t: any): string => '' + t)
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// Handle JSON attributes if present
|
||||
if (dbProduct.attributes && typeof dbProduct.attributes === 'string') {
|
||||
const attrsVal = dbObj.get('attributes')
|
||||
if (attrsVal != null && typeof attrsVal === 'string') {
|
||||
try {
|
||||
const attrs = JSON.parse(dbProduct.attributes)
|
||||
if (attrs) {
|
||||
// Merge attributes into product if they match keys
|
||||
if (attrs.specification) this.product.specification = attrs.specification
|
||||
if (attrs.usage) this.product.usage = attrs.usage
|
||||
// ... augment as needed
|
||||
const attrsObj = JSON.parse(attrsVal as string) as UTSJSONObject
|
||||
const specVal = attrsObj.getString('specification')
|
||||
if (specVal != null) {
|
||||
this.product.specification = specVal
|
||||
}
|
||||
const usageVal = attrsObj.getString('usage')
|
||||
if (usageVal != null) {
|
||||
this.product.usage = usageVal
|
||||
}
|
||||
} catch(e) {}
|
||||
}
|
||||
@@ -456,18 +482,22 @@ export default {
|
||||
console.error('Failed to load product detail:', e)
|
||||
// Fallback to options if available
|
||||
this.product.id = productId
|
||||
this.product.name = options.name ? decodeURIComponent(options.name) : '未知商品'
|
||||
this.product.price = options.price ? parseFloat(options.price) : 0
|
||||
this.product.images = options.image ? [decodeURIComponent(options.image)] : ['/static/default-product.png']
|
||||
const optsFallback = options as UTSJSONObject
|
||||
const nameVal = optsFallback.getString('name')
|
||||
this.product.name = (nameVal != null && nameVal != '') ? decodeURIComponent(nameVal) : '未知商品'
|
||||
const priceVal = optsFallback.getString('price')
|
||||
this.product.price = (priceVal != null && priceVal != '') ? parseFloat(priceVal) : 0
|
||||
const imageVal = optsFallback.getString('image')
|
||||
this.product.images = (imageVal != null && imageVal != '') ? [decodeURIComponent(imageVal)] : ['/static/default-product.png']
|
||||
}
|
||||
|
||||
// Load Merchant and SKUs
|
||||
if (this.product.merchant_id) {
|
||||
if (this.product.merchant_id != '') {
|
||||
await this.loadMerchantInfo(this.product.merchant_id)
|
||||
// 加载优惠券
|
||||
this.loadCoupons()
|
||||
}
|
||||
if (this.product.id) {
|
||||
if (this.product.id != '') {
|
||||
this.loadProductSkus(this.product.id)
|
||||
}
|
||||
|
||||
@@ -647,15 +677,20 @@ export default {
|
||||
},
|
||||
|
||||
getSkuSpecText(sku: ProductSkuType): string {
|
||||
if (sku.specifications) {
|
||||
const specs: any = sku.specifications
|
||||
return Object.keys(specs).map(key => `${key}: ${specs[key]}`).join(', ')
|
||||
if (sku.specifications != null) {
|
||||
const specs = sku.specifications as Record<string, any>
|
||||
const parts: string[] = []
|
||||
// 使用 for-in 循环代替 Object.keys
|
||||
for (const key in specs) {
|
||||
parts.push(`${key}: ${specs[key]}`)
|
||||
}
|
||||
return parts.join(', ')
|
||||
}
|
||||
return sku.sku_code
|
||||
return sku.sku_code ?? ''
|
||||
},
|
||||
|
||||
async addToCart() {
|
||||
if (this.productSkus.length > 0 && !this.selectedSkuId) {
|
||||
if (this.productSkus.length > 0 && this.selectedSkuId == '') {
|
||||
uni.showToast({
|
||||
title: '请选择规格',
|
||||
icon: 'none'
|
||||
@@ -669,7 +704,8 @@ export default {
|
||||
const success = await supabaseService.addToCart(
|
||||
this.product.id,
|
||||
this.quantity,
|
||||
this.selectedSkuId
|
||||
this.selectedSkuId,
|
||||
this.product.merchant_id
|
||||
)
|
||||
uni.hideLoading()
|
||||
|
||||
@@ -687,7 +723,7 @@ export default {
|
||||
},
|
||||
|
||||
buyNow() {
|
||||
if (this.productSkus.length > 0 && !this.selectedSkuId) {
|
||||
if (this.productSkus.length > 0 && this.selectedSkuId == '') {
|
||||
uni.showToast({
|
||||
title: '请选择规格',
|
||||
icon: 'none'
|
||||
@@ -695,16 +731,16 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
const sku = this.selectedSkuId ? this.productSkus.find(s => s.id === this.selectedSkuId) : null
|
||||
const sku = 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 && 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)),
|
||||
product_image: (sku != null && sku.image_url != '') ? sku.image_url : this.product.images[0],
|
||||
sku_specifications: sku != null ? sku.specifications : {},
|
||||
price: Number(parseFloat((sku != null ? sku.price : this.product.price).toString()).toFixed(2)),
|
||||
quantity: Number(this.quantity)
|
||||
}
|
||||
|
||||
@@ -761,7 +797,7 @@ export default {
|
||||
},
|
||||
|
||||
goToShop() {
|
||||
if (this.merchant.user_id) {
|
||||
if (this.merchant.user_id != '') {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/shop-detail?merchantId=${this.merchant.id}`
|
||||
})
|
||||
@@ -788,7 +824,7 @@ export default {
|
||||
},
|
||||
|
||||
validateQuantity() {
|
||||
let num = parseInt(this.quantity)
|
||||
let num = Number(this.quantity)
|
||||
if (isNaN(num)) num = 1
|
||||
const maxQuantity = this.getMaxQuantity()
|
||||
if (num < 1) num = 1
|
||||
@@ -799,10 +835,10 @@ export default {
|
||||
this.quantity = num
|
||||
},
|
||||
|
||||
getMaxQuantity() {
|
||||
if (this.selectedSkuId) {
|
||||
getMaxQuantity(): number {
|
||||
if (this.selectedSkuId != '') {
|
||||
const sku = this.productSkus.find(s => s.id === this.selectedSkuId)
|
||||
if (sku) return sku.stock
|
||||
if (sku != null) return sku.stock
|
||||
}
|
||||
return this.product.stock
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user