consumer模块完成95%,在和商家端对接聊天购物闭环

This commit is contained in:
2026-02-06 17:10:31 +08:00
parent 06b7369494
commit e2f1dfb097
1454 changed files with 5425 additions and 210555 deletions

View File

@@ -40,6 +40,19 @@
<text class="function-content">{{ product.usage }}</text>
</view>
<!-- 优惠券入口 (新增) -->
<view class="coupon-entry" @click="showCouponModal" v-if="coupons.length > 0">
<view class="coupon-entry-left">
<text class="coupon-entry-label">优惠</text>
<view class="coupon-tags-row">
<text class="coupon-tag" v-for="(coupon, index) in coupons.slice(0, 2)" :key="index">
{{ coupon.name }}
</text>
</view>
</view>
<text class="coupon-arrow">领券 ></text>
</view>
<!-- 商品参数 -->
<view class="params-section" @click="showParamsModal">
<text class="params-title">商品参数</text>
@@ -96,6 +109,11 @@
<!-- 底部操作栏 -->
<view class="bottom-actions">
<view class="action-buttons">
<!-- 客服按钮 (新增) -->
<view class="action-btn" @click="contactMerchant">
<text class="action-icon">💬</text>
<text class="action-text">客服</text>
</view>
<view class="action-btn" @click="goToCart">
<text class="action-icon">🛒</text>
<text class="action-text">购物车</text>
@@ -174,6 +192,34 @@
</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 scroll-y="true" 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>
</template>
@@ -219,7 +265,10 @@ export default {
selectedSpec: '',
quantity: 1,
isFavorite: false,
showParams: false
showParams: false,
// 新增: 优惠券相关
coupons: [] as any[],
showCoupons: false
}
},
onLoad(options: any) {
@@ -398,9 +447,6 @@ export default {
}
} catch(e) {}
}
// Load SKUs
// this.loadProductSkus(productId) // If SKU logic exists
} else {
throw new Error('No product found')
}
@@ -416,6 +462,8 @@ export default {
// Load Merchant and SKUs
if (this.product.merchant_id) {
await this.loadMerchantInfo(this.product.merchant_id)
// 加载优惠券
this.loadCoupons()
}
if (this.product.id) {
this.loadProductSkus(this.product.id)
@@ -508,10 +556,74 @@ export default {
} catch (e) {
console.error('Fetch SKUs error', e)
}
},
// 新增:加载优惠券
async loadCoupons() {
if (!this.product.merchant_id) return
// Safety check for cached service definition
// @ts-ignore
if (typeof supabaseService.fetchShopCoupons === 'function') {
this.coupons = await supabaseService.fetchShopCoupons(this.product.merchant_id)
} else if (typeof supabaseService.getAvailableCoupons === 'function') {
this.coupons = await supabaseService.getAvailableCoupons(this.product.merchant_id)
} else {
console.warn('SupabaseService.fetchShopCoupons method missing in runtime. Please restart project.')
}
},
// 如果没有从数据库加载到SKU则不显示规格选择直接作为无规格商品添加
// 移除之前的Mock逻辑因为Mock的ID不符合UUID格式会导致数据库错误
// 新增:联系客服(商家)
contactMerchant() {
if (!supabaseService.getCurrentUserId()) {
uni.navigateTo({ url: '/pages/auth/login' })
return
}
// Navigate to chat
const merchId = this.merchant.user_id || this.merchant.id || this.product.merchant_id;
uni.navigateTo({
url: `/pages/mall/consumer/chat?merchantId=${merchId}&merchantName=${this.merchant.shop_name}`
})
},
// 新增:优惠券弹窗
showCouponModal() {
this.showCoupons = true
},
hideCouponModal() {
this.showCoupons = false
},
// 新增:领取优惠券
async claimCoupon(coupon: any) {
const userId = supabaseService.getCurrentUserId()
if (!userId) {
uni.navigateTo({ url: '/pages/auth/login' })
return
}
uni.showLoading({ title: '领取中' })
let success = false
// @ts-ignore
if (typeof supabaseService.claimShopCoupon === 'function') {
success = await supabaseService.claimShopCoupon(coupon.id, userId)
} else if (typeof supabaseService.claimCoupon === 'function') {
success = await supabaseService.claimCoupon(coupon.id, userId)
} else {
console.warn('claimCoupon method missing')
}
uni.hideLoading()
if (success) {
uni.showToast({ title: '领取成功', icon: 'success' })
} else {
uni.showToast({ title: '领取失败或已领取', icon: 'none' })
}
},
formatDate(dateStr: string): string {
if (!dateStr) return ''
const date = new Date(dateStr)
return `${date.getFullYear()}.${date.getMonth()+1}.${date.getDate()}`
},
onSwiperChange(e: any) {
@@ -549,41 +661,26 @@ export default {
return
}
// 显示加载中
uni.showLoading({
title: '添加中...'
})
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'
})
uni.showToast({ title: '已添加到购物车', icon: 'success' })
} else {
console.error('添加购物车返回失败')
uni.showToast({
title: '添加失败,请登录重试',
icon: 'none'
})
uni.showToast({ title: '添加失败,请登录重试', icon: 'none' })
}
} catch (e) {
uni.hideLoading()
console.error('添加购物车异常', e)
uni.showToast({
title: '添加异常',
icon: 'none'
})
uni.showToast({ title: '添加异常', icon: 'none' })
}
},
@@ -598,32 +695,20 @@ export default {
const sku = this.selectedSkuId ? this.productSkus.find(s => s.id === this.selectedSkuId) : null
// 调试:打印价格信息
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)
}
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) => {
@@ -634,14 +719,7 @@ export default {
})
},
goToShop() {
uni.navigateTo({
url: `/pages/mall/consumer/shop-detail?merchantId=${this.merchant.id}`
})
},
checkFavoriteStatus(id: string) {
// console.log('product-detail checkFavoriteStatus id:', id)
this.checkFavorite(id)
},
@@ -652,65 +730,46 @@ export default {
async toggleFavorite() {
if (!this.product.id) return
// 显示loading
uni.showLoading({ title: '处理中' })
try {
// 记录操作前的状态
const wasFavorite = this.isFavorite
// 执行切换返回的是最新的状态true=已收藏false=未收藏)
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'
})
// 确保状态同步
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'
})
uni.showToast({ title: '操作异常', icon: 'none' })
}
},
goToHome() {
uni.switchTab({
url: '/pages/mall/consumer/home'
})
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}`
url: `/pages/mall/consumer/shop-detail?merchantId=${this.merchant.id}`
})
}
},
goToCart() {
uni.switchTab({
url: '/pages/mall/consumer/cart'
})
uni.switchTab({ url: '/pages/mall/consumer/cart' })
},
// 数量选择相关方法
decreaseQuantity() {
if (this.quantity > 1) {
this.quantity--
@@ -722,37 +781,23 @@ export default {
if (this.quantity < maxQuantity) {
this.quantity++
} else {
uni.showToast({
title: `最多只能购买${maxQuantity}件`,
icon: 'none'
})
uni.showToast({ title: `最多只能购买${maxQuantity}件`, icon: 'none' })
}
},
validateQuantity() {
// 确保数量是数字
let num = parseInt(this.quantity)
if (isNaN(num)) {
num = 1
}
// 限制在1和最大库存之间
if (isNaN(num)) num = 1
const maxQuantity = this.getMaxQuantity()
if (num < 1) {
num = 1
} else if (num > maxQuantity) {
if (num < 1) num = 1
else if (num > maxQuantity) {
num = maxQuantity
uni.showToast({
title: `最多只能购买${maxQuantity}件`,
icon: 'none'
})
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
@@ -857,7 +902,7 @@ export default {
padding: 30rpx;
margin-bottom: 20rpx;
display: flex;
flex-direction: row; /* 显式横向排列 */
flex-direction: row;
align-items: center;
}
@@ -871,7 +916,7 @@ export default {
.shop-details {
flex: 1;
display: flex;
flex-direction: column; /* 内部信息保持纵向,或者根据需要改为横向 */
flex-direction: column;
justify-content: center;
}
@@ -884,7 +929,7 @@ export default {
.shop-stats-row {
display: flex;
flex-direction: row; /* 显式横向排列 */
flex-direction: row;
align-items: center;
}
@@ -899,6 +944,142 @@ export default {
color: #666;
}
/* Coupon Entry Styles */
.coupon-entry {
background-color: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.coupon-entry-left {
display: flex;
align-items: center;
flex: 1;
}
.coupon-entry-label {
font-size: 30rpx;
color: #333;
width: 120rpx;
font-weight: bold;
}
.coupon-tags-row {
flex: 1;
display: flex;
flex-direction: row;
}
.coupon-tag {
font-size: 20rpx;
color: #ff4444;
border: 1px solid #ff4444;
padding: 2rpx 10rpx;
border-radius: 4rpx;
margin-right: 15rpx;
}
.coupon-arrow {
font-size: 26rpx;
color: #999;
}
/* Modal Popup Styles */
.popup-mask {
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;
}
.popup-content {
background-color: #fff;
width: 100%;
max-height: 80vh;
border-radius: 20rpx 20rpx 0 0;
padding: 30rpx;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #eee;
}
.popup-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.close-btn {
font-size: 48rpx;
color: #999;
}
.coupon-list-scroll {
max-height: 60vh;
}
.coupon-item {
display: flex;
background-color: #fff5f5;
border-radius: 10rpx;
padding: 20rpx;
margin-bottom: 20rpx;
}
.coupon-left {
width: 180rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-right: 1px dashed #ffccc7;
color: #ff4444;
}
.coupon-amount {
font-size: 40rpx;
font-weight: bold;
}
.symbol {
font-size: 24rpx;
}
.coupon-cond {
font-size: 22rpx;
margin-top: 5rpx;
}
.coupon-right {
flex: 1;
padding-left: 20rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
.coupon-info-text {
display: flex;
flex-direction: column;
}
.coupon-name {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
}
.coupon-time {
font-size: 22rpx;
color: #999;
}
.coupon-btn {
background-color: #ff4444;
color: #fff;
font-size: 24rpx;
padding: 0 24rpx;
height: 50rpx;
line-height: 50rpx;
border-radius: 25rpx;
margin: 0;
}
.spec-section {
background-color: #fff;
padding: 30rpx;
@@ -1012,21 +1193,21 @@ export default {
padding: 10rpx 20rpx;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: row; /* 显式设置横向排列 */
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.action-buttons {
display: flex;
flex-direction: row; /* 显式设置横向排列 */
flex-direction: row;
align-items: center;
margin-right: 20rpx;
}
.action-btn {
display: flex;
flex-direction: column; /* 图标文字保持纵向 */
flex-direction: column;
align-items: center;
justify-content: center;
margin-right: 20rpx;
@@ -1046,7 +1227,7 @@ export default {
.btn-group {
flex: 1;
display: flex;
flex-direction: row; /* 显式设置横向排列 */
flex-direction: row;
align-items: center;
}
@@ -1105,11 +1286,6 @@ export default {
color: #333;
}
.close-btn {
font-size: 48rpx;
color: #999;
}
.spec-list {
max-height: 60vh;
overflow-y: auto;
@@ -1306,4 +1482,4 @@ export default {
margin-left: 20rpx;
}
}
</style>
</style>