From d57592ca7d17ce3d209458375b3de10683099cca Mon Sep 17 00:00:00 2001
From: cyh666666 <2398882793@qq.com>
Date: Fri, 30 Jan 2026 17:29:02 +0800
Subject: [PATCH] =?UTF-8?q?=E7=BB=A7=E7=BB=AD=E5=AE=8C=E5=96=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pages/mall/consumer/address-edit.uvue | 199 +-
pages/mall/consumer/address-list copy.uvue | 219 +++
pages/mall/consumer/address-list.uvue | 87 +-
pages/mall/consumer/cart copy.uvue | 225 ++-
pages/mall/consumer/cart.uvue | 254 ++-
pages/mall/consumer/checkout copy 2.uvue | 1733 +++++++++++++++++
pages/mall/consumer/checkout.uvue | 491 ++++-
.../mall/consumer/product-detail copy 2.uvue | 1392 +++++++++++++
pages/mall/consumer/product-detail copy.uvue | 1163 +++++++++++
...roduct-detail copy完成商品详情数据获取.uvue} | 223 ++-
...oduct-detail copy完成图片数量数据获取.uvue | 1359 +++++++++++++
pages/mall/consumer/product-detail.uvue | 452 ++++-
types/mall-types.uts | 9 +
utils/supabaseService.uts | 604 +++++-
14 files changed, 8003 insertions(+), 407 deletions(-)
create mode 100644 pages/mall/consumer/address-list copy.uvue
create mode 100644 pages/mall/consumer/checkout copy 2.uvue
create mode 100644 pages/mall/consumer/product-detail copy 2.uvue
create mode 100644 pages/mall/consumer/product-detail copy.uvue
rename pages/mall/consumer/{product-detail - 副本.uvue => product-detail copy完成商品详情数据获取.uvue} (72%)
create mode 100644 pages/mall/consumer/product-detail copy完成图片数量数据获取.uvue
diff --git a/pages/mall/consumer/address-edit.uvue b/pages/mall/consumer/address-edit.uvue
index 18ee45d0..ce30fb61 100644
--- a/pages/mall/consumer/address-edit.uvue
+++ b/pages/mall/consumer/address-edit.uvue
@@ -53,6 +53,7 @@
+
+
diff --git a/pages/mall/consumer/address-list.uvue b/pages/mall/consumer/address-list.uvue
index 1b6aeefa..9938dd2a 100644
--- a/pages/mall/consumer/address-list.uvue
+++ b/pages/mall/consumer/address-list.uvue
@@ -36,6 +36,7 @@
+
+
diff --git a/pages/mall/consumer/checkout.uvue b/pages/mall/consumer/checkout.uvue
index 3c744516..d96e6e2a 100644
--- a/pages/mall/consumer/checkout.uvue
+++ b/pages/mall/consumer/checkout.uvue
@@ -120,8 +120,42 @@
+
+
+ 🔒
+ 您尚未登录,点击登录以同步服务器地址
+ ›
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+ 本地地址(未同步)
-
-
+
+
+
+
+
+
+
+ 规格
+ {{ product.specification }}
+
+
+ 功能主治
+ {{ product.usage }}
+
+
+ 副作用
+ {{ product.side_effects }}
+
+
+ 注意事项
+ {{ product.precautions }}
+
+
+ 有效期
+ {{ product.expiry_date }}
+
+
+ 储存条件
+ {{ product.storage_conditions }}
+
+
+ 批准文号
+ {{ product.approval_number }}
+
+
+ 标签
+ {{ product.tags.join(', ') }}
+
+
+
+
@@ -148,7 +218,8 @@ export default {
selectedSkuId: '',
selectedSpec: '',
quantity: 1,
- isFavorite: false
+ isFavorite: false,
+ showParams: false
}
},
onLoad(options: any) {
@@ -241,42 +312,186 @@ export default {
async loadProductDetail(productId: string, options: any = {}) {
// 尝试从数据库加载
- let dbProduct = null
+ let dbProductRaw = null
try {
console.log('正在尝试从数据库加载商品详情:', productId)
- dbProduct = await supabaseService.getProductById(productId)
- console.log('数据库返回的商品详情:', dbProduct)
+ dbProductRaw = await supabaseService.getProductById(productId)
+ console.log('数据库返回的商品详情 (原始数据):', dbProductRaw)
+
+ // 调试:打印数据库返回的所有字段
+ if (dbProductRaw) {
+ console.log('数据库返回字段详情:')
+ if (Array.isArray(dbProductRaw)) {
+ console.log('返回数据是数组,长度:', dbProductRaw.length)
+ if (dbProductRaw.length > 0) {
+ const firstItem = dbProductRaw[0]
+ console.log('数组第一个元素:', firstItem)
+ for (const key in firstItem) {
+ console.log(` ${key}:`, firstItem[key], typeof firstItem[key])
+ }
+ }
+ } else {
+ console.log('返回数据是对象')
+ for (const key in dbProductRaw) {
+ console.log(` ${key}:`, dbProductRaw[key], typeof dbProductRaw[key])
+ }
+ }
+ }
} catch (e) {
console.error('Failed to load product from DB', e)
}
+ // 处理数据库返回数据:可能是数组或对象
+ let dbProduct = null
+ if (dbProductRaw) {
+ if (Array.isArray(dbProductRaw)) {
+ if (dbProductRaw.length > 0) {
+ dbProduct = dbProductRaw[0] // 取数组第一个元素
+ } else {
+ console.warn('数据库返回空数组')
+ }
+ } else {
+ dbProduct = dbProductRaw // 已经是对象
+ }
+ }
+
if (dbProduct) {
console.log('使用数据库数据渲染页面')
- // 使用数据库数据
- const images = [] as Array
- if (dbProduct.image) images.push(dbProduct.image)
- else if (options.image) images.push(decodeURIComponent(options.image as string))
- else images.push('/static/product1.jpg')
- // 补充模拟图片
- images.push('/static/product2.jpg')
- images.push('/static/product3.jpg')
+ // 调试:打印dbProduct的详细结构和类型
+ console.log('dbProduct类型:', typeof dbProduct)
+ console.log('dbProduct原型:', Object.getPrototypeOf(dbProduct))
+ console.log('dbProduct的键:')
+ for (let key in dbProduct) {
+ console.log(' ', key, ':', dbProduct[key], '类型:', typeof dbProduct[key])
+ }
+
+ // 验证必要字段,如果关键字段缺失则使用模拟数据
+ // 注意:数据库返回的字段可能与本地ProductType不完全匹配
+ console.log('验证必要字段,dbProduct:', dbProduct)
+
+ // 尝试多种方式访问属性
+ 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)
+
+ const hasId = idValue !== undefined && idValue !== null
+ const hasName = nameValue !== undefined && nameValue !== null
+ const hasPrice = priceValue !== undefined && priceValue !== null
+
+ const hasRequiredFields = dbProduct && hasId && hasName && hasPrice
+ console.log('字段检查 - id:', idValue, 'hasId:', hasId, 'name:', nameValue, 'hasName:', hasName, 'price:', priceValue, 'hasPrice:', hasPrice)
+ console.log('hasRequiredFields:', hasRequiredFields)
+
+ if (!hasRequiredFields) {
+ console.warn('数据库返回数据缺少必要字段,使用模拟数据')
+ // 继续执行,会进入下面的else分支
+ dbProduct = null
+ } else {
+ // 更新dbProduct的字段为实际值,确保后续使用正确的属性访问
+ if (dbProduct.id === undefined && idValue !== undefined) dbProduct.id = idValue
+ if (dbProduct.name === undefined && nameValue !== undefined) dbProduct.name = nameValue
+ if (dbProduct.price === undefined && priceValue !== undefined) dbProduct.price = priceValue
+ // 使用数据库数据 - 处理字段映射
+ // 数据库Product接口和本地ProductType接口字段可能不同
+ const images = [] as Array
+
+ // 处理图片字段:优先使用images字段,其次使用image字段
+ console.log('处理数据库图片字段:')
+ console.log('dbProduct.images:', dbProduct.images, '类型:', typeof dbProduct.images)
+ console.log('dbProduct.image:', dbProduct.image, '类型:', typeof dbProduct.image)
+
+ // 尝试从数据库的images字段获取图片(可能是字符串或数组)
+ if (dbProduct.images) {
+ let imagesArray: any[] = []
+ if (typeof dbProduct.images === 'string') {
+ try {
+ imagesArray = JSON.parse(dbProduct.images)
+ console.log('解析images字符串成功:', imagesArray)
+ } 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]
+ }
+ }
+ } else if (Array.isArray(dbProduct.images)) {
+ imagesArray = dbProduct.images
+ }
+
+ if (imagesArray.length > 0) {
+ console.log('从数据库images字段获取图片数组:', imagesArray)
+ for (const img of imagesArray) {
+ if (typeof img === 'string' && img) {
+ images.push(img)
+ }
+ }
+ }
+ }
+
+ // 如果没有从images字段获取到图片,尝试使用image字段
+ if (images.length === 0 && dbProduct.image) {
+ console.log('使用单张图片字段:', dbProduct.image)
+ images.push(dbProduct.image)
+ }
+
+ // 如果仍然没有图片,使用传入的图片或默认图片
+ if (images.length === 0) {
+ if (options.image) {
+ images.push(decodeURIComponent(options.image as string))
+ } else {
+ images.push('/static/product1.jpg')
+ }
+ }
+
+ // 补充模拟图片(如果图片数量不足3张)
+ const needSupplementCount = 3 - images.length
+ if (needSupplementCount > 0) {
+ const supplementalImages = ['/static/product2.jpg', '/static/product3.jpg']
+ for (let i = 0; i < needSupplementCount && i < supplementalImages.length; i++) {
+ images.push(supplementalImages[i])
+ }
+ }
+
+ console.log('最终图片数组:', images)
+
+ // 映射字段:数据库shop_id对应本地merchant_id
+ 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
- this.product = {
- id: dbProduct.id,
- merchant_id: dbProduct.shop_id || 'merchant_001',
- category_id: dbProduct.category_id,
- name: dbProduct.name,
- description: dbProduct.description || '这是一个高品质的商品,具有优秀的性能和优美的外观设计。采用环保材料,经过严格质检,保证用户的使用体验。',
- images: images,
- price: dbProduct.price,
- original_price: dbProduct.original_price || null,
- stock: dbProduct.stock !== undefined ? dbProduct.stock : 0, // 确保使用数据库中的库存
- sales: dbProduct.sales !== undefined ? dbProduct.sales : 0, // 确保使用数据库中的销量
- status: 1,
- created_at: dbProduct.created_at || '2024-01-01'
- } as ProductType
- console.log('页面 product 对象已更新:', this.product)
+ this.product = {
+ id: dbProduct.id || productId,
+ merchant_id: merchantId,
+ category_id: dbProduct.category_id || 'cat_001',
+ 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,
+ 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,
+ 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)
+ console.log('商品价格:', this.product.price, '库存:', this.product.stock, '销量:', this.product.sales)
+ }
} else {
console.log('数据库无数据或加载失败,使用模拟数据')
// 数据库无数据时,使用原有模拟逻辑
@@ -378,6 +593,10 @@ export default {
}
]
},
+
+ onSwiperChange(e: any) {
+ this.currentImageIndex = e.detail.current
+ },
showSpecModal() {
this.showSpec = true
@@ -626,6 +845,21 @@ export default {
getAvailableStock() {
return this.getMaxQuantity()
+ },
+
+ previewImage(index: number) {
+ uni.previewImage({
+ current: index,
+ urls: this.product.images
+ })
+ },
+
+ showParamsModal() {
+ this.showParams = true
+ },
+
+ hideParamsModal() {
+ this.showParams = false
}
}
}
@@ -993,4 +1227,166 @@ export default {
width: 100rpx;
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;
+}
+
+.detail-image {
+ width: 100%;
+ margin-bottom: 20rpx;
+ 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;
+ }
+}
diff --git a/types/mall-types.uts b/types/mall-types.uts
index 6a54f7ee..de9460f9 100644
--- a/types/mall-types.uts
+++ b/types/mall-types.uts
@@ -85,6 +85,15 @@ export type ProductType = {
sales: number
status: number
created_at: string
+ // 药品相关字段
+ specification?: string | null // 规格说明
+ usage?: string | null // 用法用量
+ side_effects?: string | null // 副作用
+ precautions?: string | null // 注意事项
+ expiry_date?: string | null // 有效期
+ storage_conditions?: string | null // 储存条件
+ approval_number?: string | null // 批准文号
+ tags?: Array | null // 商品标签
}
// 商品SKU类型
diff --git a/utils/supabaseService.uts b/utils/supabaseService.uts
index bac53939..3ee23527 100644
--- a/utils/supabaseService.uts
+++ b/utils/supabaseService.uts
@@ -25,14 +25,46 @@ export interface Product {
original_price?: number
image?: string
manufacturer: string
- sales: number
- stock: number
+ sales?: number
+ stock?: number
badge?: string
shop_id?: string
shop_name?: string
created_at?: string
}
+export interface CartItem {
+ id: string
+ user_id: string
+ product_id: string
+ sku_id?: string
+ quantity: number
+ selected: boolean
+ product_name?: string
+ product_image?: string
+ product_price?: number
+ product_specification?: string
+ shop_id?: string
+ shop_name?: string
+ created_at?: string
+ updated_at?: string
+}
+
+export interface UserAddress {
+ id: string
+ user_id: string
+ recipient_name: string
+ phone: string
+ province: string
+ city: string
+ district: string
+ detail_address: string
+ postal_code?: string
+ is_default: boolean
+ created_at?: string
+ updated_at?: string
+}
+
export interface PaginatedResponse {
data: T[]
total: number
@@ -42,6 +74,17 @@ export interface PaginatedResponse {
}
class SupabaseService {
+ // 获取当前用户ID
+ private getCurrentUserId(): string | null {
+ try {
+ const userId = uni.getStorageSync('user_id')
+ return userId ? userId as string : null
+ } catch (e) {
+ console.error('获取用户ID失败:', e)
+ return null
+ }
+ }
+
// 获取所有分类
async getCategories(): Promise {
try {
@@ -176,7 +219,7 @@ class SupabaseService {
.select('*')
.eq('id', productId)
.single()
- .execute()
+ .executeAs()
if (response.error) {
console.error('获取商品详情失败:', response.error)
@@ -304,6 +347,561 @@ class SupabaseService {
return []
}
}
+
+ // 获取当前用户的购物车商品(关联商品和店铺信息)
+ async getCartItems(): Promise {
+ try {
+ const userId = this.getCurrentUserId()
+ if (!userId) {
+ console.warn('用户未登录,无法获取购物车')
+ return []
+ }
+
+ // 查询购物车表,并关联商品表(使用内联关联)
+ const response = await supa
+ .from('ml_shopping_cart')
+ .select(`
+ id,
+ user_id,
+ product_id,
+ sku_id,
+ quantity,
+ selected,
+ created_at,
+ updated_at,
+ products!inner (
+ id,
+ name,
+ image,
+ price,
+ specification,
+ shop_id,
+ shop_name
+ )
+ `)
+ .eq('user_id', userId)
+ .order('created_at', { ascending: false })
+ .execute()
+
+ if (response.error) {
+ console.error('获取购物车失败:', response.error)
+ return []
+ }
+
+ // 处理返回数据,构建CartItem数组
+ const cartItems: CartItem[] = []
+ if (response.data && Array.isArray(response.data)) {
+ for (const item of response.data) {
+ const product = item.products as any
+
+ cartItems.push({
+ id: item.id as string,
+ user_id: item.user_id as string,
+ product_id: item.product_id as string,
+ sku_id: item.sku_id as string,
+ quantity: item.quantity as number,
+ selected: item.selected as boolean,
+ product_name: product?.name as string,
+ product_image: product?.image as string,
+ product_price: product?.price as number,
+ product_specification: product?.specification as string,
+ shop_id: product?.shop_id as string,
+ shop_name: product?.shop_name as string,
+ created_at: item.created_at as string,
+ updated_at: item.updated_at as string
+ })
+ }
+ }
+
+ return cartItems
+ } catch (error) {
+ console.error('获取购物车异常:', error)
+ return []
+ }
+ }
+
+ // 添加商品到购物车
+ async addToCart(productId: string, quantity: number = 1, skuId?: string): Promise {
+ try {
+ const userId = this.getCurrentUserId()
+ if (!userId) {
+ console.error('用户未登录,无法添加商品到购物车')
+ return false
+ }
+
+ // 检查商品是否已在购物车中
+ const existingResponse = await supa
+ .from('ml_shopping_cart')
+ .select('*')
+ .eq('user_id', userId)
+ .eq('product_id', productId)
+ .eq('sku_id', skuId || '')
+ .single()
+ .execute()
+
+ let response
+ if (existingResponse.data) {
+ // 商品已存在,更新数量
+ const existingItem = existingResponse.data as any
+ response = await supa
+ .from('ml_shopping_cart')
+ .update({
+ quantity: (existingItem.quantity || 0) + quantity,
+ updated_at: new Date().toISOString()
+ })
+ .eq('id', existingItem.id)
+ .execute()
+ } else {
+ // 商品不存在,添加新记录
+ response = await supa
+ .from('ml_shopping_cart')
+ .insert({
+ user_id: userId,
+ product_id: productId,
+ sku_id: skuId || null,
+ quantity: quantity,
+ selected: true,
+ created_at: new Date().toISOString(),
+ updated_at: new Date().toISOString()
+ })
+ .execute()
+ }
+
+ if (response.error) {
+ console.error('添加商品到购物车失败:', response.error)
+ return false
+ }
+
+ return true
+ } catch (error) {
+ console.error('添加商品到购物车异常:', error)
+ return false
+ }
+ }
+
+ // 更新购物车商品数量
+ async updateCartItemQuantity(cartItemId: string, quantity: number): Promise {
+ try {
+ const userId = this.getCurrentUserId()
+ if (!userId) {
+ console.error('用户未登录,无法更新购物车')
+ return false
+ }
+
+ if (quantity < 1) {
+ // 数量小于1时删除商品
+ return await this.deleteCartItem(cartItemId)
+ }
+
+ const response = await supa
+ .from('ml_shopping_cart')
+ .update({
+ quantity: quantity,
+ updated_at: new Date().toISOString()
+ })
+ .eq('id', cartItemId)
+ .eq('user_id', userId)
+ .execute()
+
+ if (response.error) {
+ console.error('更新购物车商品数量失败:', response.error)
+ return false
+ }
+
+ return true
+ } catch (error) {
+ console.error('更新购物车商品数量异常:', error)
+ return false
+ }
+ }
+
+ // 更新购物车商品选中状态
+ async updateCartItemSelection(cartItemId: string, selected: boolean): Promise {
+ try {
+ const userId = this.getCurrentUserId()
+ if (!userId) {
+ console.error('用户未登录,无法更新购物车')
+ return false
+ }
+
+ const response = await supa
+ .from('ml_shopping_cart')
+ .update({
+ selected: selected,
+ updated_at: new Date().toISOString()
+ })
+ .eq('id', cartItemId)
+ .eq('user_id', userId)
+ .execute()
+
+ if (response.error) {
+ console.error('更新购物车商品选中状态失败:', response.error)
+ return false
+ }
+
+ return true
+ } catch (error) {
+ console.error('更新购物车商品选中状态异常:', error)
+ return false
+ }
+ }
+
+ // 批量更新购物车商品选中状态
+ async batchUpdateCartItemSelection(cartItemIds: string[], selected: boolean): Promise {
+ try {
+ const userId = this.getCurrentUserId()
+ if (!userId) {
+ console.error('用户未登录,无法更新购物车')
+ return false
+ }
+
+ const response = await supa
+ .from('ml_shopping_cart')
+ .update({
+ selected: selected,
+ updated_at: new Date().toISOString()
+ })
+ .eq('user_id', userId)
+ .in('id', cartItemIds)
+ .execute()
+
+ if (response.error) {
+ console.error('批量更新购物车商品选中状态失败:', response.error)
+ return false
+ }
+
+ return true
+ } catch (error) {
+ console.error('批量更新购物车商品选中状态异常:', error)
+ return false
+ }
+ }
+
+ // 删除购物车商品
+ async deleteCartItem(cartItemId: string): Promise {
+ try {
+ const userId = this.getCurrentUserId()
+ if (!userId) {
+ console.error('用户未登录,无法删除购物车商品')
+ return false
+ }
+
+ const response = await supa
+ .from('ml_shopping_cart')
+ .delete()
+ .eq('id', cartItemId)
+ .eq('user_id', userId)
+ .execute()
+
+ if (response.error) {
+ console.error('删除购物车商品失败:', response.error)
+ return false
+ }
+
+ return true
+ } catch (error) {
+ console.error('删除购物车商品异常:', error)
+ return false
+ }
+ }
+
+ // 批量删除购物车商品
+ async batchDeleteCartItems(cartItemIds: string[]): Promise {
+ try {
+ const userId = this.getCurrentUserId()
+ if (!userId) {
+ console.error('用户未登录,无法删除购物车商品')
+ return false
+ }
+
+ const response = await supa
+ .from('ml_shopping_cart')
+ .delete()
+ .eq('user_id', userId)
+ .in('id', cartItemIds)
+ .execute()
+
+ if (response.error) {
+ console.error('批量删除购物车商品失败:', response.error)
+ return false
+ }
+
+ return true
+ } catch (error) {
+ console.error('批量删除购物车商品异常:', error)
+ return false
+ }
+ }
+
+ // 清空购物车
+ async clearCart(): Promise {
+ try {
+ const userId = this.getCurrentUserId()
+ if (!userId) {
+ console.error('用户未登录,无法清空购物车')
+ return false
+ }
+
+ const response = await supa
+ .from('ml_shopping_cart')
+ .delete()
+ .eq('user_id', userId)
+ .execute()
+
+ if (response.error) {
+ console.error('清空购物车失败:', response.error)
+ return false
+ }
+
+ return true
+ } catch (error) {
+ console.error('清空购物车异常:', error)
+ return false
+ }
+ }
+
+ // 获取当前用户的所有地址
+ async getAddresses(): Promise {
+ try {
+ const userId = this.getCurrentUserId()
+ if (!userId) {
+ console.warn('用户未登录,无法获取地址')
+ return []
+ }
+
+ const response = await supa
+ .from('ml_user_addresses')
+ .select('*')
+ .eq('user_id', userId)
+ .order('is_default', { ascending: false })
+ .order('created_at', { ascending: false })
+ .execute()
+
+ if (response.error) {
+ console.error('获取地址失败:', response.error)
+ return []
+ }
+
+ return response.data as UserAddress[]
+ } catch (error) {
+ console.error('获取地址异常:', error)
+ return []
+ }
+ }
+
+ // 根据ID获取地址详情
+ async getAddressById(addressId: string): Promise {
+ try {
+ const userId = this.getCurrentUserId()
+ if (!userId) {
+ console.warn('用户未登录,无法获取地址')
+ return null
+ }
+
+ const response = await supa
+ .from('ml_user_addresses')
+ .select('*')
+ .eq('id', addressId)
+ .eq('user_id', userId)
+ .single()
+ .execute()
+
+ if (response.error) {
+ console.error('获取地址详情失败:', response.error)
+ return null
+ }
+
+ return response.data as UserAddress
+ } catch (error) {
+ console.error('获取地址详情异常:', error)
+ return null
+ }
+ }
+
+ // 添加新地址
+ async addAddress(address: {
+ recipient_name: string
+ phone: string
+ province: string
+ city: string
+ district: string
+ detail_address: string
+ postal_code?: string
+ is_default?: boolean
+ }): Promise {
+ try {
+ const userId = this.getCurrentUserId()
+ if (!userId) {
+ console.error('用户未登录,无法添加地址')
+ return false
+ }
+
+ // 如果设置为默认地址,需要先取消其他默认地址
+ if (address.is_default) {
+ await this.clearDefaultAddress(userId)
+ }
+
+ const response = await supa
+ .from('ml_user_addresses')
+ .insert({
+ user_id: userId,
+ recipient_name: address.recipient_name,
+ phone: address.phone,
+ province: address.province,
+ city: address.city,
+ district: address.district,
+ detail_address: address.detail_address,
+ postal_code: address.postal_code || null,
+ is_default: address.is_default || false,
+ created_at: new Date().toISOString(),
+ updated_at: new Date().toISOString()
+ })
+ .execute()
+
+ if (response.error) {
+ console.error('添加地址失败:', response.error)
+ return false
+ }
+
+ return true
+ } catch (error) {
+ console.error('添加地址异常:', error)
+ return false
+ }
+ }
+
+ // 更新地址
+ async updateAddress(addressId: string, address: {
+ recipient_name?: string
+ phone?: string
+ province?: string
+ city?: string
+ district?: string
+ detail_address?: string
+ postal_code?: string
+ is_default?: boolean
+ }): Promise {
+ try {
+ const userId = this.getCurrentUserId()
+ if (!userId) {
+ console.error('用户未登录,无法更新地址')
+ return false
+ }
+
+ // 如果设置为默认地址,需要先取消其他默认地址
+ if (address.is_default) {
+ await this.clearDefaultAddress(userId)
+ }
+
+ const response = await supa
+ .from('ml_user_addresses')
+ .update({
+ ...address,
+ updated_at: new Date().toISOString()
+ })
+ .eq('id', addressId)
+ .eq('user_id', userId)
+ .execute()
+
+ if (response.error) {
+ console.error('更新地址失败:', response.error)
+ return false
+ }
+
+ return true
+ } catch (error) {
+ console.error('更新地址异常:', error)
+ return false
+ }
+ }
+
+ // 删除地址
+ async deleteAddress(addressId: string): Promise {
+ try {
+ const userId = this.getCurrentUserId()
+ if (!userId) {
+ console.error('用户未登录,无法删除地址')
+ return false
+ }
+
+ const response = await supa
+ .from('ml_user_addresses')
+ .delete()
+ .eq('id', addressId)
+ .eq('user_id', userId)
+ .execute()
+
+ if (response.error) {
+ console.error('删除地址失败:', response.error)
+ return false
+ }
+
+ return true
+ } catch (error) {
+ console.error('删除地址异常:', error)
+ return false
+ }
+ }
+
+ // 设置默认地址
+ async setDefaultAddress(addressId: string): Promise {
+ try {
+ const userId = this.getCurrentUserId()
+ if (!userId) {
+ console.error('用户未登录,无法设置默认地址')
+ return false
+ }
+
+ // 先取消所有默认地址
+ await this.clearDefaultAddress(userId)
+
+ // 设置新的默认地址
+ const response = await supa
+ .from('ml_user_addresses')
+ .update({
+ is_default: true,
+ updated_at: new Date().toISOString()
+ })
+ .eq('id', addressId)
+ .eq('user_id', userId)
+ .execute()
+
+ if (response.error) {
+ console.error('设置默认地址失败:', response.error)
+ return false
+ }
+
+ return true
+ } catch (error) {
+ console.error('设置默认地址异常:', error)
+ return false
+ }
+ }
+
+ // 清除用户的默认地址(内部方法)
+ private async clearDefaultAddress(userId: string): Promise {
+ try {
+ const response = await supa
+ .from('ml_user_addresses')
+ .update({
+ is_default: false,
+ updated_at: new Date().toISOString()
+ })
+ .eq('user_id', userId)
+ .eq('is_default', true)
+ .execute()
+
+ if (response.error) {
+ console.error('清除默认地址失败:', response.error)
+ return false
+ }
+
+ return true
+ } catch (error) {
+ console.error('清除默认地址异常:', error)
+ return false
+ }
+ }
}
// 导出单例实例