import supa from '@/components/supadb/aksupainstance.uts' import type { AkReqResponse } from '@/uni_modules/ak-req/index.uts' // 使用单例 Supabase 客户端 // const supa = createClient(SUPA_URL, SUPA_KEY) // 类型定义 export interface Category { id: string name: string icon: string description: string color: string created_at?: string } export interface Product { id: string category_id: string merchant_id: string name: string subtitle?: string description?: string base_price: number market_price?: number cost_price?: number main_image_url?: string image_urls?: string // JSON string array video_urls?: string // JSON string array sale_count?: number view_count?: number total_stock?: number available_stock?: number is_hot?: boolean is_new?: boolean is_featured?: boolean status?: number rating_avg?: number rating_count?: number tags?: string // array string in DB attributes?: string // JSON string created_at?: string updated_at?: string // View fields brand_name?: string category_name?: string shop_name?: string merchant_name?: string } export interface Shop { id: string merchant_id: string shop_name: string shop_logo?: string shop_banner?: string description?: string contact_name?: string contact_phone?: string rating_avg?: number total_sales?: number product_count?: number total_sales_count?: number created_at?: string } export interface CartItem { id: string user_id: string product_id: string sku_id?: string merchant_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 Notification { id: string user_id: string type: string title: string content: string icon_url?: string link_url?: string is_read: boolean extra_data?: string created_at?: string } export interface ChatMessage { id: string session_id?: string sender_id?: string receiver_id?: string content: string msg_type: string is_read: boolean is_from_user: boolean extra_data?: string created_at?: string } export interface PaginatedResponse { data: T[] total: number page: number limit: number hasmore: boolean } export interface ProductSku { id: string product_id: string sku_code: string specifications: string // JSON string price: number market_price?: number cost_price?: number stock?: number warning_stock?: number image_url?: string weight?: number status?: number created_at?: string } class SupabaseService { // 获取当前用户ID public getCurrentUserId(): string | null { try { // 优先从 Supabase 会话获取 const session = supa.getSession() if (session && session.user) { return session.user.getString('id') } // 后备:尝试从本地存储获取 (兼容旧逻辑) const userId = uni.getStorageSync('user_id') return userId ? userId as string : null } catch (e) { console.error('获取用户ID失败:', e) return null } } // 获取所有分类 async getCategories(): Promise { try { const response = await supa .from('ml_categories') .select('*') .order('name', { ascending: true }) .execute() if (response.error) { console.error('获取分类失败:', response.error) return [] } return response.data as Category[] } catch (error) { console.error('获取分类异常:', error) return [] } } // 获取指定分类的商品 async getProductsByCategory( categoryId: string, page: number = 1, limit: number = 20 ): Promise> { try { const response = await supa .from('ml_products_detail_view') .select('*', { count: 'exact' }) .eq('category_id', categoryId) .eq('status', 1) .order('sale_count', { ascending: false }) .page(page) .limit(limit) .execute() if (response.error) { console.error('获取商品失败:', response.error) return { data: [], total: 0, page, limit, hasmore: false } } return { data: response.data as Product[], total: response.total || 0, page, limit, hasmore: response.hasmore || false } } catch (error) { console.error('获取商品异常:', error) return { data: [], total: 0, page, limit, hasmore: false } } } // 根据商品ID获取SKU列表 async getProductSkus(productId: string): Promise { try { const response = await supa .from('ml_product_skus') .select('*') .eq('product_id', productId) .eq('status', 1) .execute() if (response.error) { console.error('获取商品SKU失败:', response.error) return [] } return response.data as ProductSku[] } catch (error) { console.error('获取商品SKU异常:', error) return [] } } // 搜索商品 async searchProducts( keyword: string, page: number = 1, limit: number = 20, sortBy: string = 'sales', ascending: boolean = false ): Promise> { try { let query = supa .from('ml_products_detail_view') .select('*', { count: 'exact' }) .eq('status', 1) .or(`name.ilike.%${keyword}%,description.ilike.%${keyword}%,subtitle.ilike.%${keyword}%,brand_name.ilike.%${keyword}%`) // 根据sortBy和ascending设置排序 if (sortBy === 'price') { query = query.order('base_price', { ascending }) } else if (sortBy === 'sales' || sortBy === 'sale_count') { query = query.order('sale_count', { ascending: false }) // 销量总是降序 } else { // 默认按销量降序 query = query.order('sale_count', { ascending: false }) } const response = await query .page(page) .limit(limit) .execute() if (response.error) { console.error('搜索商品失败:', response.error) return { data: [], total: 0, page, limit, hasmore: false } } return { data: response.data as Product[], total: response.total || 0, page, limit, hasmore: response.hasmore || false } } catch (error) { console.error('搜索商品异常:', error) return { data: [], total: 0, page, limit, hasmore: false } } } // 获取单个商品详情 async getProductById(productId: string): Promise { try { const response = await supa .from('ml_products_detail_view') .select('*') .eq('id', productId) .single() .executeAs() if (response.error) { console.error('获取商品详情失败:', response.error) return null } return response.data as Product } catch (error) { console.error('获取商品详情异常:', error) return null } } // 根据商户ID获取店铺信息 async getShopByMerchantId(merchantId: string): Promise { try { const response = await supa .from('ml_shops') .select('*') .eq('merchant_id', merchantId) .single() .executeAs() if (response.error) { console.error('获取店铺信息失败:', response.error) return null } const data = response.data if (Array.isArray(data)) { if (data.length > 0) return data[0] as Shop return null } return data as Shop } catch (error) { console.error('获取店铺信息异常:', error) return null } } // 根据商户ID获取商品列表 async getProductsByMerchantId(merchantId: string, page: number = 1, limit: number = 20): Promise> { try { const response = await supa .from('ml_products') .select('*', { count: 'exact' }) .eq('merchant_id', merchantId) .order('created_at', { ascending: false }) .page(page) .limit(limit) .execute() if (response.error) { console.error('获取商户商品失败:', response.error) return { data: [], total: 0, page, limit, hasmore: false } } return { data: response.data as Product[], total: response.total || 0, page, limit, hasmore: response.hasmore || false } } catch (error) { console.error('获取商户商品异常:', error) return { data: [], total: 0, page, limit, hasmore: false } } } // 获取热销商品(按销量排序) async getHotProducts(limit: number = 10): Promise { try { const response = await supa .from('ml_products_detail_view') .select('*') .eq('is_hot', true) .eq('status', 1) .order('sale_count', { ascending: false }) .limit(limit) .execute() if (response.error) { console.error('获取热销商品失败:', response.error) return [] } return response.data as Product[] } catch (error) { console.error('获取热销商品异常:', error) return [] } } // 获取按价格排序的商品(升序:从低到高) async getProductsByPrice(limit: number = 10, ascending: boolean = true): Promise { try { const response = await supa .from('ml_products_detail_view') .select('*') .eq('status', 1) .order('base_price', { ascending }) .limit(limit) .execute() if (response.error) { console.error('获取价格排序商品失败:', response.error) return [] } return response.data as Product[] } catch (error) { console.error('获取价格排序商品异常:', error) return [] } } // 获取新品(按创建时间排序,最新的在前) async getProductsByNewest(limit: number = 10): Promise { try { const response = await supa .from('ml_products_detail_view') .select('*') .eq('is_new', true) .eq('status', 1) .order('published_at', { ascending: false }) // Use published_at for newest .limit(limit) .execute() if (response.error) { console.error('获取新品失败:', response.error) return [] } return response.data as Product[] } catch (error) { console.error('获取新品异常:', error) return [] } } // 获取推荐商品(is_featured=true) async getRecommendedProducts(limit: number = 10): Promise { try { // 查询 is_featured = true 的商品 const response = await supa .from('ml_products_detail_view') .select('*') .eq('is_featured', true) .eq('status', 1) .order('sale_count', { ascending: false }) .limit(limit) .execute() if (response.error) { console.error('获取推荐商品失败:', response.error) return [] } console.log('推荐商品查询结果条数:', response.data?.length || 0) return response.data as Product[] || [] } catch (error) { console.error('获取推荐商品异常:', error) return [] } } // 获取特价商品(这里假设没有specific flag, just use logic or tag if exists, defaulting to hot for now or just skip) // Modify to use compatible logic if badge column doesn't exist async getDiscountProducts(limit: number = 10): Promise { return [] // 暂无特价字段 } // 获取当前用户的购物车商品(关联商品和店铺信息) async getCartItems(): Promise { try { const userId = this.getCurrentUserId() if (!userId) { console.warn('用户未登录,无法获取购物车') return [] } // 查询购物车表,并关联商品表(使用内联关联) // 注意:使用 !inner 进行内连接,或者 left join (默认) // 修改查询语法以符合 PostgREST 规范 // 尝试简化查询,先只查购物车,再查商品,避免复杂的嵌套查询导致 400 错误 const response = await supa .from('ml_shopping_cart') .select('*') .eq('user_id', userId) .order('created_at', { ascending: false }) .execute() if (response.error) { console.error('获取购物车失败:', response.error) return [] } const cartData = response.data as any[] // console.log('Raw Cart Data:', JSON.stringify(cartData)) if (!cartData || cartData.length === 0) { return [] } // 收集所有 product_id 和 sku_id const productIds: string[] = [] const skuIds: string[] = [] for (let i = 0; i < cartData.length; i++) { let item = cartData[i] let pid: string = '' let sid: string = '' if (item instanceof UTSJSONObject) { pid = item.getString('product_id') || '' sid = item.getString('sku_id') || '' } else { pid = (item['product_id'] as string) || '' sid = (item['sku_id'] as string) || '' } if (pid !== '' && !productIds.includes(pid)) { productIds.push(pid) } if (sid !== '' && !skuIds.includes(sid)) { skuIds.push(sid) } } // 批量查询商品详情 (使用视图关联店铺信息,修复字段名 specification -> attributes) const productMap = new Map() if (productIds.length > 0) { const productRes = await supa .from('ml_products_detail_view') .select('id,name,main_image_url,base_price,attributes,merchant_id,shop_name') .in('id', productIds) .execute() if (!productRes.error && productRes.data != null) { const products = productRes.data as any[] for (let i = 0; i < products.length; i++) { let p = products[i] let pid: string = '' if (p instanceof UTSJSONObject) { pid = p.getString('id') || '' } else { pid = (p['id'] as string) || '' } if (pid !== '') { productMap.set(pid, p) } } } } // 批量查询 SKU 详情 const skuMap = new Map() if (skuIds.length > 0) { const skuRes = await supa .from('ml_product_skus') .select('id, specifications, price, image_url') .in('id', skuIds) .execute() if (!skuRes.error && skuRes.data != null) { const skus = skuRes.data as any[] for (let i = 0; i < skus.length; i++) { let s = skus[i] let sid: string = '' if (s instanceof UTSJSONObject) { sid = s.getString('id') || '' } else { sid = (s['id'] as string) || '' } if (sid !== '') { skuMap.set(sid, s) } } } } // 处理返回数据,构建CartItem数组 const cartItems: CartItem[] = [] if (cartData && Array.isArray(cartData)) { for (let i = 0; i < cartData.length; i++) { let item = cartData[i] let itemId: string = '' let userIdVal: string = '' let productId: string = '' let skuId: string = '' let quantity: number = 0 let selected: boolean = false let createdAt: string = '' let updatedAt: string = '' if (item instanceof UTSJSONObject) { itemId = item.getString('id') || '' userIdVal = item.getString('user_id') || '' productId = item.getString('product_id') || '' skuId = item.getString('sku_id') || '' quantity = item.getNumber('quantity') || 0 selected = item.getBoolean('selected') || false createdAt = item.getString('created_at') || '' updatedAt = item.getString('updated_at') || '' } else { itemId = (item['id'] as string) || '' userIdVal = (item['user_id'] as string) || '' productId = (item['product_id'] as string) || '' skuId = (item['sku_id'] as string) || '' quantity = (item['quantity'] as number) || 0 selected = (item['selected'] as boolean) || false createdAt = (item['created_at'] as string) || '' updatedAt = (item['updated_at'] as string) || '' } const product = productMap.get(productId) const sku = (skuId !== '' && skuMap.has(skuId)) ? skuMap.get(skuId) : null let merchantId: string = '' let productName: string = '' let productImage: string = '' let productPrice: number = 0 let productSpec: string = '' let shopNameStr: string = '未知店铺' if (product != null) { if (product instanceof UTSJSONObject) { merchantId = product.getString('merchant_id') || '' productName = product.getString('name') || '' productImage = product.getString('main_image_url') || '' productPrice = product.getNumber('base_price') || 0 shopNameStr = product.getString('shop_name') || '未知店铺' // 只有当没有sku信息时,才尝试使用商品的attributes(作为降级显示) // 或者如果业务需求是属性和规格都显示,可以修改这里 // 但通常SKU规格更具体。如果sku为空,再显示product的attributes if (sku == null) { const specRaw = product.get('attributes') if (specRaw != null) { if (typeof specRaw === 'string') { productSpec = specRaw } else if (specRaw instanceof UTSJSONObject) { const keys = UTSJSONObject.keys(specRaw) const parts: string[] = [] for(let k = 0; k < keys.length; k++) { let val = specRaw.get(keys[k]) if (val != null) { parts.push(`${keys[k]}: ${val}`) } } productSpec = parts.join('; ') } else { try { let jsonStr = JSON.stringify(specRaw) productSpec = jsonStr.replace(/["{}]/g, '').replace(/,/g, '; ') } catch (e) {} } } } } else { merchantId = (product['merchant_id'] as string) || '' productName = (product['name'] as string) || '' productImage = (product['main_image_url'] as string) || '' productPrice = (product['base_price'] as number) || 0 shopNameStr = (product['shop_name'] as string) || '未知店铺' if (sku == null) { const specRaw = product['attributes'] if (specRaw != null) { if (typeof specRaw === 'string') { productSpec = specRaw } else if (specRaw instanceof UTSJSONObject) { const keys = UTSJSONObject.keys(specRaw) const parts: string[] = [] for(let k = 0; k < keys.length; k++) { let val = specRaw.get(keys[k]) if (val != null) { parts.push(`${keys[k]}: ${val}`) } } productSpec = parts.join('; ') } else { try { let jsonStr = JSON.stringify(specRaw) productSpec = jsonStr.replace(/["{}]/g, '').replace(/,/g, '; ') } catch (e) {} } } } } } // 如果有SKU信息,覆盖价格、图片和规格 if (sku != null) { if (sku instanceof UTSJSONObject) { const skuPrice = sku.getNumber('price') if (skuPrice != null && skuPrice > 0) { productPrice = skuPrice } const skuImg = sku.getString('image_url') if (skuImg != null && skuImg !== '') { productImage = skuImg } const specRaw = sku.get('specifications') if (specRaw != null) { // 优先使用SKU的规格 if (typeof specRaw === 'string') { productSpec = specRaw } else if (specRaw instanceof UTSJSONObject) { const keys = UTSJSONObject.keys(specRaw) const parts: string[] = [] for(let k = 0; k < keys.length; k++) { let val = specRaw.get(keys[k]) if (val != null) { parts.push(`${keys[k]}: ${val}`) } } productSpec = parts.join('; ') } else { try { let jsonStr = JSON.stringify(specRaw) productSpec = jsonStr.replace(/["{}]/g, '').replace(/,/g, '; ') } catch (e) {} } } } else { const skuPrice = sku['price'] as number if (skuPrice > 0) productPrice = skuPrice const skuImg = sku['image_url'] as string if (skuImg && skuImg !== '') productImage = skuImg const specRaw = sku['specifications'] if (specRaw != null) { // 优先使用SKU的规格 if (typeof specRaw === 'string') { productSpec = specRaw } else if (specRaw instanceof UTSJSONObject) { const keys = UTSJSONObject.keys(specRaw) const parts: string[] = [] for(let k = 0; k < keys.length; k++) { let val = specRaw.get(keys[k]) if (val != null) { parts.push(`${keys[k]}: ${val}`) } } productSpec = parts.join('; ') } else { try { let jsonStr = JSON.stringify(specRaw) productSpec = jsonStr.replace(/["{}]/g, '').replace(/,/g, '; ') } catch (e) {} } } } } let shopIdStr = merchantId || 'unknown_shop' cartItems.push({ id: itemId, user_id: userIdVal, product_id: productId, sku_id: skuId, merchant_id: merchantId, quantity: quantity, selected: selected, product_name: productName, product_image: productImage, product_price: productPrice, product_specification: productSpec, shop_id: shopIdStr, shop_name: shopNameStr, created_at: createdAt, updated_at: updatedAt }) } } return cartItems } catch (error) { console.error('获取购物车异常:', error) return [] } } // 获取用户通知 (系统、活动、订单) async getUserNotifications(type: string | null = null): Promise { try { const userId = this.getCurrentUserId() if (!userId) return [] let query = supa .from('ml_notifications') .select('*') .eq('user_id', userId) if (type) { query = query.eq('type', type) } const response = await query.order('created_at', { ascending: false }).execute() if (response.error) { console.error('获取通知失败:', response.error) return [] } return response.data as Notification[] } catch (e) { console.error('获取通知异常:', e) return [] } } // 获取用户聊天消息 async getUserChatMessages(): Promise { try { const userId = this.getCurrentUserId() if (!userId) return [] const response = await supa .from('ml_chat_messages') .select('*') .or(`sender_id.eq.${userId},receiver_id.eq.${userId}`) .order('created_at', { ascending: false }) .limit(50) .execute() if (response.error) { console.error('获取聊天记录失败:', response.error) return [] } return response.data as ChatMessage[] } catch (e) { console.error('获取聊天记录异常:', e) return [] } } // 添加商品到购物车 async addToCart(productId: string, quantity: number = 1, skuId?: string): Promise { try { const userId = this.getCurrentUserId() if (!userId) { console.error('用户未登录,无法添加商品到购物车') return false } // 检查商品是否已在购物车中 // 注意:必须处理 sku_id 为空的情况,使用 is.null 过滤器 let query = supa .from('ml_shopping_cart') .select('*') .eq('user_id', userId) .eq('product_id', productId) if (skuId && skuId.length > 0) { query = query.eq('sku_id', skuId) } else { query = query.is('sku_id', null) } const existingResponse = await query.single().execute() let existingItem: any | null = null if (existingResponse.data != null) { const rawData = existingResponse.data as any if (Array.isArray(rawData)) { if (rawData.length > 0) { existingItem = rawData[0] } } else { existingItem = rawData } } let response if (existingItem != null) { // 商品已存在,更新数量 console.log('Found existing cart item:', JSON.stringify(existingItem)) // 确保 existingItem.id 存在 const itemId = existingItem['id'] const itemQty = existingItem['quantity'] if (itemId != null) { const currentQty = typeof itemQty === 'number' ? itemQty : parseInt(String(itemQty || 0)) const newQty = currentQty + quantity response = await supa .from('ml_shopping_cart') .update({ quantity: newQty, updated_at: new Date().toISOString() }) .eq('id', itemId) .execute() } else { console.error('购物车已有商品但缺少ID,无法更新. Data:', JSON.stringify(existingItem)) return false } } 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 { console.log('正在执行删除购物车商品,ID:', cartItemId) const userId = this.getCurrentUserId() if (!userId) { console.error('用户未登录,无法删除购物车商品') return false } const response = await supa .from('ml_shopping_cart') .eq('id', cartItemId) .eq('user_id', userId) .delete() .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') .eq('user_id', userId) .in('id', cartItemIds) .delete() .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') .eq('user_id', userId) .delete() .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('*, recipient_name:receiver_name, phone:receiver_phone, detail_address:address_detail') .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('*, recipient_name:receiver_name, phone:receiver_phone, detail_address:address_detail') .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, receiver_name: address.recipient_name, receiver_phone: address.phone, province: address.province, city: address.city, district: address.district, address_detail: 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 updateData = {} if (address.recipient_name != null) updateData['receiver_name'] = address.recipient_name if (address.phone != null) updateData['receiver_phone'] = address.phone if (address.province != null) updateData['province'] = address.province if (address.city != null) updateData['city'] = address.city if (address.district != null) updateData['district'] = address.district if (address.detail_address != null) updateData['address_detail'] = address.detail_address if (address.postal_code != null) updateData['postal_code'] = address.postal_code if (address.is_default != null) updateData['is_default'] = address.is_default updateData['updated_at'] = new Date().toISOString() const response = await supa .from('ml_user_addresses') .update(updateData) .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 { console.log('正在执行删除地址,ID:', addressId) const userId = this.getCurrentUserId() if (!userId) { console.error('用户未登录,无法删除地址') return false } const response = await supa .from('ml_user_addresses') .eq('id', addressId) .eq('user_id', userId) .delete() .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 { await supa .from('ml_user_addresses') .update({ is_default: false, updated_at: new Date().toISOString() }) .eq('user_id', userId) .eq('is_default', true) .execute() } catch (error) { console.error('清除默认地址异常:', error) } } // 获取用户资料 async getUserProfile(): Promise { try { const userId = this.getCurrentUserId() if (!userId) return null // 联合查询 auth user 和 profile // 由于 Supabase auth table 不可直接访问,这里查询 ml_user_profiles const response = await supa .from('ml_user_profiles') .select('*') .eq('user_id', userId) .single() .execute() if (response.error) { // 如果不存在 profile,可能只有 auth user,这里暂时返回空或创建默认 return null } return response.data } catch (e) { return null } } // 创建订单 async createOrder(orderData: { merchant_id: string, product_amount: number, shipping_fee: number, total_amount: number, shipping_address: any, items: any[] }): Promise { try { const userId = this.getCurrentUserId() if (!userId) { console.error('CreateOrder: User not logged in') return null } // 生成订单号 const orderNo = 'ML' + Date.now() + Math.floor(Math.random() * 1000) // 1. 创建主订单 const orderResponse = await supa .from('ml_orders') .insert({ user_id: userId, merchant_id: orderData.merchant_id, order_no: orderNo, product_amount: orderData.product_amount, shipping_fee: orderData.shipping_fee, total_amount: orderData.total_amount, paid_amount: 0, shipping_address: typeof orderData.shipping_address === 'string' ? orderData.shipping_address : JSON.stringify(orderData.shipping_address), order_status: 1, // 待付款 payment_status: 1, // 未支付 shipping_status: 1, // 未发货 created_at: new Date().toISOString(), updated_at: new Date().toISOString() }) .select() .single() .execute() if (orderResponse.error) { console.error('创建订单失败:', orderResponse.error) return null } const orderId = orderResponse.data['id'] as string // 2. 创建订单项 const orderItems = orderData.items.map((item: any) => ({ order_id: orderId, product_id: item.product_id, sku_id: item.sku_id || null, product_name: item.product_name, sku_name: item.sku_name || '', specifications: item.specifications ? (typeof item.specifications === 'string' ? item.specifications : JSON.stringify(item.specifications)) : '{}', image_url: item.product_image || item.image_url, price: item.price, quantity: item.quantity, total_amount: item.price * item.quantity, created_at: new Date().toISOString() })) const itemsResponse = await supa .from('ml_order_items') .insert(orderItems) .execute() if (itemsResponse.error) { console.error('创建订单项失败:', itemsResponse.error) return null } // 3. 清除购物车中已购买的商品 const cartItemIds = orderData.items .filter((item: any) => item.id && item.id.length > 10) // 假设UUID长度大于10,区分临时ID .map((item: any) => item.id as string) if (cartItemIds.length > 0) { await this.batchDeleteCartItems(cartItemIds) } return orderId } catch (error) { console.error('创建订单异常:', error) return null } } // 批量通过店铺创建订单 async createOrdersByShop(params: { shipping_address: any, shopGroups: any[], deliveryFee: number, discountAmount: number }): Promise<{ success: boolean, orderIds: string[], error?: string }> { try { const orderIds: string[] = [] // 为每个店铺创建一个订单 for (const group of params.shopGroups) { const shopItems = group.items as any[] const productAmount = shopItems.reduce((sum, item) => sum + (item.price * item.quantity), 0) // 简单平摊运费和优惠 (实际逻辑可能更复杂) const ratio = productAmount / params.shopGroups.reduce((sum, g) => sum + g.items.reduce((s, i) => s + (i.price * i.quantity), 0), 0) const shopShippingFee = params.deliveryFee * ratio const shopDiscount = params.discountAmount * ratio const shopTotal = productAmount + shopShippingFee - shopDiscount const orderId = await this.createOrder({ merchant_id: group.merchant_id || group.shopId, // 兼容旧字段 product_amount: productAmount, shipping_fee: shopShippingFee, total_amount: shopTotal, shipping_address: params.shipping_address, items: shopItems }) if (orderId) { orderIds.push(orderId) } else { return { success: false, orderIds, error: `店铺 ${group.shopName} 订单创建失败` } } } return { success: true, orderIds } } catch (e) { console.error('批量创建订单异常:', e) return { success: false, orderIds: [], error: '系统异常' } } } // 获取订单列表 async getOrders(status: number = 0): Promise { try { const userId = this.getCurrentUserId() if (!userId) return [] let query = supa .from('ml_orders') .select(` *, ml_order_items (*) `) .eq('user_id', userId) .order('created_at', { ascending: false }) if (status > 0) { query = query.eq('order_status', status) } const response = await query.execute() if (response.error) { console.error('获取订单列表失败:', response.error) return [] } return response.data || [] } catch (error) { console.error('获取订单列表异常:', error) return [] } } // 获取订单详情 async getOrderDetail(orderId: string): Promise { try { const userId = this.getCurrentUserId() if (!userId) return null const response = await supa .from('ml_orders') .select(` *, ml_order_items (*) `) .eq('id', orderId) .eq('user_id', userId) .single() .execute() if (response.error) { return null } return response.data } catch (e) { return null } } // 支付订单 async payOrder(orderId: string, paymentMethod: string, amount: number): Promise { try { const userId = this.getCurrentUserId() if (!userId) return false // 1. Update order status const response = await supa .from('ml_orders') .update({ order_status: 2, // 待发货(已支付) payment_status: 1, // Pay Success payment_method: paymentMethod, // e.g. 'wechat', 'alipay' payment_time: new Date().toISOString(), updated_at: new Date().toISOString() }) .eq('id', orderId) .eq('user_id', userId) .execute() if (response.error) { console.error('Pay order failed', response.error) return false } // 2. Handle specific payment methods (e.g., deduct balance) if (paymentMethod === 'balance') { // await this.deductBalance(userId, amount) } return true } catch (e) { console.error('Pay order error', e) return false } } // 获取用户钱包余额 async getUserBalance(): Promise { try { const userId = this.getCurrentUserId() if (!userId) return 0 // Check if wallet table exists or uses profile // Assume ml_user_wallets or field in profile // For now returning mock high balance or check profile const profile = await this.getUserProfile() if (profile && typeof profile.balance === 'number') { return profile.balance } return 0 } catch(e) { return 0 } } // 收藏相关 async checkFavorite(productId: string): Promise { try { const userId = this.getCurrentUserId() console.log(`[CheckFav] Checking for User: ${userId}, Product: ${productId}`) if (!userId) return false const response = await supa .from('ml_user_favorites') .select('*') // Select all to verify data .eq('user_id', userId) .eq('target_id', productId) .eq('target_type', 1) // 1 for product .limit(1) .execute() // console.log(`[CheckFav] Response: ${JSON.stringify(response)}`) if (response.error) { console.error(`[CheckFav] Error: ${JSON.stringify(response.error)}`) return false } const data = response.data if (Array.isArray(data)) { if (data.length > 0) { // Double check: ensure the returned item actually matches the product ID // This guards against potential query filter failures const item = data[0] let targetId = '' if (item instanceof UTSJSONObject) { targetId = item.getString('target_id') || '' } else { targetId = (item['target_id'] as string) || '' } if (targetId !== '' && targetId !== productId) { console.error(`[CheckFav] ID Mismatch! Query ${productId}, Got ${targetId}`) return false } return true } } else if (data instanceof UTSJSONObject) { // Handle single object return case (though limit(1) usually returns array) let targetId = data.getString('target_id') || '' if (targetId !== '' && targetId !== productId) { return false } return true } return false } catch(e) { console.error(`[CheckFav] Exception: ${e}`) return false } } async toggleFavorite(productId: string): Promise { try { const userId = this.getCurrentUserId() if (!userId) return false console.log(`[ToggleFav] Toggling for ${productId}`) // Check if exists const exists = await this.checkFavorite(productId) console.log(`[ToggleFav] Current status: ${exists}`) if (exists) { // Delete const response = await supa .from('ml_user_favorites') .eq('user_id', userId) .eq('target_id', productId) .eq('target_type', 1) .delete() .execute() if (response.error) { console.error('取消收藏失败:', response.error) return true // 仍然是收藏状态 } return false // 已取消收藏 } else { // Add const response = await supa .from('ml_user_favorites') .insert({ user_id: userId, target_id: productId, target_type: 1, created_at: new Date().toISOString() }) .execute() if (response.error) { console.error('添加收藏失败:', response.error) return false // 添加失败,仍未收藏 } return true // 已收藏 } } catch (e) { console.error('切换收藏状态异常:', e) // 发生异常时,尝试查询当前状态返回 return await this.checkFavorite(productId) } } async getFavorites(): Promise { try { const userId = this.getCurrentUserId() if (!userId) return [] // 第一步:查询收藏列表 const response = await supa .from('ml_user_favorites') .select('*') .eq('user_id', userId) .eq('target_type', 1) .order('created_at', { ascending: false }) .execute() if (response.error) return [] const favorites = response.data as any[] if (!favorites || favorites.length === 0) return [] // 第二步:收集商品ID const productIds: string[] = [] for (let i = 0; i < favorites.length; i++) { let item = favorites[i] let pid = '' if (item instanceof UTSJSONObject) { pid = item.getString('target_id') || '' } else { pid = (item['target_id'] as string) || '' } if (pid !== '') productIds.push(pid) } if (productIds.length === 0) return [] // 第三步:批量查询商品详情 const productRes = await supa .from('ml_products') .select('id, name, main_image_url, base_price, sale_count') .in('id', productIds) .execute() if (productRes.error) return [] const products = productRes.data as any[] const productMap = new Map() for (let i = 0; i < products.length; i++) { let p = products[i] let pid = '' if (p instanceof UTSJSONObject) { pid = p.getString('id') || '' } else { pid = (p['id'] as string) || '' } if (pid !== '') productMap.set(pid, p) } // 第四步:组合数据 const result: any[] = [] for (let i = 0; i < favorites.length; i++) { let item = favorites[i] // 深拷贝或重新构造 item,避免修改原引用 let newItem: any if (item instanceof UTSJSONObject) { newItem = item.toMap() } else { newItem = { ...item } } let targetId = '' if (item instanceof UTSJSONObject) { targetId = item.getString('target_id') || '' } else { targetId = (item['target_id'] as string) || '' } const product = productMap.get(targetId) if (product) { // 将商品信息挂载到 ml_products 字段上,保持与前端代码兼容 newItem['ml_products'] = product result.push(newItem) } } return result } catch (e) { console.error('获取收藏列表异常:', e) return [] } } // 足迹相关 async addFootprint(productId: string): Promise { try { const userId = this.getCurrentUserId() if (!userId) return false // 检查是否已存在 const checkRes = await supa .from('ml_user_footprints') .select('id') .eq('user_id', userId) .eq('product_id', productId) .limit(1) .execute() if (checkRes.error) { console.error('检查足迹失败', checkRes.error) // 可能是表不存在,但我们还是尝试继续或返回 // 这里假设如果检查失败可能是网络问题或权限 // 如果表不存在,下面的 insert 也会失败 } const data = checkRes.data let exists = false if (Array.isArray(data) && data.length > 0) exists = true else if (data instanceof UTSJSONObject) exists = true // single object case if (exists) { // 更新时间 await supa .from('ml_user_footprints') .update({ updated_at: new Date().toISOString(), created_at: new Date().toISOString() // 更新 created_at 以便排序(可选,视需求而定,通常足迹按访问时间倒序) }) .eq('user_id', userId) .eq('product_id', productId) .execute() } else { // 新增 await supa .from('ml_user_footprints') .insert({ user_id: userId, product_id: productId, created_at: new Date().toISOString(), updated_at: new Date().toISOString() }) .execute() } return true } catch (e) { console.error('添加足迹异常:', e) return false } } async getFootprints(): Promise { try { const userId = this.getCurrentUserId() if (!userId) return [] // 1. 获取足迹列表 const response = await supa .from('ml_user_footprints') .select('*') .eq('user_id', userId) .order('updated_at', { ascending: false }) // 按最后访问时间倒序 .limit(100) // 限制数量 .execute() if (response.error) { console.error('获取足迹列表失败:', response.error) return [] } const footprints = response.data as any[] console.log('原始足迹数据条数:', footprints.length) if (footprints.length > 0) { console.log('第一条足迹:', JSON.stringify(footprints[0])) } if (!footprints || footprints.length === 0) return [] // 2. 收集商品ID const productIds: string[] = [] for(let i=0; i() for(let i=0; i { try { const userId = this.getCurrentUserId() if (!userId) return false await supa .from('ml_user_footprints') .eq('user_id', userId) .eq('product_id', productId) .delete() .execute() return true } catch (e) { return false } } async clearFootprints(): Promise { try { const userId = this.getCurrentUserId() if (!userId) return false await supa .from('ml_user_footprints') .eq('user_id', userId) .delete() .execute() return true } catch (e) { return false } } async getAddressList(): Promise { try { const userId = this.getCurrentUserId() if (!userId) return [] const response = await supa .from('ml_user_addresses') .select('*, recipient_name:receiver_name, phone:receiver_phone, detail_address:address_detail') .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 (e) { console.error('获取地址列表异常:', e) return [] } } // 设置默认地址 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 } } } // 导出单例实例 export const supabaseService = new SupabaseService() // 默认导出 export default supabaseService