consumer模块完成95%,在和商家端对接聊天购物闭环
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user