继续完善购物逻辑闭环,consumer模块完成度80%

This commit is contained in:
2026-01-27 17:33:39 +08:00
parent f2f208f258
commit 4ab722a118
22 changed files with 5290 additions and 515 deletions

View File

@@ -22,17 +22,26 @@
<!-- 商品列表 -->
<view class="products-section">
<view v-for="item in checkoutItems" :key="item.id" class="product-item">
<image class="product-image" :src="item.product_image" />
<view class="product-info">
<text class="product-name">{{ item.product_name }}</text>
<text v-if="item.sku_specifications" class="product-spec">{{ getSpecText(item.sku_specifications) }}</text>
<view class="product-bottom">
<text class="product-price">¥{{ item.price }}</text>
<text class="product-quantity">×{{ item.quantity }}</text>
<!-- 调试信息 -->
<view v-if="checkoutItems.length > 0" class="debug-info">
<text class="debug-text">调试:共{{ checkoutItems.length }}件商品,总价计算:{{ totalAmount }}</text>
</view>
<view v-if="checkoutItems.length > 0">
<view v-for="item in checkoutItems" :key="item.id" class="product-item">
<image class="product-image" :src="item.product_image" />
<view class="product-info">
<text class="product-name">{{ item.product_name }}</text>
<text v-if="item.sku_specifications" class="product-spec">{{ getSpecText(item.sku_specifications) }}</text>
<view class="product-bottom">
<text class="product-price">¥{{ item.price }}</text>
<text class="product-quantity">×{{ item.quantity }}</text>
</view>
</view>
</view>
</view>
<view v-else class="no-products">
<text class="no-products-text">暂无商品信息</text>
</view>
</view>
<!-- 配送方式 -->
@@ -210,11 +219,28 @@
</view>
</view>
</view>
<!-- 确认保存弹窗 -->
<view v-if="showSaveConfirm" class="confirm-popup-mask">
<view class="confirm-popup">
<view class="confirm-header">
<text class="confirm-title">保存地址</text>
</view>
<view class="confirm-content">
<text class="confirm-message">是否保存该地址用于下次使用?</text>
</view>
<view class="confirm-buttons">
<button class="confirm-btn cancel" @click="handleSaveConfirm(false)">仅本次</button>
<button class="confirm-btn confirm" @click="handleSaveConfirm(true)">保存</button>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, onMounted, computed } from 'vue'
import { ref, onMounted, computed, watch, onUnmounted, getCurrentInstance } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
type CheckoutItemType = {
id: string
@@ -267,10 +293,44 @@ const showNewAddressForm = ref<boolean>(false)
const showSaveConfirm = ref<boolean>(false)
const smartAddressInput = ref<string>('')
// 计算属性
// 计算属性 - 修复价格同步问题
const totalAmount = computed(() => {
return checkoutItems.value.reduce((sum, item) =>
sum + (item.price * item.quantity), 0)
console.log('计算商品总价checkoutItems:', checkoutItems.value)
if (!checkoutItems.value || checkoutItems.value.length === 0) {
console.log('商品列表为空返回0')
return 0
}
// 确保每个商品的价格和数量都是数字类型,并计算总和
const total = checkoutItems.value.reduce((sum, item) => {
// 确保item存在且包含必要的属性
if (!item) return sum
// 将价格转换为数字(确保是数字类型)
let price = 0
if (item.price !== undefined && item.price !== null) {
price = typeof item.price === 'string' ? parseFloat(item.price) : Number(item.price)
}
// 将数量转换为数字
let quantity = 0
if (item.quantity !== undefined && item.quantity !== null) {
quantity = typeof item.quantity === 'string' ? parseInt(item.quantity) : Number(item.quantity)
}
// 验证转换后的数字是否有效
if (isNaN(price) || isNaN(quantity) || price <= 0 || quantity <= 0) {
console.warn('商品价格或数量无效:', item, 'price:', price, 'quantity:', quantity)
return sum
}
const itemTotal = price * quantity
console.log(`商品 ${item.product_name},单价: ${price},数量: ${quantity},小计: ${itemTotal}`)
return sum + itemTotal
}, 0)
console.log('商品总价计算结果:', total)
return total
})
const deliveryFee = computed(() => {
@@ -282,6 +342,7 @@ const discountAmount = computed(() => {
if (!selectedCoupon.value || !selectedCoupon.value.template) return 0
const coupon = selectedCoupon.value.template
// 确保使用计算后的商品总价进行比较
if (totalAmount.value < coupon.min_order_amount) return 0
// 简单处理:假设都是满减券
@@ -289,14 +350,105 @@ const discountAmount = computed(() => {
})
const actualAmount = computed(() => {
let amount = totalAmount.value + deliveryFee.value - discountAmount.value
// 确保所有值都是数字类型
const total = typeof totalAmount.value === 'number' ? totalAmount.value : 0
const delivery = typeof deliveryFee.value === 'number' ? deliveryFee.value : 0
const discount = typeof discountAmount.value === 'number' ? discountAmount.value : 0
// 正确计算:商品总价 + 运费 - 优惠减免
let amount = total + delivery - discount
// 金额必须大于等于0
return amount > 0 ? amount : 0
})
// 监听checkoutItems变化 - 调试用
watch(checkoutItems, (newItems) => {
console.log('checkoutItems变化了:', newItems)
console.log('商品总价计算:', totalAmount.value)
}, { deep: true })
// 页面加载时监听eventChannel
onLoad(() => {
// 优先检查Storage中是否有"立即购买"的数据
const checkoutType = uni.getStorageSync('checkout_type')
if (checkoutType === 'buy_now') {
console.log('检测到立即购买模式从Storage加载数据')
const itemsStr = uni.getStorageSync('checkout_items')
if (itemsStr) {
try {
const items = JSON.parse(itemsStr as string)
console.log('从Storage加载的商品数据:', items)
processCheckoutItems(items)
// 清除Storage避免污染下次进入刷新页面时可能需要保留暂时不清除或者在离开页面时清除
// uni.removeStorageSync('checkout_type')
// uni.removeStorageSync('checkout_items')
loadDefaultAddress()
return // 成功加载,直接返回
} catch (e) {
console.error('解析立即购买数据失败', e)
}
}
}
// 从上一页获取数据
const eventChannel = uni.getEventChannel ? uni.getEventChannel() : null
if (eventChannel) {
eventChannel.on('acceptData', (data: any) => {
console.log('接收到商品数据:', data)
let items = data.selectedItems || []
processCheckoutItems(items)
loadDefaultAddress()
})
} else {
// 如果没有eventChannel尝试从本地存储加载例如从购物车进入
loadFromLocalStorage()
}
})
// 处理商品数据清洗
const processCheckoutItems = (items: any[]) => {
// 数据清洗:确保价格和数量是数字类型
if (items && items.length > 0) {
items = items.map((item: any) => {
// 确保价格是数字
let price = item.price
if (price !== undefined && price !== null) {
price = typeof price === 'string' ? parseFloat(price) : Number(item.price)
if (isNaN(price)) price = 0
} else {
price = 0
}
// 确保数量是数字
let quantity = item.quantity
if (quantity !== undefined && quantity !== null) {
quantity = typeof quantity === 'string' ? parseInt(quantity) : Number(item.quantity)
if (isNaN(quantity) || quantity < 1) quantity = 1
} else {
quantity = 1
}
return {
...item,
price: Number(price.toFixed(2)), // 保留两位小数
quantity: quantity
}
})
}
checkoutItems.value = items
// 调试:打印每个商品的价格
if (checkoutItems.value && checkoutItems.value.length > 0) {
console.log('清洗后商品价格明细:')
checkoutItems.value.forEach((item: any, index: number) => {
console.log(`商品${index}:`, item.product_name, '价格:', item.price, '类型:', typeof item.price, '数量:', item.quantity)
})
}
}
// 生命周期
onMounted(() => {
loadCheckoutData()
// 监听地址更新事件
uni.$on('addressUpdated', (updatedAddressList: any) => {
addressList.value = updatedAddressList
@@ -316,16 +468,40 @@ onUnmounted(() => {
uni.$off('addressUpdated')
})
// 加载结算数据
const loadCheckoutData = () => {
// 从上一页获取数据
const eventChannel = uni.getEventChannel()
if (eventChannel) {
eventChannel.on('acceptData', (data: any) => {
checkoutItems.value = data.selectedItems || []
loadDefaultAddress()
})
// 从本地存储加载结算数据(例如从购物车进入)
const loadFromLocalStorage = () => {
// 尝试从本地存储获取购物车选中的商品
const cartData = uni.getStorageSync('cart')
if (cartData) {
try {
const cartItems = JSON.parse(cartData as string) as any[]
// 筛选选中的商品
const selectedCartItems = cartItems.filter(item => item.selected === true)
if (selectedCartItems.length > 0) {
// 转换为CheckoutItemType格式
const convertedItems: CheckoutItemType[] = selectedCartItems.map(item => ({
id: item.id,
product_id: item.productId,
sku_id: item.id, // 购物车中item.id就是SKU ID
product_name: item.name,
product_image: item.image,
sku_specifications: item.spec ? { spec: item.spec } : {},
price: item.price,
quantity: item.quantity
}))
checkoutItems.value = convertedItems
}
} catch (e) {
console.error('解析购物车数据失败:', e)
}
}
loadDefaultAddress()
}
// 加载结算数据兼容旧版本现在主要在onLoad中处理
const loadCheckoutData = () => {
// 为了兼容性保留但主要逻辑已在onLoad中实现
loadFromLocalStorage()
}
// 加载默认地址
@@ -459,7 +635,7 @@ const saveNewAddress = async () => {
}
const userId = getCurrentUserId()
if (!userId) return
// if (!userId) return // 允许未登录用户保存地址用于演示
// 触发保存确认弹窗
showSaveConfirm.value = true
@@ -722,106 +898,52 @@ const submitOrder = async () => {
})
return
}
// 模拟提交成功跳转
// 实际项目中应等待API返回
uni.navigateTo({
url: `/pages/mall/consumer/payment?orderId=ORDER_MOCK_${Date.now()}&amount=${actualAmount.value}`
})
return;
const userId = getCurrentUserId()
if (!userId) {
uni.showToast({
title: '用户信息错误',
icon: 'none'
})
return
}
// 生成订单号
const orderNo = generateOrderNo()
const orderData = {
order_no: orderNo,
user_id: userId,
merchant_id: 'default', // 这里需要根据商品确定商家
status: 1, // 待支付
total_amount: totalAmount.value,
discount_amount: discountAmount.value,
delivery_fee: deliveryFee.value,
actual_amount: actualAmount.value,
payment_method: 0, // 待选择
payment_status: 0,
delivery_address: selectedAddress.value,
remark: remark.value,
created_at: new Date().toISOString()
}
try {
// 创建订单
/* const { data: order, error: orderError } = await supa
.from('orders')
.insert(orderData)
.select()
.single()
if (orderError !== null) {
throw orderError
}
// 创建订单商品项
const orderItems = checkoutItems.value.map(item => ({
order_id: order.id,
product_id: item.product_id,
sku_id: item.sku_id,
product_name: item.product_name,
sku_specifications: item.sku_specifications,
price: item.price,
quantity: item.quantity,
total_amount: item.price * item.quantity
}))
const { error: itemsError } = await supa
.from('order_items')
.insert(orderItems)
if (itemsError !== null) {
throw itemsError
}
// 使用优惠券
if (selectedCoupon.value) {
const { error: couponError } = await supa
.from('user_coupons')
.update({
status: 2, // 已使用
used_at: new Date().toISOString()
})
.eq('id', selectedCoupon.value.id)
if (couponError !== null) {
console.error('更新优惠券状态失败:', couponError)
}
}
// 清空购物车
await clearShoppingCart()
// 跳转到支付页面
uni.navigateTo({
url: `/pages/mall/consumer/payment?orderId=${order.id}&amount=${actualAmount.value}`
}) */
// MOCK ORDER SUBMISSION
// 模拟创建成功
try {
const mockOrderId = `order_${Date.now()}`
// MOCK ORDER SUBMISSION
// 模拟创建成功
const mockOrderId = `order_mock_${Date.now()}`
// 创建订单对象
const newOrder = {
id: mockOrderId,
order_no: generateOrderNo(),
user_id: getCurrentUserId() || 'user_001',
merchant_id: checkoutItems.value[0]?.product_id || 'merchant_001', // 简化处理取第一个商品的merchant
status: 1, // 待支付
total_amount: totalAmount.value,
discount_amount: discountAmount.value,
delivery_fee: deliveryFee.value,
actual_amount: actualAmount.value,
payment_method: 0,
payment_status: 0,
delivery_address: selectedAddress.value,
items: checkoutItems.value,
created_at: new Date().toISOString()
}
// 保存到本地存储
const storedOrders = uni.getStorageSync('orders')
let orders: any[] = []
if (storedOrders) {
try {
orders = JSON.parse(storedOrders as string) as any[]
} catch (e) {
console.error('解析订单数据失败', e)
}
}
orders.unshift(newOrder)
uni.setStorageSync('orders', JSON.stringify(orders))
uni.showLoading({ title: '提交中...' })
await new Promise(resolve => setTimeout(resolve, 500))
uni.hideLoading()
// 携带价格详情跳转
uni.navigateTo({
url: `/pages/mall/consumer/payment?orderId=${mockOrderId}&amount=${actualAmount.value}`
url: `/pages/mall/consumer/payment?orderId=${mockOrderId}&amount=${actualAmount.value}&productAmount=${totalAmount.value}&deliveryFee=${deliveryFee.value}&discountAmount=${discountAmount.value}`
})
} catch (err) {
} catch (err) {
console.error('创建订单失败:', err)
uni.showToast({
title: '订单创建失败',
@@ -967,6 +1089,20 @@ const goBack = () => {
padding: 0 15px;
}
.debug-info {
background-color: #f8f9fa;
padding: 10px 15px;
border-bottom: 1px solid #e5e5e5;
margin-bottom: 10px;
}
.debug-text {
font-size: 12px;
color: #666;
display: block;
text-align: center;
}
.product-item {
display: flex;
padding: 15px 0;
@@ -1505,4 +1641,76 @@ const goBack = () => {
font-size: 16px;
border: none;
}
/* 确认弹窗样式 */
.confirm-popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 10002;
}
.confirm-popup {
background-color: #ffffff;
width: 80%;
max-width: 320px;
border-radius: 12px;
overflow: hidden;
}
.confirm-header {
padding: 20px 0 10px;
text-align: center;
}
.confirm-title {
font-size: 18px;
font-weight: bold;
color: #333333;
}
.confirm-content {
padding: 0 20px 20px;
text-align: center;
}
.confirm-message {
font-size: 16px;
color: #666666;
}
.confirm-buttons {
display: flex;
border-top: 1px solid #e5e5e5;
}
.confirm-btn {
flex: 1;
height: 50px;
line-height: 50px;
text-align: center;
font-size: 16px;
background-color: #ffffff;
border: none;
border-radius: 0;
}
.confirm-btn::after {
border: none;
}
.confirm-btn.cancel {
color: #666666;
border-right: 1px solid #e5e5e5;
}
.confirm-btn.confirm {
color: #007aff;
font-weight: bold;
}
</style>