前端各页面对接数据

This commit is contained in:
2026-02-02 17:34:31 +08:00
parent d57592ca7d
commit b6200cda28
25 changed files with 7634 additions and 1977 deletions

File diff suppressed because one or more lines are too long

View File

@@ -168,6 +168,8 @@
<script setup lang="uts">
import { ref, computed, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import supabaseService from '@/utils/supabaseService.uts'
import type { CartItem } from '@/utils/supabaseService.uts'
// 响应式数据
const cartItems = ref<any[]>([])
@@ -289,23 +291,46 @@ onShow(() => {
})
// 加载数据
const loadCartData = () => {
const loadCartData = async () => {
loading.value = true
// 从本地存储加载购物车数据
const cartData = uni.getStorageSync('cart')
if (cartData) {
try {
cartItems.value = JSON.parse(cartData as string) as any[]
} catch (e) {
console.error('解析购物车数据失败', e)
try {
// 从Supabase获取购物车数据
const dbCartItems = await supabaseService.getCartItems()
console.log('从Supabase获取购物车数据:', dbCartItems)
// 将数据库CartItem映射为页面CartItem格式
cartItems.value = dbCartItems.map((item: CartItem) => ({
id: item.id, // 购物车项ID
productId: item.product_id, // 商品ID
shopId: item.shop_id || 'shop_default', // 店铺ID
shopName: item.shop_name || '自营店铺', // 店铺名称
name: item.product_name || '商品名称', // 商品名称
price: item.product_price || 0, // 商品价格
image: item.product_image || '/static/images/default-product.png', // 商品图片
spec: item.product_specification || '默认规格', // 商品规格
quantity: item.quantity, // 数量
selected: item.selected // 是否选中
}))
// 同时更新本地存储作为缓存
uni.setStorageSync('cart', JSON.stringify(cartItems.value))
console.log('购物车数据已从数据库加载,数量:', cartItems.value.length)
} catch (error) {
console.error('从Supabase加载购物车数据失败:', error)
// 如果数据库加载失败,尝试从本地存储恢复
const cartData = uni.getStorageSync('cart')
if (cartData) {
try {
cartItems.value = JSON.parse(cartData as string) as any[]
console.log('使用本地存储购物车数据,数量:', cartItems.value.length)
} catch (e) {
console.error('解析购物车数据失败', e)
cartItems.value = []
}
} else {
cartItems.value = []
}
} else {
// 如果本地没有数据使用Mock数据可选或者直接为空
// 为了演示效果这里可以保留一部分Mock数据或者初始化为空
// cartItems.value = [...mockCartItems]
cartItems.value = []
}
setTimeout(() => {
@@ -315,22 +340,42 @@ const loadCartData = () => {
}, 500)
}
// 监听购物车数据变化并保存到本地存储
// 保存购物车数据到本地存储(作为缓存)
const saveCartData = () => {
uni.setStorageSync('cart', JSON.stringify(cartItems.value))
}
// 商品操作 - 增加保存逻辑
const toggleSelect = (itemId: string) => {
// 商品操作 - 切换选择状态
const toggleSelect = async (itemId: string) => {
const index = cartItems.value.findIndex(item => item.id === itemId)
if (index !== -1) {
cartItems.value[index].selected = !cartItems.value[index].selected
cartItems.value = [...cartItems.value] // 触发响应式更新
saveCartData()
const newSelected = !cartItems.value[index].selected
try {
// 更新数据库中的选择状态
const success = await supabaseService.updateCartItemSelection(itemId, newSelected)
if (success) {
cartItems.value[index].selected = newSelected
cartItems.value = [...cartItems.value] // 触发响应式更新
saveCartData()
} else {
console.error('更新购物车项选择状态失败')
uni.showToast({
title: '操作失败',
icon: 'none'
})
}
} catch (error) {
console.error('更新购物车项选择状态异常:', error)
uni.showToast({
title: '操作失败',
icon: 'none'
})
}
}
}
const toggleShopSelect = (shopId: string) => {
const toggleShopSelect = async (shopId: string) => {
const group = cartGroups.value.find((g: any) => g.shopId === shopId)
if (!group) return
@@ -338,56 +383,164 @@ const toggleShopSelect = (shopId: string) => {
const isAllShopSelected = (group.items as any[]).every((item: any) => item.selected)
const newState = !isAllShopSelected
// 更新该店铺下所有商品的状态
cartItems.value.forEach(item => {
if (item.shopId === shopId) {
item.selected = newState
// 获取该店铺下所有购物车项的ID
const cartItemIds = cartItems.value
.filter(item => item.shopId === shopId)
.map(item => item.id)
if (cartItemIds.length === 0) return
try {
// 批量更新数据库中的选择状态
const success = await supabaseService.batchUpdateCartItemSelection(cartItemIds, newState)
if (success) {
// 更新本地状态
cartItems.value.forEach(item => {
if (item.shopId === shopId) {
item.selected = newState
}
})
cartItems.value = [...cartItems.value]
saveCartData()
} else {
console.error('批量更新购物车项选择状态失败')
uni.showToast({
title: '操作失败',
icon: 'none'
})
}
})
cartItems.value = [...cartItems.value]
saveCartData()
}
const toggleSelectAll = () => {
const newSelectedState = !allSelected.value
cartItems.value = cartItems.value.map(item => ({
...item,
selected: newSelectedState
}))
saveCartData()
}
const increaseQuantity = (itemId: string) => {
const index = cartItems.value.findIndex(item => item.id === itemId)
if (index !== -1) {
cartItems.value[index].quantity++
cartItems.value = [...cartItems.value]
saveCartData()
} catch (error) {
console.error('批量更新购物车项选择状态异常:', error)
uni.showToast({
title: '操作失败',
icon: 'none'
})
}
}
const decreaseQuantity = (itemId: string) => {
const toggleSelectAll = async () => {
const newSelectedState = !allSelected.value
// 获取所有购物车项的ID
const cartItemIds = cartItems.value.map(item => item.id)
if (cartItemIds.length === 0) return
try {
// 批量更新数据库中的选择状态
const success = await supabaseService.batchUpdateCartItemSelection(cartItemIds, newSelectedState)
if (success) {
// 更新本地状态
cartItems.value = cartItems.value.map(item => ({
...item,
selected: newSelectedState
}))
saveCartData()
} else {
console.error('批量更新全选状态失败')
uni.showToast({
title: '操作失败',
icon: 'none'
})
}
} catch (error) {
console.error('批量更新全选状态异常:', error)
uni.showToast({
title: '操作失败',
icon: 'none'
})
}
}
const increaseQuantity = async (itemId: string) => {
const index = cartItems.value.findIndex(item => item.id === itemId)
if (index !== -1) {
const newQuantity = cartItems.value[index].quantity + 1
try {
// 更新数据库中的数量
const success = await supabaseService.updateCartItemQuantity(itemId, newQuantity)
if (success) {
cartItems.value[index].quantity = newQuantity
cartItems.value = [...cartItems.value]
saveCartData()
} else {
console.error('更新购物车项数量失败')
uni.showToast({
title: '操作失败',
icon: 'none'
})
}
} catch (error) {
console.error('更新购物车项数量异常:', error)
uni.showToast({
title: '操作失败',
icon: 'none'
})
}
}
}
const decreaseQuantity = async (itemId: string) => {
const index = cartItems.value.findIndex(item => item.id === itemId)
if (index !== -1) {
if (cartItems.value[index].quantity > 1) {
cartItems.value[index].quantity--
cartItems.value = [...cartItems.value]
saveCartData()
const newQuantity = cartItems.value[index].quantity - 1
try {
// 更新数据库中的数量
const success = await supabaseService.updateCartItemQuantity(itemId, newQuantity)
if (success) {
cartItems.value[index].quantity = newQuantity
cartItems.value = [...cartItems.value]
saveCartData()
} else {
console.error('更新购物车项数量失败')
uni.showToast({
title: '操作失败',
icon: 'none'
})
}
} catch (error) {
console.error('更新购物车项数量异常:', error)
uni.showToast({
title: '操作失败',
icon: 'none'
})
}
} else {
// 数量为1时询问是否删除
uni.showModal({
title: '提示',
content: '确定要从购物车移除该商品吗?',
success: (res) => {
success: async (res) => {
if (res.confirm) {
cartItems.value.splice(index, 1)
cartItems.value = [...cartItems.value]
saveCartData()
uni.showToast({
title: '已移除',
icon: 'none'
})
try {
// 从数据库删除购物车项
const success = await supabaseService.deleteCartItem(itemId)
if (success) {
cartItems.value.splice(index, 1)
cartItems.value = [...cartItems.value]
saveCartData()
uni.showToast({
title: '已移除',
icon: 'success'
})
} else {
console.error('删除购物车项失败')
uni.showToast({
title: '删除失败',
icon: 'none'
})
}
} catch (error) {
console.error('删除购物车项异常:', error)
uni.showToast({
title: '删除失败',
icon: 'none'
})
}
}
}
})
@@ -395,8 +548,8 @@ const decreaseQuantity = (itemId: string) => {
}
}
// 删除商品 - 增加保存逻辑
const deleteSelectedItems = () => {
// 删除商品 - 批量删除选中的商品
const deleteSelectedItems = async () => {
if (selectedCount.value === 0) {
uni.showToast({
title: '请选择要删除的商品',
@@ -408,70 +561,75 @@ const deleteSelectedItems = () => {
uni.showModal({
title: '提示',
content: `确定要删除选中的 ${selectedCount.value} 件商品吗?`,
success: (res) => {
success: async (res) => {
if (res.confirm) {
cartItems.value = cartItems.value.filter(item => !item.selected)
saveCartData()
// 获取选中的购物车项ID
const selectedCartItemIds = cartItems.value
.filter(item => item.selected)
.map(item => item.id)
// 如果购物车删空了,退出管理模式
if (cartItems.value.length === 0) {
isManageMode.value = false
try {
// 批量从数据库删除购物车项
const success = await supabaseService.batchDeleteCartItems(selectedCartItemIds)
if (success) {
// 更新本地状态
cartItems.value = cartItems.value.filter(item => !item.selected)
saveCartData()
// 如果购物车删空了,退出管理模式
if (cartItems.value.length === 0) {
isManageMode.value = false
}
uni.showToast({
title: '删除成功',
icon: 'success'
})
} else {
console.error('批量删除购物车项失败')
uni.showToast({
title: '删除失败',
icon: 'none'
})
}
} catch (error) {
console.error('批量删除购物车项异常:', error)
uni.showToast({
title: '删除失败',
icon: 'none'
})
}
uni.showToast({
title: '删除成功',
icon: 'success'
})
}
}
})
}
const addToCart = (product: any) => {
// 获取现有购物车数据
const cartData = uni.getStorageSync('cart')
let currentItems: any[] = []
if (cartData) {
try {
currentItems = JSON.parse(cartData as string) as any[]
} catch (e) {
console.error('解析购物车数据失败', e)
const addToCart = async (product: any) => {
try {
// 调用SupabaseService添加商品到购物车
const success = await supabaseService.addToCart(product.id, 1, product.skuId)
if (success) {
uni.showToast({
title: '已添加到购物车',
icon: 'success'
})
// 重新加载购物车数据
loadCartData()
} else {
console.error('添加商品到购物车失败')
uni.showToast({
title: '添加失败',
icon: 'none'
})
}
}
// 检查商品是否已存在 (使用商品ID匹配因为推荐商品没有SKU)
const existingItem = currentItems.find((item: any) =>
item.productId === product.id || item.id === product.id
)
if (existingItem) {
existingItem.quantity++
} else {
// 添加新商品
currentItems.push({
id: product.id, // 商品ID因为没有SKU
productId: product.id, // 同样存储商品ID
shopId: product.shopId || 'shop_recommend',
shopName: product.shopName || '推荐好物',
name: product.name,
price: product.price,
image: product.image,
spec: product.specification || '默认规格', // 优先使用商品自带的规格
quantity: 1,
selected: true
} catch (error) {
console.error('添加商品到购物车异常:', error)
uni.showToast({
title: '添加失败',
icon: 'none'
})
}
// 保存回存储
uni.setStorageSync('cart', JSON.stringify(currentItems))
uni.showToast({
title: '已添加到购物车',
icon: 'success'
})
// 立即刷新当前列表
loadCartData()
}
// 导航函数

View File

@@ -523,52 +523,32 @@ const deleteSelectedItems = async () => {
})
}
const addToCart = (product: any) => {
// 获取现有购物车数据
const cartData = uni.getStorageSync('cart')
let currentItems: any[] = []
if (cartData) {
try {
currentItems = JSON.parse(cartData as string) as any[]
} catch (e) {
console.error('解析购物车数据失败', e)
const addToCart = async (product: any) => {
try {
// 调用SupabaseService添加商品到购物车
const success = await supabaseService.addToCart(product.id, 1, product.skuId)
if (success) {
uni.showToast({
title: '已添加到购物车',
icon: 'success'
})
// 重新加载购物车数据
loadCartData()
} else {
console.error('添加商品到购物车失败')
uni.showToast({
title: '添加失败',
icon: 'none'
})
}
}
// 检查商品是否已存在 (使用商品ID匹配因为推荐商品没有SKU)
const existingItem = currentItems.find((item: any) =>
item.productId === product.id || item.id === product.id
)
if (existingItem) {
existingItem.quantity++
} else {
// 添加新商品
currentItems.push({
id: product.id, // 商品ID因为没有SKU
productId: product.id, // 同样存储商品ID
shopId: product.shopId || 'shop_recommend',
shopName: product.shopName || '推荐好物',
name: product.name,
price: product.price,
image: product.image,
spec: product.specification || '默认规格', // 优先使用商品自带的规格
quantity: 1,
selected: true
} catch (error) {
console.error('添加商品到购物车异常:', error)
uni.showToast({
title: '添加失败',
icon: 'none'
})
}
// 保存回存储
uni.setStorageSync('cart', JSON.stringify(currentItems))
uni.showToast({
title: '已添加到购物车',
icon: 'success'
})
// 立即刷新当前列表
loadCartData()
}
// 导航函数

File diff suppressed because it is too large Load Diff

View File

@@ -122,6 +122,7 @@ const productList = ref<Product[]>([])
const activePrimary = ref<string>('')
const cartCount = ref(3)
const hasMore = ref(true)
const hasLoadedFromParams = ref(false) // 标记是否已通过参数加载
// 获取当前分类信息
const currentCategoryName = ref('')
@@ -134,34 +135,67 @@ const pageParams = ref<any>({})
// 生命周期
onMounted(async() => {
await loadCategories()
await loadProducts()
// 等待分类加载完成后,再检查是否需要加载默认分类的商品
// 延迟一点时间,确保页面参数处理完成
setTimeout(async () => {
if (!hasLoadedFromParams.value && activePrimary.value) {
await loadProducts()
}
}, 300)
})
// 添加加载分类的方法
const loadCategories = async () => {
const categories = await supabaseService.getCategories()
if (categories.length > 0) {
primaryCategories.value = categories
// 设置默认选中第一个分类
if (!activePrimary.value && categories[0]) {
activePrimary.value = categories[0].id
try {
const categories = await supabaseService.getCategories()
console.log('加载分类数据成功,数量:', categories.length)
if (categories.length > 0) {
primaryCategories.value = categories
// 如果没有通过参数设置分类,则设置默认选中第一个分类
if (!activePrimary.value && categories[0]) {
activePrimary.value = categories[0].id
console.log('设置默认分类为:', categories[0].name, 'ID:', categories[0].id)
}
} else {
console.warn('从Supabase获取的分类数据为空')
}
} catch (error) {
console.error('加载分类数据失败:', error)
}
}
// 加载商品数据
const loadProducts = async () => {
if (activePrimary.value) {
const response = await supabaseService.getProductsByCategory(activePrimary.value)
productList.value = response.data
hasMore.value = response.hasmore
// 更新当前分类信息
const category = primaryCategories.value.find(cat => cat.id === activePrimary.value)
if (category) {
currentCategoryName.value = category.name
currentCategoryDesc.value = category.description
try {
if (activePrimary.value) {
console.log('开始加载商品分类ID:', activePrimary.value)
const response = await supabaseService.getProductsByCategory(activePrimary.value)
console.log('商品加载结果:', {
dataCount: response.data.length,
total: response.total,
hasmore: response.hasmore
})
productList.value = response.data
hasMore.value = response.hasmore
// 更新当前分类信息
const category = primaryCategories.value.find(cat => cat.id === activePrimary.value)
if (category) {
currentCategoryName.value = category.name
currentCategoryDesc.value = category.description || ''
console.log('当前分类信息:', category.name, '描述:', category.description)
} else {
console.warn('未找到对应的分类信息分类ID:', activePrimary.value)
}
console.log('商品列表加载完成,数量:', productList.value.length)
} else {
console.warn('activePrimary为空无法加载商品')
}
} catch (error) {
console.error('加载商品数据失败:', error)
productList.value = []
}
}
@@ -200,6 +234,7 @@ onLoad((options: any) => {
// 如果有找到分类ID则选中对应的分类
if (categoryId) {
hasLoadedFromParams.value = true
console.log('✅ 准备选中分类:', categoryId)
console.log('分类名称:', categoryName || '未指定')
@@ -244,6 +279,7 @@ onShow(() => {
// 检查是否有分类参数
if (pageOptions.categoryId) {
hasLoadedFromParams.value = true
const categoryId = pageOptions.categoryId
const categoryName = pageOptions.name || ''
@@ -288,6 +324,7 @@ onShow(() => {
const params = new URLSearchParams(queryString)
const urlCategoryId = params.get('categoryId')
if (urlCategoryId) {
hasLoadedFromParams.value = true
console.log('✅ 从URL解析到分类参数:', urlCategoryId)
selectPrimaryCategory(urlCategoryId)
}

File diff suppressed because it is too large Load Diff

View File

@@ -772,64 +772,10 @@ const loadDefaultAddress = async () => {
// 获取当前用户ID
const getCurrentUserId = (): string => {
// 尝试从多个可能的键名获取用户ID
const possibleKeys = ['user_id', 'userId', 'uid', 'user_uuid', 'userID', 'user.id']
for (const key of possibleKeys) {
const value = uni.getStorageSync(key)
console.log(`getCurrentUserId: 尝试键名 ${key}:`, value)
if (value) {
console.log(`getCurrentUserId: 从 ${key} 获取到用户ID:`, value)
return value as string
}
}
// 尝试从userInfo对象获取
const userInfo = uni.getStorageSync('userInfo')
console.log('getCurrentUserId: 从userInfo获取:', userInfo)
if (userInfo) {
// userInfo可能是字符串需要解析或对象
let userInfoObj: any = userInfo
if (typeof userInfo === 'string') {
try {
userInfoObj = JSON.parse(userInfo)
} catch (e) {
console.error('解析userInfo失败:', e)
}
}
// 尝试多个可能的属性名
const possibleProps = ['id', 'userId', 'uid', 'user_id', 'uuid', 'user_uuid']
for (const prop of possibleProps) {
if (userInfoObj && userInfoObj[prop]) {
console.log(`getCurrentUserId: 从userInfo.${prop} 获取到用户ID:`, userInfoObj[prop])
return userInfoObj[prop] as string
}
}
}
// 尝试从auth获取如果使用Supabase Auth
const authData = uni.getStorageSync('supabase.auth.token')
if (authData) {
console.log('getCurrentUserId: 从supabase.auth.token获取:', authData)
try {
const authObj = typeof authData === 'string' ? JSON.parse(authData) : authData
if (authObj.currentSession && authObj.currentSession.user && authObj.currentSession.user.id) {
console.log('getCurrentUserId: 从auth session获取用户ID:', authObj.currentSession.user.id)
return authObj.currentSession.user.id as string
}
} catch (e) {
console.error('解析auth数据失败:', e)
}
}
// 打印所有存储键,用于调试
console.log('getCurrentUserId: 所有Storage键:')
const allKeys = uni.getStorageInfoSync().keys
console.log('Storage keys:', allKeys)
console.log('getCurrentUserId: 未找到用户ID')
return ''
// 使用 SupabaseService 获取当前用户ID
const userId = supabaseService.getCurrentUserId()
console.log('getCurrentUserId: 从SupabaseService获取到用户ID:', userId)
return userId ?? ''
}
// 用户登录状态
@@ -1280,65 +1226,89 @@ const selectCoupon = () => {
// 提交订单
const submitOrder = async () => {
if (!selectedAddress.value) {
uni.showToast({
title: '请选择收货地址',
icon: 'none'
})
return
}
// 校验地址
if (!selectedAddress.value) {
uni.showToast({
title: '请选择收货地址',
icon: 'none'
})
return
}
// 校验商品
if (checkoutItems.value.length === 0) {
uni.showToast({
title: '订单中没有商品',
icon: 'none'
})
return
}
uni.showLoading({ title: '提交中...' })
// MOCK ORDER SUBMISSION
// 模拟创建成功
try {
const mockOrderId = `order_${Date.now()}`
const userId = getCurrentUserId()
// 确保使用当前登录用户ID (如果本地存储为空,可能需要处理)
if (!userId) {
uni.hideLoading()
uni.showToast({
title: '请先登录',
icon: 'none'
})
return
}
// 创建订单对象
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()
}
// 准备订单项数据
// 注意:需根据 checkoutItems 的实际结构转换为 createOrder 需要的 CartItem 结构
// 假设 checkoutItems 已经包含了 product_id, quantity, price, name, image 等字段
const orderItems = checkoutItems.value.map((item: any): any => ({
id: item.id || '', // 这是一个临时ID或者购物车IDcreateOrder 中会使用 product_id
product_id: item.product_id || item.id, // 确保有 product_id
quantity: item.quantity,
price: item.price,
product_name: item.name,
product_image: item.image,
spec: item.spec,
checked: true
}))
// 保存到本地存储
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))
// 调用 Supabase 服务创建订单
const result = await supabaseService.createOrder(
userId,
selectedAddress.value!.id, // 地址ID
actualAmount.value, // 实付金额
orderItems
)
uni.hideLoading()
// 携带价格详情跳转
uni.navigateTo({
url: `/pages/mall/consumer/payment?orderId=${mockOrderId}&amount=${actualAmount.value}&productAmount=${totalAmount.value}&deliveryFee=${deliveryFee.value}&discountAmount=${discountAmount.value}`
})
} catch (err) {
console.error('创建订单失败:', err)
uni.showToast({
title: '订单创建失败',
icon: 'none'
})
}
if (result.success) {
// 清除购买的商品 (如果来自购物车,应该在 createOrder 成功后清除,或者这里手动清除本地存储)
// 这里我们假设购物车清理逻辑可能在 createOrder 后端处理,或者需要在这里清除本地
try {
uni.removeStorageSync('checkout_items')
} catch(e) {
console.error('清除结算商品失败', e)
}
const activeOrderId = result.data as string
// 跳转支付页面
uni.navigateTo({
url: `/pages/mall/consumer/payment?orderId=${activeOrderId}&amount=${actualAmount.value}&productAmount=${totalAmount.value}&deliveryFee=${deliveryFee.value}&discountAmount=${discountAmount.value}`
})
} else {
throw new Error(result.error)
}
} catch (err: any) {
uni.hideLoading()
console.error('创建订单失败:', err)
uni.showToast({
title: err.message || '订单创建失败',
icon: 'none'
})
}
}
// 生成订单号

View File

@@ -31,6 +31,7 @@
<script setup lang="uts">
import { ref, onMounted } from 'vue'
import { supabaseService } from '@/utils/supabaseService.uts'
type Product = {
id: string
@@ -48,61 +49,46 @@ onMounted(() => {
loadFavorites()
})
const addToCart = (product: Product) => {
// 获取现有购物车数据
const cartData = uni.getStorageSync('cart')
let cartItems: any[] = []
if (cartData) {
try {
cartItems = JSON.parse(cartData as string) as any[]
} catch (e) {
console.error('解析购物车数据失败', e)
}
}
// 检查商品是否已存在
const existingItem = cartItems.find((item: any) => item.id === product.id)
if (existingItem) {
existingItem.quantity++
const addToCart = async (product: Product) => {
uni.showLoading({ title: '添加中' })
const success = await supabaseService.addToCart(product.id, 1)
uni.hideLoading()
if (success) {
uni.showToast({ title: '已添加到购物车', icon: 'success' })
} else {
// 添加新商品
cartItems.push({
id: product.id,
shopId: product.shopId || 'shop_favorite_default',
shopName: product.shopName || '收藏店铺',
name: product.name,
price: product.price,
image: product.image,
spec: '默认规格',
quantity: 1,
selected: true
})
uni.showToast({ title: '添加失败', icon: 'none' })
}
// 保存回存储
uni.setStorageSync('cart', JSON.stringify(cartItems))
uni.showToast({
title: '已添加到购物车',
icon: 'success'
})
}
const loadFavorites = () => {
// 从本地存储获取收藏列表
const storedFavorites = uni.getStorageSync('favorites')
if (storedFavorites) {
try {
favorites.value = JSON.parse(storedFavorites as string) as Product[]
} catch (e) {
console.error('Failed to parse favorites', e)
favorites.value = []
}
} else {
favorites.value = []
}
const loadFavorites = async () => {
const res = await supabaseService.getFavorites()
// Map response
favorites.value = res.map((item: any): Product => {
const prod = item.ml_products
let image = '/static/default-product.png'
if (prod) {
if (prod.main_image_url) image = prod.main_image_url
else if (prod.image_url) image = prod.image_url
else if (prod.image_urls) {
// Try parse
try {
const arr = JSON.parse(prod.image_urls)
if (Array.isArray(arr) && arr.length > 0) image = arr[0]
} catch(e) {}
}
}
return {
id: prod?.id || item.target_id,
name: prod?.name || '未知商品',
price: prod?.price || 0,
image: image,
sales: prod?.sales || 0,
shopId: '',
shopName: ''
}
})
}
const goShopping = () => {
@@ -111,26 +97,29 @@ const goShopping = () => {
})
}
const goToDetail = (product: Product) => {
const goToDetail = (id: string) => {
uni.navigateTo({
url: `/pages/mall/consumer/product-detail?productId=${product.id}&price=${product.price}&originalPrice=${product.original_price || ''}`
url: `/pages/mall/consumer/product-detail?productId=${id}`
})
}
const removeFavorite = (id: string) => {
const removeFavorite = async (id: string) => {
uni.showModal({
title: '取消收藏',
content: '确定要取消收藏该商品吗?',
success: (res) => {
success: async (res) => {
if (res.confirm) {
const index = favorites.value.findIndex(item => item.id === id)
if (index !== -1) {
favorites.value.splice(index, 1)
uni.setStorageSync('favorites', JSON.stringify(favorites.value))
uni.showToast({
title: '已取消收藏',
icon: 'none'
})
const success = await supabaseService.toggleFavorite(id) // Toggle removes if exists
if (success) {
// Remove from local list
const index = favorites.value.findIndex(item => item.id === id)
if (index !== -1) {
favorites.value.splice(index, 1)
}
uni.showToast({
title: '已取消收藏',
icon: 'none'
})
}
}
}

View File

@@ -271,12 +271,14 @@ import { ref, reactive, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import supabaseService from '@/utils/supabaseService.uts'
import type { Product, Category } from '@/utils/supabaseService.uts'
import { getCurrentUser } from '@/utils/store.uts'
// 响应式数据
const statusBarHeight = ref(0)
const scrollHeight = ref(0)
const refreshing = ref(false)
const loading = ref(false)
const isFirstShow = ref(true)
const hasMore = ref(true)
const activeSort = ref('sales')
const activeFilter = ref('recommend')
@@ -334,13 +336,13 @@ const healthNews = [
const loadCategories = async () => {
try {
const categoriesData = await supabaseService.getCategories()
// 映射字段:将description映射为desc保持与原有结构兼容
// 映射字段:根据ml_categories表结构映射
categories.value = categoriesData.map((cat: any) => ({
id: cat.id,
name: cat.name,
icon: cat.icon || '📦',
desc: cat.description || cat.desc || '',
color: cat.color || '#4CAF50'
icon: cat.icon_url || '📦', // 使用icon_url字段
desc: cat.description || '', // 使用description字段
color: '#4CAF50' // 默认颜色表中可能没有color字段
}))
} catch (error) {
console.error('加载分类数据失败:', error)
@@ -408,6 +410,13 @@ const loadRecommendedProducts = async (limit: number = 6) => {
// 初始化数据
const initData = async () => {
// 首先确保用户资料已加载
try {
await getCurrentUser()
console.log('主页初始化:用户资料加载完成')
} catch (error) {
console.error('加载用户资料失败:', error)
}
await loadCategories()
await loadHotProducts()
await loadRecommendedProducts()
@@ -489,6 +498,22 @@ onShow(() => {
// 让分类页面在成功读取后自行清除
// 这样可以确保分类页面能正确读取到传递的数据
// 每次页面显示时尝试更新用户资料
if (!isFirstShow.value) {
getCurrentUser().then(profile => {
if (profile) {
console.log('主页onShow用户资料更新成功')
} else {
console.log('主页onShow用户资料为空可能未登录')
}
}).catch(error => {
console.error('主页onShow加载用户资料失败:', error)
})
} else {
isFirstShow.value = false
console.log('主页首次显示跳过onShow中的用户资料检查交由initData处理')
}
console.log('=== index页面onShow执行完成 ===')
})

File diff suppressed because it is too large Load Diff

View File

@@ -156,9 +156,11 @@
<script setup lang="uts">
import { ref, reactive, onMounted, computed } from 'vue'
import { onShow, onLoad } from '@dcloudio/uni-app'
import { supabaseService } from '@/utils/supabaseService.uts'
// 响应式数据
const orders = ref<any[]>([])
const allOrdersList = ref<any[]>([]) // Store all fetched orders for client-side filtering
const loading = ref<boolean>(false)
const loadingMore = ref<boolean>(false)
const hasMore = ref<boolean>(true)
@@ -169,144 +171,16 @@ const searchKeyword = ref<string>('')
// 订单标签页
const orderTabs = reactive([
{ id: 'all', name: '全部', count: 12 },
{ id: 'pending', name: '待付款', count: 2 },
{ id: 'shipping', name: '待发货', count: 1 },
{ id: 'delivering', name: '待收货', count: 3 },
{ id: 'completed', name: '已完成', count: 5 },
{ id: 'cancelled', name: '已取消', count: 1 }
{ id: 'all', name: '全部', count: 0 },
{ id: 'pending', name: '待付款', count: 0 },
{ id: 'shipping', name: '待发货', count: 0 },
{ id: 'delivering', name: '待收货', count: 0 },
{ id: 'completed', name: '已完成', count: 0 },
{ id: 'cancelled', name: '已取消', count: 0 }
])
// Mock 订单数据
const mockOrders = [
{
id: '202311230001',
order_no: '202311230001',
status: 1, // 1:待付款 2:待发货 3:待收货 4:已完成 5:已取消
create_time: '2023-11-23 14:30:22',
product_amount: 378.00,
shipping_fee: 0.00,
total_amount: 378.00,
products: [
{
id: '1001',
name: '无线蓝牙耳机 降噪版',
price: 299.00,
image: 'https://picsum.photos/80/80?random=1',
spec: '白色',
quantity: 1
},
{
id: '1002',
name: '耳机保护套',
price: 29.00,
image: 'https://picsum.photos/80/80?random=2',
spec: '黑色',
quantity: 1
},
{
id: '1003',
name: '数据线',
price: 19.00,
image: 'https://picsum.photos/80/80?random=3',
spec: '1米',
quantity: 2
}
]
},
{
id: '202311220001',
order_no: '202311220001',
status: 2,
create_time: '2023-11-22 10:15:33',
product_amount: 199.00,
shipping_fee: 10.00,
total_amount: 209.00,
products: [
{
id: '2001',
name: '运动T恤 速干面料',
price: 79.00,
image: 'https://picsum.photos/80/80?random=4',
spec: '黑色 L',
quantity: 2
},
{
id: '2002',
name: '运动短裤',
price: 59.00,
image: 'https://picsum.photos/80/80?random=5',
spec: '黑色 M',
quantity: 1
}
]
},
{
id: '202311210001',
order_no: '202311210001',
status: 3,
create_time: '2023-11-21 16:45:12',
product_amount: 299.00,
shipping_fee: 0.00,
total_amount: 299.00,
products: [
{
id: '3001',
name: '智能手环 心率监测',
price: 199.00,
image: 'https://picsum.photos/80/80?random=6',
spec: '黑色',
quantity: 1
},
{
id: '3002',
name: '手环腕带',
price: 29.00,
image: 'https://picsum.photos/80/80?random=7',
spec: '蓝色',
quantity: 2
}
]
},
{
id: '202311200001',
order_no: '202311200001',
status: 4,
create_time: '2023-11-20 09:30:45',
product_amount: 99.00,
shipping_fee: 0.00,
total_amount: 99.00,
products: [
{
id: '4001',
name: '保温杯 500ml',
price: 49.00,
image: 'https://picsum.photos/80/80?random=8',
spec: '白色',
quantity: 2
}
]
},
{
id: '202311190001',
order_no: '202311190001',
status: 5,
create_time: '2023-11-19 14:20:18',
product_amount: 599.00,
shipping_fee: 0.00,
total_amount: 599.00,
products: [
{
id: '5001',
name: '蓝牙音箱 便携式',
price: 199.00,
image: 'https://picsum.photos/80/80?random=9',
spec: '黑色',
quantity: 3
}
]
}
]
// Removed Mock Data
// 计算属性:根据当前标签筛选订单
const filteredOrders = computed(() => {
@@ -349,90 +223,79 @@ onShow(() => {
// 加载订单数据
const loadOrders = async () => {
loading.value = true
const userStore = uni.getStorageSync('userInfo')
const userId = userStore?.id
if (!userId) {
loading.value = false
return
}
try {
// 从本地存储获取订单
const ordersStr = uni.getStorageSync('orders')
let localOrders: any[] = []
if (ordersStr) {
localOrders = JSON.parse(ordersStr as string) as any[]
}
// 如果本地存储为空,使用 Mock 数据
if (localOrders.length === 0) {
localOrders = mockOrders
// 可选:将 Mock 数据写入本地存储,以便后续操作生效
// uni.setStorageSync('orders', JSON.stringify(mockOrders))
}
// Fetch all orders from Supabase (status=0)
const fetchedOrders = await supabaseService.getOrders(0)
// 过滤当前用户的订单
// const userOrders = localOrders.filter((o: any) => o.user_id === userId)
// 暂时显示所有订单用于测试
let userOrders = localOrders
// 根据标签页过滤
let filtered = userOrders
const statusMap: Record<string, number> = {
'pending': 1,
'shipping': 2,
'delivering': 3,
'completed': 4,
'cancelled': 5
}
if (activeTab.value !== 'all') {
const targetStatus = statusMap[activeTab.value]
filtered = userOrders.filter((o: any) => o.status === targetStatus)
}
// 按时间倒序
filtered.sort((a: any, b: any) => {
const timeA = new Date(a.created_at || a.create_time).getTime()
const timeB = new Date(b.created_at || b.create_time).getTime()
return timeB - timeA
})
// 处理数据格式以适配当前页面
orders.value = filtered.map((order: any) => ({
// Map to View Model
const mappedOrders = fetchedOrders.map((order: any) => ({
id: order.id,
order_no: order.order_no,
status: order.status,
create_time: order.created_at || order.create_time,
product_amount: order.total_amount,
status: order.order_status,
create_time: order.created_at,
product_amount: order.product_amount || order.actual_amount,
shipping_fee: order.delivery_fee,
total_amount: order.actual_amount,
products: (order.items || order.products || []).map((item: any) => ({
id: item.product_id || item.id,
name: item.product_name || item.name,
products: (order.ml_order_items || []).map((item: any) => ({
id: item.product_id,
name: item.product_name,
price: item.price,
image: item.product_image || item.image || '/static/default-product.png',
spec: item.sku_specifications ? formatSpec(item.sku_specifications) : (item.spec || ''),
image: item.product_image,
spec: item.spec || '',
quantity: item.quantity
}))
}))
// Sort by created_at desc
mappedOrders.sort((a: any, b: any) => {
const timeA = new Date(a.create_time).getTime()
const timeB = new Date(b.create_time).getTime()
return timeB - timeA
})
allOrdersList.value = mappedOrders
// 更新统计数据
orderTabs[0].count = userOrders.length
orderTabs[1].count = userOrders.filter((o: any) => o.status === 1).length
orderTabs[2].count = userOrders.filter((o: any) => o.status === 2).length
orderTabs[3].count = userOrders.filter((o: any) => o.status === 3).length
orderTabs[4].count = userOrders.filter((o: any) => o.status === 4).length
orderTabs[5].count = userOrders.filter((o: any) => o.status === 5).length
// Update tab counts
updateTabsCounts(mappedOrders)
// Apply current tab filter
filterOrdersByTab()
} catch (err) {
console.error('加载订单异常:', err)
uni.showToast({ title: '加载订单失败', icon: 'none' })
} finally {
loading.value = false
}
}
const updateTabsCounts = (allOrders: any[]) => {
orderTabs[0].count = allOrders.length
orderTabs[1].count = allOrders.filter((o: any) => o.status === 1).length
orderTabs[2].count = allOrders.filter((o: any) => o.status === 2).length
orderTabs[3].count = allOrders.filter((o: any) => o.status === 3).length
orderTabs[4].count = allOrders.filter((o: any) => o.status === 4).length
orderTabs[5].count = allOrders.filter((o: any) => o.status === 5).length
}
const filterOrdersByTab = () => {
const statusMap: Record<string, number> = {
'pending': 1,
'shipping': 2,
'delivering': 3,
'completed': 4,
'cancelled': 5
}
if (activeTab.value === 'all') {
orders.value = allOrdersList.value
} else {
const targetStatus = statusMap[activeTab.value]
orders.value = allOrdersList.value.filter((o: any) => o.status === targetStatus)
}
}
const formatDate = (isoString: string): string => {
if (!isoString) return ''
const date = new Date(isoString)
@@ -483,9 +346,7 @@ const performSearch = () => {
}
const getCurrentOrderData = () => {
// 这里应该从本地存储或API获取完整订单数据
// 暂时返回当前orders.value
return orders.value
return allOrdersList.value
}
const formatSpec = (specs: any): string => {
@@ -499,9 +360,7 @@ const formatSpec = (specs: any): string => {
// 切换标签
const switchTab = (tabId: string) => {
activeTab.value = tabId
page.value = 1
orders.value = []
loadOrders()
filterOrdersByTab()
}
// 获取状态文本

View File

@@ -34,6 +34,23 @@
<text class="enter-shop" @click.stop="goToShop">进店 ></text>
</view>
<!-- 功能主治(药品功能) -->
<view class="function-section" v-if="product.usage">
<text class="function-title">功能主治</text>
<text class="function-content">{{ product.usage }}</text>
</view>
<!-- 商品参数 -->
<view class="params-section" @click="showParamsModal">
<text class="params-title">商品参数</text>
<view class="params-summary">
<text class="params-item" v-if="product.specification">规格: {{ product.specification }}</text>
<text class="params-item" v-if="product.expiry_date">有效期: {{ product.expiry_date }}</text>
<text class="params-item" v-if="product.approval_number">批准文号: {{ product.approval_number }}</text>
</view>
<text class="params-arrow">></text>
</view>
<!-- 规格选择 -->
<view class="spec-section" @click="showSpecModal">
<text class="spec-title">规格</text>
@@ -113,6 +130,50 @@
</view>
</view>
</view>
<!-- 商品参数弹窗 -->
<view v-if="showParams" class="params-modal" @click="hideParamsModal">
<view class="params-content" @click.stop>
<view class="params-header">
<text class="params-title">商品参数</text>
<text class="close-btn" @click="hideParamsModal">×</text>
</view>
<view class="params-list">
<view class="params-item" v-if="product.specification">
<text class="params-label">规格</text>
<text class="params-value">{{ product.specification }}</text>
</view>
<view class="params-item" v-if="product.usage">
<text class="params-label">功能主治</text>
<text class="params-value">{{ product.usage }}</text>
</view>
<view class="params-item" v-if="product.side_effects">
<text class="params-label">副作用</text>
<text class="params-value">{{ product.side_effects }}</text>
</view>
<view class="params-item" v-if="product.precautions">
<text class="params-label">注意事项</text>
<text class="params-value">{{ product.precautions }}</text>
</view>
<view class="params-item" v-if="product.expiry_date">
<text class="params-label">有效期</text>
<text class="params-value">{{ product.expiry_date }}</text>
</view>
<view class="params-item" v-if="product.storage_conditions">
<text class="params-label">储存条件</text>
<text class="params-value">{{ product.storage_conditions }}</text>
</view>
<view class="params-item" v-if="product.approval_number">
<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">
<text class="params-label">标签</text>
<text class="params-value">{{ product.tags.join(', ') }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
@@ -157,7 +218,8 @@ export default {
selectedSkuId: '',
selectedSpec: '',
quantity: 1,
isFavorite: false
isFavorite: false,
showParams: false
}
},
onLoad(options: any) {
@@ -415,7 +477,16 @@ export default {
stock: stock,
sales: sales,
status: 1,
created_at: dbProduct.created_at || '2024-01-01'
created_at: dbProduct.created_at || '2024-01-01',
// 药品相关字段
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,
tags: dbProduct.tags ? (typeof dbProduct.tags === 'string' ? JSON.parse(dbProduct.tags) : dbProduct.tags) : []
} as ProductType
console.log('页面 product 对象已更新:', this.product)
console.log('商品图片数组:', this.product.images)
@@ -781,6 +852,14 @@ export default {
current: index,
urls: this.product.images
})
},
showParamsModal() {
this.showParams = true
},
hideParamsModal() {
this.showParams = false
}
}
}
@@ -1149,6 +1228,131 @@ export default {
text-align: right;
}
/* 功能主治样式 */
.function-section {
background-color: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
}
.function-title {
font-size: 30rpx;
color: #333;
font-weight: bold;
margin-bottom: 15rpx;
display: block;
}
.function-content {
font-size: 28rpx;
color: #666;
line-height: 1.5;
}
/* 商品参数样式 */
.params-section {
background-color: #fff;
padding: 30rpx;
margin-bottom: 20rpx;
display: flex;
align-items: center;
}
.params-title {
font-size: 30rpx;
color: #333;
width: 120rpx;
flex-shrink: 0;
}
.params-summary {
flex: 1;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
}
.params-item {
font-size: 26rpx;
color: #666;
line-height: 1.5;
margin-right: 20rpx;
margin-bottom: 5rpx;
white-space: nowrap;
}
.params-arrow {
font-size: 28rpx;
color: #999;
flex-shrink: 0;
margin-left: 10rpx;
}
/* 商品参数弹窗样式 */
.params-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: 1000;
}
.params-content {
background-color: #fff;
width: 100%;
max-height: 80vh;
border-radius: 20rpx 20rpx 0 0;
padding: 30rpx;
}
.params-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
padding-bottom: 20rpx;
border-bottom: 1rpx solid #eee;
}
.params-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
width: auto;
}
.params-list {
max-height: 60vh;
overflow-y: auto;
}
.params-item {
display: flex;
align-items: flex-start;
padding: 20rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.params-label {
font-size: 28rpx;
color: #333;
font-weight: bold;
width: 150rpx;
flex-shrink: 0;
}
.params-value {
flex: 1;
font-size: 28rpx;
color: #666;
line-height: 1.5;
}
/* 商品详情图片样式 */
.detail-images {
margin-top: 30rpx;
@@ -1160,4 +1364,29 @@ export default {
border-radius: 10rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}
/* 电脑端适配 */
@media (min-width: 768px) {
.params-section {
padding: 20rpx 30rpx;
}
.params-summary {
flex-wrap: nowrap;
justify-content: space-between;
}
.params-item {
flex: 1;
margin-right: 0;
text-align: center;
white-space: normal;
word-break: break-word;
padding: 0 10rpx;
}
.params-arrow {
margin-left: 20rpx;
}
}
</style>

View File

@@ -373,7 +373,18 @@ export default {
// 尝试多种方式访问属性
const idValue = dbProduct.id !== undefined ? dbProduct.id : (dbProduct['id'] !== undefined ? dbProduct['id'] : undefined)
const nameValue = dbProduct.name !== undefined ? dbProduct.name : (dbProduct['name'] !== undefined ? dbProduct['name'] : undefined)
const priceValue = dbProduct.price !== undefined ? dbProduct.price : (dbProduct['price'] !== undefined ? dbProduct['price'] : undefined)
// 价格字段兼容性处理:优先查找 price其次查找 base_price
let priceValue = dbProduct.price
if (priceValue === undefined || priceValue === null) {
priceValue = dbProduct.base_price
}
if (priceValue === undefined || priceValue === null) {
priceValue = dbProduct['price']
}
if (priceValue === undefined || priceValue === null) {
priceValue = dbProduct['base_price']
}
const hasId = idValue !== undefined && idValue !== null
const hasName = nameValue !== undefined && nameValue !== null
@@ -396,33 +407,27 @@ export default {
// 数据库Product接口和本地ProductType接口字段可能不同
const images = [] as Array<string>
// 处理图片字段优先使用images字段其次使用image字段
console.log('处理数据库图片字段:')
console.log('dbProduct.images:', dbProduct.images, '类型:', typeof dbProduct.images)
console.log('dbProduct.image:', dbProduct.image, '类型:', typeof dbProduct.image)
// 处理图片字段优先使用image_urls字段其次使用main_image_url
console.log('处理数据库图片字段')
// 尝试从数据库的images字段获取图片可能是字符串或数组
if (dbProduct.images) {
// 尝试从数据库的image_urls字段获取图片JSON字符串或对象
if (dbProduct.image_urls) {
let imagesArray: any[] = []
if (typeof dbProduct.images === 'string') {
if (typeof dbProduct.image_urls === 'string') {
try {
imagesArray = JSON.parse(dbProduct.images)
console.log('解析images字符串成功:', imagesArray)
imagesArray = JSON.parse(dbProduct.image_urls)
} catch (e) {
console.error('解析images字段失败:', e, dbProduct.images)
// 如果不是JSON尝试逗号分割
if (dbProduct.images.includes(',')) {
imagesArray = dbProduct.images.split(',').map((img: string) => img.trim())
} else if (dbProduct.images) {
imagesArray = [dbProduct.images]
console.error('解析image_urls字段失败:', e, dbProduct.image_urls)
// 尝试逗号分割
if (dbProduct.image_urls.includes(',')) {
imagesArray = dbProduct.image_urls.split(',').map((img: string) => img.trim())
}
}
} else if (Array.isArray(dbProduct.images)) {
imagesArray = dbProduct.images
} else if (Array.isArray(dbProduct.image_urls)) {
imagesArray = dbProduct.image_urls
}
if (imagesArray.length > 0) {
console.log('从数据库images字段获取图片数组:', imagesArray)
for (const img of imagesArray) {
if (typeof img === 'string' && img) {
images.push(img)
@@ -430,11 +435,18 @@ export default {
}
}
}
// 如果没有从images字段获取到图片尝试使用image字段
// 如果没有获取到相册图,但有主图,放入相册
if (dbProduct.main_image_url) {
// 如果相册里没有这张图,把它加到第一位
if (!images.includes(dbProduct.main_image_url)) {
images.unshift(dbProduct.main_image_url)
}
}
// 兼容旧字段 image
if (images.length === 0 && dbProduct.image) {
console.log('使用单张图片字段:', dbProduct.image)
images.push(dbProduct.image)
images.push(dbProduct.image)
}
// 如果仍然没有图片,使用传入的图片或默认图片
@@ -461,9 +473,33 @@ export default {
const merchantId = dbProduct.shop_id || dbProduct.merchant_id || 'merchant_001'
// 确保数值字段有效
const price = typeof dbProduct.price === 'number' ? dbProduct.price : 0
const stock = (dbProduct.stock != null && !isNaN(Number(dbProduct.stock))) ? Math.floor(Number(dbProduct.stock)) : 100
const sales = (dbProduct.sales != null && !isNaN(Number(dbProduct.sales))) ? Math.floor(Number(dbProduct.sales)) : 50
// 优先使用 price不存在则使用 base_price
let productPrice = 0
if (typeof dbProduct.price === 'number') {
productPrice = dbProduct.price
} else if (typeof dbProduct.base_price === 'number') {
productPrice = dbProduct.base_price
} else if (priceValue !== undefined) {
// 使用上面校验时获取到的 priceValue
productPrice = Number(priceValue)
}
const stock = (dbProduct.stock != null && !isNaN(Number(dbProduct.stock))) ? Math.floor(Number(dbProduct.stock)) : ((dbProduct.total_stock != null && !isNaN(Number(dbProduct.total_stock))) ? Math.floor(Number(dbProduct.total_stock)) : 100)
const sales = (dbProduct.sales != null && !isNaN(Number(dbProduct.sales))) ? Math.floor(Number(dbProduct.sales)) : ((dbProduct.sale_count != null && !isNaN(Number(dbProduct.sale_count))) ? Math.floor(Number(dbProduct.sale_count)) : 50)
// 解析 attributes
let attributes: any = {}
if (dbProduct.attributes) {
try {
if (typeof dbProduct.attributes === 'string') {
attributes = JSON.parse(dbProduct.attributes)
} else {
attributes = dbProduct.attributes
}
} catch (e) {
console.error('解析 attributes 失败', e)
}
}
this.product = {
id: dbProduct.id || productId,
@@ -472,20 +508,20 @@ export default {
name: dbProduct.name || '商品名称',
description: dbProduct.description || '这是一个高品质的商品,具有优秀的性能和优美的外观设计。采用环保材料,经过严格质检,保证用户的使用体验。',
images: images,
price: price,
original_price: (dbProduct.original_price != null && !isNaN(Number(dbProduct.original_price))) ? Number(dbProduct.original_price) : null,
price: productPrice,
original_price: (dbProduct.original_price != null && !isNaN(Number(dbProduct.original_price))) ? Number(dbProduct.original_price) : ((dbProduct.market_price != null && !isNaN(Number(dbProduct.market_price))) ? Number(dbProduct.market_price) : null),
stock: stock,
sales: sales,
status: 1,
created_at: dbProduct.created_at || '2024-01-01',
// 药品相关字段
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: attributes.specification || dbProduct.specification || null,
usage: attributes.usage || dbProduct.usage || null,
side_effects: attributes.side_effects || dbProduct.side_effects || null,
precautions: attributes.precautions || dbProduct.precautions || null,
expiry_date: attributes.expiry_date || dbProduct.expiry_date || null,
storage_conditions: attributes.storage_conditions || dbProduct.storage_conditions || null,
approval_number: attributes.approval_number || dbProduct.approval_number || null,
tags: dbProduct.tags ? (typeof dbProduct.tags === 'string' ? JSON.parse(dbProduct.tags) : dbProduct.tags) : []
} as ProductType
console.log('页面 product 对象已更新:', this.product)
@@ -535,37 +571,109 @@ export default {
}
}
// 根据商家ID生成不同的商家信息
const merchantIndex = Math.abs(this.product.merchant_id.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % 5
const shopNames = ['优质好店', '品牌直营店', '官方旗舰店', '专卖店', '精品小店']
const shopDescriptions = [
'专注品质生活',
'品牌官方直营,正品保障',
'厂家直销,价格优惠',
'专注本领域十年老店',
'用心服务每一位顾客'
]
const contactNames = ['店主小王', '店长小李', '经理小张', '客服小赵', '老板小钱']
this.merchant = {
id: this.product.merchant_id,
user_id: 'user_' + (merchantIndex + 1).toString().padStart(3, '0'),
shop_name: shopNames[merchantIndex],
shop_logo: '/static/shop-logo.png',
shop_banner: '/static/shop-banner.png',
shop_description: shopDescriptions[merchantIndex],
contact_name: contactNames[merchantIndex],
contact_phone: '138' + (10000000 + merchantIndex * 1111111).toString().substring(0, 8),
shop_status: 1,
rating: 4.5 + (merchantIndex * 0.1),
total_sales: 10000 + merchantIndex * 5000,
created_at: '2023-06-01'
// 尝试加载真实商户信息
let realMerchantLoaded = false
// 只有当 ID 是 UUID 格式(包含-)或者是真实数据时才尝试查询
if (this.product.merchant_id && (this.product.merchant_id.includes('-') || !this.product.merchant_id.startsWith('merchant_'))) {
console.log('尝试加载商户信息:', this.product.merchant_id)
try {
const shop = await supabaseService.getShopByMerchantId(this.product.merchant_id)
if (shop) {
console.log('加载到商户信息:', shop.shop_name)
// 确保字段存在,避免 undefined 导致构造失败
this.merchant = {
id: shop.id || '',
user_id: shop.merchant_id || '',
shop_name: shop.shop_name || '未命名店铺',
shop_logo: shop.shop_logo || '/static/default-shop.png',
shop_banner: shop.shop_banner || '/static/default-banner.png',
shop_description: shop.description || '',
contact_name: shop.contact_name || '店主',
contact_phone: shop.contact_phone || '',
shop_status: 1,
// 优先使用 avg_rating没有则使用默认值
rating: shop.rating_avg !== undefined && shop.rating_avg !== null ? shop.rating_avg : 4.8,
// 使用 order_count 或 product_count 作为销量/活跃度指标,如果没有则默认 0
total_sales: shop.total_sales !== undefined ? shop.total_sales : (shop.order_count !== undefined ? shop.order_count : 0),
created_at: shop.created_at || new Date().toISOString()
} as MerchantType
realMerchantLoaded = true
}
} catch (e) {
console.error('加载商户信息失败', e)
}
}
if (!realMerchantLoaded) {
// 根据商家ID生成不同的商家信息
const merchantIndex = Math.abs(this.product.merchant_id.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)) % 5
const shopNames = ['优质好店', '品牌直营店', '官方旗舰店', '专卖店', '精品小店']
const shopDescriptions = [
'专注品质生活',
'品牌官方直营,正品保障',
'厂家直销,价格优惠',
'专注本领域十年老店',
'用心服务每一位顾客'
]
const contactNames = ['店主小王', '店长小李', '经理小张', '客服小赵', '老板小钱']
this.merchant = {
id: this.product.merchant_id,
user_id: 'user_' + (merchantIndex + 1).toString().padStart(3, '0'),
shop_name: shopNames[merchantIndex],
shop_logo: '/static/shop-logo.png',
shop_banner: '/static/shop-banner.png',
shop_description: shopDescriptions[merchantIndex],
contact_name: contactNames[merchantIndex],
contact_phone: '138' + (10000000 + merchantIndex * 1111111).toString().substring(0, 8),
shop_status: 1,
rating: 4.5 + (merchantIndex * 0.1),
total_sales: 10000 + merchantIndex * 5000,
created_at: '2023-06-01'
}
}
this.loadProductSkus(productId)
},
loadProductSkus(productId: string) {
async loadProductSkus(productId: string) {
// 尝试从数据库加载SKU
try {
const skus = await supabaseService.getProductSkus(productId)
if (skus.length > 0) {
console.log('加载到商品SKU:', skus.length)
this.productSkus = skus.map((sku): ProductSkuType => {
let specs: UTSJSONObject = {}
if (sku.specifications) {
try {
if (typeof sku.specifications === 'string') {
specs = JSON.parse(sku.specifications) as UTSJSONObject
} else {
// 假设已经是对象
specs = sku.specifications as unknown as UTSJSONObject
}
} catch(e) {
console.error('解析SKU规格失败', e)
}
}
return {
id: sku.id,
product_id: sku.product_id,
sku_code: sku.sku_code,
specifications: specs,
price: sku.price,
stock: sku.stock !== undefined ? sku.stock : 0,
image_url: sku.image_url || '',
status: sku.status !== undefined ? sku.status : 1
} as ProductSkuType
})
return
}
} catch (e) {
console.error('Fetch SKUs error', e)
}
// 模拟加载商品SKU数据
const basePrice = this.product.price
@@ -620,7 +728,7 @@ export default {
return sku.sku_code
},
addToCart() {
async addToCart() {
if (!this.selectedSkuId) {
uni.showToast({
title: '请选择规格',
@@ -629,50 +737,42 @@ export default {
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'
// 显示加载中
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'
})
} else {
console.error('添加购物车返回失败')
uni.showToast({
title: '添加失败,请登录重试',
icon: 'none'
})
}
} catch (e) {
uni.hideLoading()
console.error('添加购物车异常', e)
uni.showToast({
title: '添加异常',
icon: 'none'
})
}
},
buyNow() {
@@ -787,6 +887,14 @@ export default {
})
},
goToShop() {
if (this.merchant.user_id) {
uni.navigateTo({
url: `/pages/mall/consumer/shop-detail?merchantId=${this.merchant.user_id}`
})
}
},
goToCart() {
uni.switchTab({
url: '/pages/mall/consumer/cart'

View File

@@ -44,6 +44,7 @@
<script setup lang="uts">
import { ref, onMounted } from 'vue'
import { MerchantType, ProductType } from '@/types/mall-types.uts'
import { supabaseService } from '@/utils/supabaseService.uts'
const merchant = ref<MerchantType>({
id: '',
@@ -74,73 +75,81 @@ onMounted(() => {
}
})
const loadShopData = (id: string) => {
// 模拟加载店铺数据
merchant.value = {
id: id,
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'
const loadShopData = async (id: string) => {
const shop = await supabaseService.getShopByMerchantId(id)
if (shop) {
merchant.value = {
id: shop.id,
user_id: shop.merchant_id, // 映射关系
shop_name: shop.shop_name,
shop_logo: shop.shop_logo || '/static/default-shop.png',
shop_banner: shop.shop_banner || '/static/default-banner.png',
shop_description: shop.description || '',
contact_name: shop.contact_name || '',
contact_phone: shop.contact_phone || '',
shop_status: 1, // 默认正常
rating: shop.rating_avg || 5.0,
total_sales: shop.total_sales || 0,
created_at: shop.created_at || ''
}
}
}
const loadShopProducts = (id: string) => {
// 模拟加载店铺商品列表
products.value = [
{
id: 'prod_001',
merchant_id: id,
category_id: 'cat_001',
name: '精选好物商品 A',
description: '商品描述 A',
images: ['/static/product1.jpg'],
price: 199.99,
original_price: 299.99,
stock: 100,
sales: 1256,
status: 1,
created_at: '2024-01-15'
},
{
id: 'prod_002',
merchant_id: id,
category_id: 'cat_001',
name: '精选好物商品 B',
description: '商品描述 B',
images: ['/static/product2.jpg'],
price: 299.00,
original_price: 399.00,
stock: 50,
sales: 856,
status: 1,
created_at: '2024-01-16'
},
{
id: 'prod_003',
merchant_id: id,
category_id: 'cat_002',
name: '精选好物商品 C',
description: '商品描述 C',
images: ['/static/product3.jpg'],
price: 99.00,
original_price: 129.00,
stock: 200,
sales: 3256,
status: 1,
created_at: '2024-01-17'
}
]
const loadShopProducts = async (id: string) => {
const res = await supabaseService.getProductsByMerchantId(id)
if (res.data.length > 0) {
products.value = res.data.map((item): ProductType => {
// 解析图片数组
let images: string[] = []
if (item.image_urls) {
try {
const rawUrl = item.image_urls
if (Array.isArray(rawUrl)) {
// 已经是数组
images = rawUrl as string[]
} else if (typeof rawUrl === 'string') {
if (rawUrl.startsWith('[')) {
images = JSON.parse(rawUrl) as string[]
} else {
// 单个图片路径字符串
images = [rawUrl]
}
}
} catch(e) {
console.error('解析图片数组失败:', e)
// 降级处理:尝试直接作为单个图片
if (typeof item.image_urls === 'string') {
images = [item.image_urls!]
}
}
}
if (images.length === 0 && item.image) {
images.push(item.image!)
}
if (images.length === 0 && item.main_image_url) {
images.push(item.main_image_url!)
}
return {
id: item.id,
merchant_id: item.merchant_id,
category_id: item.category_id,
name: item.name,
description: item.description || '',
images: images,
price: item.price,
original_price: item.original_price || item.price,
stock: item.stock || 0,
sales: item.sales || 0,
status: 1,
created_at: item.created_at || ''
}
})
}
}
const toggleFollow = () => {
// TODO: Implement actual follow logic with Supabase
isFollowed.value = !isFollowed.value
uni.showToast({
title: isFollowed.value ? '关注成功' : '已取消关注',
@@ -148,47 +157,24 @@ const toggleFollow = () => {
})
}
const addToCart = (product: ProductType) => {
// 获取现有购物车数据
const cartData = uni.getStorageSync('cart')
let cartItems: any[] = []
const addToCart = async (product: ProductType) => {
uni.showLoading({ title: '添加中...' })
if (cartData) {
try {
cartItems = JSON.parse(cartData as string) as any[]
} catch (e) {
console.error('解析购物车数据失败', e)
}
}
const success = await supabaseService.addToCart(product.id, 1)
// 检查商品是否已存在
const existingItem = cartItems.find((item: any) => item.productId === product.id)
uni.hideLoading()
if (existingItem) {
existingItem.quantity++
if (success) {
uni.showToast({
title: '已添加到购物车',
icon: 'success'
})
} else {
// 添加新商品
cartItems.push({
id: product.id, // 简单使用产品ID作为购物车ID实际可能有规格
productId: product.id,
shopId: merchant.value.id,
shopName: merchant.value.shop_name,
name: product.name,
price: product.price,
image: product.images[0],
spec: '默认规格',
quantity: 1,
selected: true
uni.showToast({
title: '添加失败,请重试',
icon: 'none'
})
}
// 保存回存储
uni.setStorageSync('cart', JSON.stringify(cartItems))
uni.showToast({
title: '已添加到购物车',
icon: 'success'
})
}
const goToProduct = (id: string) => {