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) // 辅助函数:安全获取字符串值 function safeGetString(obj: UTSJSONObject, key: string): string { try { const rawVal = obj.get(key) if (rawVal == null) return '' if (typeof rawVal == 'string') return rawVal as string if (typeof rawVal == 'number') return (rawVal as number).toString() if (typeof rawVal == 'boolean') return (rawVal as boolean) ? 'true' : 'false' return '' } catch (e) { console.error('safeGetString error for key:', key, e) return '' } } // 辅助函数:安全获取数值 function safeGetNumber(obj: UTSJSONObject, key: string): number { try { const rawVal = obj.get(key) if (rawVal == null) return 0 if (typeof rawVal == 'number') return rawVal as number if (typeof rawVal == 'string') { const parsed = parseFloat(rawVal as string) return isNaN(parsed) ? 0 : parsed } return 0 } catch (e) { console.error('safeGetNumber error for key:', key, e) return 0 } } // 辅助函数:安全获取布尔值 function safeGetBoolean(obj: UTSJSONObject, key: string): boolean { try { const rawVal = obj.get(key) if (rawVal == null) return false if (typeof rawVal == 'boolean') return rawVal as boolean if (typeof rawVal == 'string') return (rawVal as string) === 'true' if (typeof rawVal == 'number') return (rawVal as number) !== 0 return false } catch (e) { console.error('safeGetBoolean error for key:', key, e) return false } } // 辅助函数:安全获取字符串数组 function safeGetStringArray(obj: UTSJSONObject, key: string): string[] { try { const rawVal = obj.get(key) if (rawVal != null && Array.isArray(rawVal)) { return rawVal as string[] } return [] as string[] } catch (e) { console.error('safeGetStringArray error for key:', key, e) return [] as string[] } } // 辅助函数:从原始数据解析商品 function parseProductFromRaw(item: any): Product { try { const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const getSafeString = (key: string): string => { const val = itemObj.get(key) if (val == null) return '' if (typeof val === 'string') return val if (typeof val === 'number') return val.toString() if (typeof val === 'boolean') return val ? 'true' : 'false' return '' } const getSafeNumber = (key: string): number => { const val = itemObj.get(key) if (val == null) return 0 if (typeof val === 'number') return val if (typeof val === 'string') { const parsed = parseFloat(val) return isNaN(parsed) ? 0 : parsed } return 0 } const getSafeBoolean = (key: string): boolean => { const val = itemObj.get(key) if (val == null) return false if (typeof val === 'boolean') return val if (typeof val === 'string') return val === 'true' if (typeof val === 'number') return val !== 0 return false } const getSafeStringArray = (key: string): string[] => { const val = itemObj.get(key) if (val != null && Array.isArray(val)) { return val as string[] } return [] } const mainImageUrl = getSafeString('main_image_url') return { id: getSafeString('id'), name: getSafeString('name'), description: getSafeString('description'), base_price: getSafeNumber('base_price'), price: getSafeNumber('base_price'), original_price: getSafeNumber('market_price'), market_price: getSafeNumber('market_price'), main_image_url: mainImageUrl, image_url: mainImageUrl, images: getSafeStringArray('image_urls'), category_id: getSafeString('category_id'), brand_id: getSafeString('brand_id'), merchant_id: getSafeString('merchant_id'), total_stock: getSafeNumber('total_stock'), stock: getSafeNumber('total_stock'), sale_count: getSafeNumber('sale_count'), status: getSafeNumber('status'), is_featured: getSafeBoolean('is_featured'), is_new: getSafeBoolean('is_new'), is_hot: getSafeBoolean('is_hot'), specification: getSafeString('specification'), usage: getSafeString('usage'), side_effects: getSafeString('side_effects'), precautions: getSafeString('precautions'), expiry_date: getSafeString('expiry_date'), storage_conditions: getSafeString('storage_conditions'), approval_number: getSafeString('approval_number'), created_at: getSafeString('created_at') } as Product } catch (e) { console.error('parseProductFromRaw error:', e) return { id: '', name: '', description: '', base_price: 0, price: 0, original_price: 0, market_price: 0, main_image_url: '', image_url: '', images: [] as string[], category_id: '', brand_id: '', merchant_id: '', total_stock: 0, stock: 0, sale_count: 0, status: 0, is_featured: false, is_new: false, is_hot: false, specification: '', usage: '', side_effects: '', precautions: '', expiry_date: '', storage_conditions: '', approval_number: '', created_at: '' } as Product } } // 类型定义 export type Brand = { id: string name: string logo_url: string description: string } export type Category = { id: string name: string icon: string description: string color: string parent_id?: string level?: number slug?: string created_at?: string } export type 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_url?: string image_urls?: string video_urls?: string images?: string[] 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 rating?: number review_count?: number brand_id?: string shop_id?: string tags?: string attributes?: string specification?: string usage?: string side_effects?: string precautions?: string expiry_date?: string storage_conditions?: string approval_number?: string created_at?: string updated_at?: string price?: number original_price?: number stock?: number sales?: number cover?: string brand_name?: string category_name?: string shop_name?: string merchant_name?: string } export type 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 type 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 type 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 label?: string created_at?: string updated_at?: string } export type UserCoupon = { id: string user_id: string template_id: string coupon_code: string status: number // 1: unused, 2: used, 3: expired received_at: string expire_at: string used_at?: string // join fields from template or view template_name?: string amount?: number min_spend?: number name?: string title?: string } export type ChatRoom = { id: string user_id: string merchant_id: string shop_name: string shop_logo?: string last_message?: string last_message_at?: string unread_count: number is_top: boolean created_at?: string updated_at?: string } export type 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 type 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 type PaginatedResponse = { data: T[] total: number page: number limit: number hasmore: boolean } export type 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 } export type AddAddressParams = { recipient_name: string phone: string province: string city: string district: string detail_address: string postal_code?: string is_default?: boolean label?: string } export type UpdateAddressParams = { recipient_name?: string phone?: string province?: string city?: string district?: string detail_address?: string postal_code?: string is_default?: boolean label?: string } export type CreateOrderParams = { merchant_id: string product_amount: number shipping_fee: number total_amount: number shipping_address: any items: any[] } export type ShopOrderParams = { shipping_address: any shopGroups: any[] deliveryFee: number discountAmount: number } export type ShopOrderResponse = { success: boolean orderIds: string[] error?: string } export type RefundResponse = { success: boolean message: string } export type ConfirmReceiptResponse = { success: boolean error?: string } class SupabaseService { // 获取当前用户ID public getCurrentUserId(): string | null { try { // 1. 优先从 Supabase 会话获取 const session = supa.getSession() if (session != null && session.user != null) { return session.user.getString('id') } // 2. 尝试从 Storage 恢复 Session (针对 App 重启后内存丢失的情况) // 注意:这里无法异步调用 hydrate,所以只能依赖 UI 层或 init 层的预加载 // 但我们可以返回本地存储 ID 作为 fallback,前提是 Token 有效 // 后备:尝试从本地存储获取 const userId = uni.getStorageSync('user_id') return userId != null ? userId as string : null } catch (e) { console.error('获取用户ID失败:', e) return null } } // 确保会话有效 (异步) async ensureSession(): Promise { let session = supa.getSession() if (session.user == null) { console.log('Session user is null, attempting to hydrate from storage...') await supa.hydrateSessionFromStorage() session = supa.getSession() } if (session.user != null) { // 同步 user_id 到 storage 保持一致 const uid = session.user!!.getString('id') if (uid != null) { uni.setStorageSync('user_id', uid) return uid } } return this.getCurrentUserId() } // 获取所有分类 async getCategories(): Promise { try { const response = await supa .from('ml_categories') .select('*') .order('name', { ascending: true }) .execute() if (response.error != null) { console.error('获取分类失败:', response.error) return [] } const rawData = response.data if (rawData == null) { return [] } const categories: Category[] = [] const rawList = rawData as any[] for (let i: number = 0; i < rawList.length; i++) { const item = rawList[i] const catObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const idVal = catObj.get('id') const nameVal = catObj.get('name') const iconVal = catObj.get('icon') const iconUrlVal = catObj.get('icon_url') const descVal = catObj.get('description') const colorVal = catObj.get('color') const parentIdVal = catObj.get('parent_id') const levelVal = catObj.get('level') const cat: Category = { id: (typeof idVal == 'string') ? (idVal as string) : '', name: (typeof nameVal == 'string') ? (nameVal as string) : '', icon: (typeof iconVal == 'string') ? (iconVal as string) : ((typeof iconUrlVal == 'string') ? (iconUrlVal as string) : ''), description: (typeof descVal == 'string') ? (descVal as string) : '', color: (typeof colorVal == 'string') ? (colorVal as string) : '#4CAF50', parent_id: (typeof parentIdVal == 'string') ? (parentIdVal as string) : null, level: (typeof levelVal == 'number') ? (levelVal as number) : 0 } as Category categories.push(cat) } return categories } catch (error) { console.error('获取分类异常:', error) return [] } } // 根据ID获取单个分类 async getCategoryById(categoryId: string): Promise { try { const response = await supa .from('ml_categories') .select('*') .eq('id', categoryId) .limit(1) .execute() if (response.error != null) { console.error('获取分类失败:', response.error) return null } const rawData = response.data if (rawData == null) { return null } // 处理数组返回值 const rawList = rawData as any[] if (rawList.length == 0) { return null } const item = rawList[0] const catObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const idVal = catObj.get('id') const nameVal = catObj.get('name') const iconVal = catObj.get('icon') const iconUrlVal = catObj.get('icon_url') const descVal = catObj.get('description') const colorVal = catObj.get('color') const parentIdVal = catObj.get('parent_id') const levelVal = catObj.get('level') const cat: Category = { id: (typeof idVal == 'string') ? (idVal as string) : '', name: (typeof nameVal == 'string') ? (nameVal as string) : '', icon: (typeof iconVal == 'string') ? (iconVal as string) : ((typeof iconUrlVal == 'string') ? (iconUrlVal as string) : ''), description: (typeof descVal == 'string') ? (descVal as string) : '', color: (typeof colorVal == 'string') ? (colorVal as string) : '#4CAF50', parent_id: (typeof parentIdVal == 'string') ? (parentIdVal as string) : null, level: (typeof levelVal == 'number') ? (levelVal as number) : 0 } as Category return cat } catch (error) { console.error('获取分类异常:', error) return null } } // 获取一级分类 async getParentCategories(): Promise { try { const response = await supa .from('ml_categories') .select('*') .is('parent_id', null) .order('sort_order', { ascending: true }) .execute() if (response.error != null) { console.error('获取一级分类失败:', response.error) return [] } const rawData = response.data if (rawData == null) { return [] } const categories: Category[] = [] const rawList = rawData as Array for (let i = 0; i < rawList.length; i++) { const item = rawList[i] const icon = this.getCategoryIcon(item) // 安全获取属性 const idVal = item['id'] const nameVal = item['name'] const descVal = item['description'] const colorVal = item['color'] const slugVal = item['slug'] const cat: Category = { id: (typeof idVal == 'string') ? (idVal as string) : '', name: (typeof nameVal == 'string') ? (nameVal as string) : '', icon: icon, description: (typeof descVal == 'string') ? (descVal as string) : '', color: (typeof colorVal == 'string') ? (colorVal as string) : '#4CAF50', level: 1, slug: (typeof slugVal == 'string') ? (slugVal as string) : '' } categories.push(cat) } return categories } catch (error) { console.error('获取一级分类异常:', error) return [] } } // 获取子分类 async getSubCategories(parentId: string): Promise { try { console.log('[getSubCategories] 开始获取子分类, parentId:', parentId) const response = await supa .from('ml_categories') .select('*') .order('sort_order', { ascending: true }) .execute() console.log('[getSubCategories] 查询完成') if (response.error != null) { console.error('获取子分类失败:', response.error) return [] } const rawData = response.data if (rawData == null) { console.log('[getSubCategories] 数据为空') return [] } const categories: Category[] = [] const rawList = rawData as any[] console.log('[getSubCategories] 原始数据条数:', rawList.length) for (let i = 0; i < rawList.length; i++) { const item = rawList[i] const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject // 手动过滤 parent_id const itemParentId = safeGetString(itemObj, 'parent_id') const isMatch = (itemParentId.length > 0 && itemParentId == parentId) if (!isMatch) { continue } const icon = this.getCategoryIcon(itemObj) const cat: Category = { id: safeGetString(itemObj, 'id'), name: safeGetString(itemObj, 'name'), icon: icon, description: safeGetString(itemObj, 'description'), color: safeGetString(itemObj, 'color').length > 0 ? safeGetString(itemObj, 'color') : '#4CAF50', level: 2, parent_id: safeGetString(itemObj, 'parent_id'), slug: safeGetString(itemObj, 'slug') } categories.push(cat) } console.log('[getSubCategories] 返回分类数量:', categories.length) return categories } catch (error) { console.error('获取子分类异常:', error) return [] } } // 获取分类图标的辅助方法 getCategoryIcon(item: UTSJSONObject): string { const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const icon = safeGetString(itemObj, 'icon') if (icon.length > 0) { return icon } const iconUrl = safeGetString(itemObj, 'icon_url') if (iconUrl.length > 0) { return iconUrl } const name = safeGetString(itemObj, 'name') if (name.includes('数码') || name.includes('电器') || name.includes('手机')) return '📱' if (name.includes('服装') || name.includes('衣服') || name.includes('鞋')) return '👕' if (name.includes('食品') || name.includes('水果') || name.includes('零食')) return '🍎' if (name.includes('美妆') || name.includes('护肤') || name.includes('化妆')) return '💄' if (name.includes('母婴') || name.includes('婴儿') || name.includes('儿童')) return '👶' if (name.includes('家居') || name.includes('家具') || name.includes('装饰')) return '🏠' if (name.includes('图书') || name.includes('文具')) return '📚' if (name.includes('运动') || name.includes('户外') || name.includes('健身')) return '⚽' if (name.includes('医药') || name.includes('保健') || name.includes('健康')) return '💊' return '📦' } // 获取所有品牌 async getBrands(): Promise { try { console.log('[getBrands] 开始获取品牌数据...') const response = await supa .from('ml_brands') .select('id, name, logo_url, description, is_active') .order('name', { ascending: true }) .execute() if (response.error != null) { console.error('获取品牌失败:', response.error) return [] } const rawData = response.data if (rawData == null) { console.log('[getBrands] 数据为空') return [] } const brands: Brand[] = [] const rawList = rawData as any[] console.log('[getBrands] 数据条数:', rawList.length) for (let i: number = 0; i < rawList.length; i++) { const item = rawList[i] const brandObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const idVal = brandObj.get('id') const nameVal = brandObj.get('name') const logoVal = brandObj.get('logo_url') const descVal = brandObj.get('description') const isActiveVal = brandObj.get('is_active') let isActiveBool: boolean = true if (isActiveVal != null) { if (typeof isActiveVal == 'boolean') { isActiveBool = isActiveVal as boolean } else if (typeof isActiveVal == 'number') { isActiveBool = (isActiveVal as number) === 1 } } if (!isActiveBool) { continue } const brand: Brand = { id: (typeof idVal == 'string') ? (idVal as string) : '', name: (typeof nameVal == 'string') ? (nameVal as string) : '', logo_url: (typeof logoVal == 'string') ? (logoVal as string) : '', description: (typeof descVal == 'string') ? (descVal as string) : '' } as Brand brands.push(brand) } console.log('[getBrands] 返回品牌数量:', brands.length) return brands } catch (error) { console.error('获取品牌异常:', error) return [] } } // 获取指定分类的商品 async getProductsByCategory( categoryId: string, page: number = 1, limit: number = 20 ): Promise> { try { console.log('[getProductsByCategory] 开始查询,分类ID:', categoryId, '页码:', page) // 在数据库层面进行分类过滤 const response = await supa .from('ml_products_detail_view') .select('*', { count: 'exact' }) .eq('category_id', categoryId) .eq('status', '1') // 使用字符串 '1' 而不是整数 1 .order('sale_count', { ascending: false }) .page(page) .limit(limit) .execute() console.log('[getProductsByCategory] 查询完成,total:', response.total) if (response.error != null) { console.error('获取商品失败:', response.error) return { data: [] as Product[], total: 0, page, limit, hasmore: false } } const rawData = response.data if (rawData == null) { return { data: [] as Product[], total: 0, page, limit, hasmore: false } } const products: Product[] = [] const rawList = rawData as any[] console.log('[getProductsByCategory] 返回数据条数:', rawList.length) for (let i: number = 0; i < rawList.length; i++) { const item = rawList[i] products.push(parseProductFromRaw(item)) } return { data: products, total: response.total ?? products.length, page, limit, hasmore: response.hasmore ?? false } } catch (error) { console.error('获取商品异常:', error) return { data: [] as Product[], total: 0, page, limit, hasmore: false } } } // 根据商品ID获取SKU列表 async getProductSkus(productId: string): Promise { try { console.log('[getProductSkus] 开始获取SKU,商品ID:', productId) const response = await supa .from('ml_product_skus') .select('*') .eq('product_id', productId) .eq('status', '1') .execute() if (response.error != null) { console.error('获取商品SKU失败:', response.error) return [] } const rawData = response.data if (rawData == null) return [] const skus: ProductSku[] = [] const rawList = rawData as any[] console.log('[getProductSkus] 获取到SKU数量:', rawList.length) for (let i = 0; i < rawList.length; i++) { const item = rawList[i] const skuObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const rawId = skuObj.get('id') const rawSkuCode = skuObj.get('sku_code') const rawProdId = skuObj.get('product_id') const rawPrice = skuObj.get('price') const rawStock = skuObj.get('stock') const rawImageUrl = skuObj.get('image_url') const rawSpecs = skuObj.get('specifications') let specsStr = '' if (rawSpecs != null) { try { if (typeof rawSpecs == 'string') { specsStr = rawSpecs as string } else { specsStr = JSON.stringify(rawSpecs) } } catch(e) { console.error('解析SKU规格失败', e) } } const sku: ProductSku = { id: (typeof rawId == 'string') ? (rawId as string) : '', product_id: (typeof rawProdId == 'string') ? (rawProdId as string) : '', sku_code: (typeof rawSkuCode == 'string') ? (rawSkuCode as string) : '', specifications: specsStr, price: (typeof rawPrice == 'number') ? (rawPrice as number) : 0, stock: (typeof rawStock == 'number') ? (rawStock as number) : 0, image_url: (typeof rawImageUrl == 'string') ? (rawImageUrl as string) : '', status: 1 } skus.push(sku) } return skus } 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' }) // 根据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 != null) { console.error('搜索商品失败:', response.error) return { data: [] as Product[], total: 0, page, limit, hasmore: false } } const rawData = response.data if (rawData == null) { return { data: [] as Product[], total: 0, page, limit, hasmore: false } } const products: Product[] = [] const rawList = rawData as any[] const keywordLower = keyword.toLowerCase() for (let i = 0; i < rawList.length; i++) { const item = rawList[i] const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject // 手动过滤 status const rawStatus = prodObj.get('status') let statusNum: number = 0 if (typeof rawStatus == 'number') { statusNum = rawStatus as number } if (statusNum !== 1) continue // 手动过滤关键词 const name = safeGetString(prodObj, 'name').toLowerCase() const desc = safeGetString(prodObj, 'description').toLowerCase() const subtitle = safeGetString(prodObj, 'subtitle').toLowerCase() const brandName = safeGetString(prodObj, 'brand_name').toLowerCase() if (name.indexOf(keywordLower) === -1 && desc.indexOf(keywordLower) === -1 && subtitle.indexOf(keywordLower) === -1 && brandName.indexOf(keywordLower) === -1) { continue } products.push(parseProductFromRaw(item)) } return { data: products, total: response.total ?? products.length, page, limit, hasmore: response.hasmore ?? false } } catch (error) { console.error('搜索商品异常:', error) return { data: [] as Product[], total: 0, page, limit, hasmore: false } } } // 搜索店铺 async searchShops( keyword: string, page: number = 1, limit: number = 20 ): Promise> { try { const response = await supa .from('ml_shops') .select('*', { count: 'exact' }) .ilike('shop_name', `%${keyword}%`) .order('product_count', { ascending: false }) .page(page) .limit(limit) .execute() if (response.error != null) { console.error('搜索店铺失败:', response.error) return { data: [] as Shop[], total: 0, page, limit, hasmore: false } } const rawData = response.data if (rawData == null) { return { data: [] as Shop[], total: 0, page, limit, hasmore: false } } const shops: Shop[] = [] const dataList = rawData as any[] for (let i = 0; i < dataList.length; i++) { const item = dataList[i] const shopObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject // 手动过滤 status const rawStatus = shopObj.get('status') let statusNum: number = 0 if (typeof rawStatus == 'number') { statusNum = rawStatus as number } if (statusNum !== 1) continue shops.push(item as Shop) } return { data: shops, total: response.total ?? shops.length, page, limit, hasmore: response.hasmore ?? false } } catch (error) { console.error('搜索店铺异常:', error) return { data: [] as Shop[], total: 0, page, limit, hasmore: false } } } // 获取单个商品详情 async getProductById(productId: string): Promise { try { console.log('[getProductById] 开始获取商品详情,ID:', productId) const response = await supa .from('ml_products_detail_view') .select('*') .eq('id', productId) .limit(1) .execute() if (response.error != null) { console.error('获取商品详情失败:', response.error) return null } const rawData = response.data if (rawData == null) { console.log('[getProductById] 数据为空') return null } const rawList = rawData as any[] if (rawList.length == 0) { console.log('[getProductById] 未找到商品') return null } const item = rawList[0] const product = parseProductFromRaw(item) console.log('[getProductById] 成功获取商品:', product.name) return product } catch (error) { console.error('获取商品详情异常:', error) return null } } // --- 关注店铺相关 --- // 检查是否已关注店铺 async isShopFollowed(shopId: string, userId: string): Promise { try { const res = await supa .from('ml_shop_follows') .select('id', { count: 'exact' }) .eq('shop_id', shopId) .eq('user_id', userId) .limit(1) .execute() return (res.total != null && res.total! > 0) } catch (e) { console.error('Check follow error:', e) return false } } // 关注店铺 async followShop(shopId: string, userId: string): Promise { try { const res = await supa .from('ml_shop_follows') .insert({ user_id: userId, shop_id: shopId }) .execute() return res.error == null } catch (e) { console.error('Follow shop error:', e) return false } } // 取消关注 async unfollowShop(shopId: string, userId: string): Promise { try { const res = await supa .from('ml_shop_follows') .eq('shop_id', shopId) .eq('user_id', userId) .delete() .execute() return res.error == null } catch (e) { console.error('Unfollow shop error:', e) return false } } // 获取我关注的店铺列表 async getFollowedShops(userId: string): Promise { try { // 关联查询店铺信息 const res = await supa .from('ml_shop_follows') .select('*, ml_shops(*)') .eq('user_id', userId) .order('created_at', { ascending: false }) .execute() if (res.error != null) { console.error('getFollowedShops error:', res.error) return [] } return res.data as any[] } catch (e) { console.error('getFollowedShops exception:', e) return [] } } // 根据商户ID获取店铺信息 async getShopByMerchantId(merchantId: string): Promise { try { console.log('[getShopByMerchantId] 开始获取店铺信息,merchantId:', merchantId) // 1. Try querying by merchant_id let response = await supa .from('ml_shops') .select('*') .eq('merchant_id', merchantId) .limit(1) .execute() if (response.error == null && response.data != null && (response.data as any[]).length > 0) { const shopData = (response.data as any[])[0] const shop = this.parseShopFromRaw(shopData) console.log('[getShopByMerchantId] 通过 merchant_id 找到店铺:', shop.shop_name) return shop } // 2. Fallback: Try querying by id (Maybe the passed ID is the Shop ID?) console.log('getShopByMerchantId: merchant_id not found, trying id...', merchantId) response = await supa .from('ml_shops') .select('*') .eq('id', merchantId) .limit(1) .execute() if (response.error == null && response.data != null && (response.data as any[]).length > 0) { console.log('Found shop by ID instead of MerchantID') const shopData = (response.data as any[])[0] const shop = this.parseShopFromRaw(shopData) return shop } if (response.error != null) { console.error('获取店铺信息失败:', response.error) } return null } catch (error) { console.error('获取店铺信息异常:', error) return null } } // 解析店铺数据 parseShopFromRaw(item: any): Shop { const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const getSafeString = (key: string): string => { const val = itemObj.get(key) if (val == null) return '' if (typeof val == 'string') return val return '' } const getSafeNumber = (key: string): number => { const val = itemObj.get(key) if (val == null) return 0 if (typeof val == 'number') return val return 0 } return { id: getSafeString('id'), merchant_id: getSafeString('merchant_id'), shop_name: getSafeString('shop_name'), shop_logo: getSafeString('shop_logo'), shop_banner: getSafeString('shop_banner'), description: getSafeString('description'), contact_name: getSafeString('contact_name'), contact_phone: getSafeString('contact_phone'), rating_avg: getSafeNumber('rating_avg'), total_sales: getSafeNumber('total_sales'), product_count: getSafeNumber('product_count'), total_sales_count: getSafeNumber('total_sales_count'), created_at: getSafeString('created_at') } as Shop } // 根据商户ID获取商品列表 async getProductsByMerchantId(merchantId: string, page: number = 1, limit: number = 20): Promise> { try { console.log('getProductsByMerchantId querying for:', merchantId) // 1. Try fetching from view strictly first let query = supa .from('ml_products_detail_view') .select('*', { count: 'exact' }) .eq('merchant_id', merchantId) // .eq('status', 1) // Temporarily disabled status check to see if products exist at all .order('created_at', { ascending: false }) .page(page) .limit(limit) const response = await query.execute() // 检查视图结果:如果有错误 OR 数据为空,都尝试去查原始表 if (response.error != null || (response.data != null && (response.data as any[]).length === 0)) { if (response.error != null) { console.error('获取商户商品失败 (View):', response.error) } else { console.log('View returned 0 products, trying raw table fallback...') } // Fallback: Try raw table console.log('Falling back to raw ml_products table...') const query2 = supa .from('ml_products') .select('*', { count: 'exact' }) .eq('merchant_id', merchantId) // .eq('status', 1) // Also disabled here .order('created_at', { ascending: false }) .page(page) .limit(limit) const res2 = await query2.execute() if (res2.error != null) { console.error('获取商户商品失败 (Raw):', res2.error) return {data:[] as Product[], total:0, page, limit, hasmore:false} } console.log(`Fallback (Raw) found: ${(res2.data as any[]).length} products`) const mappedData: Product[] = [] const rawData = res2.data as any[] for(let i = 0; i < rawData.length; i++) { const item = JSON.parse(JSON.stringify(rawData[i])) as UTSJSONObject const images: string[] = [] const mainImageUrl = item.getString('main_image_url') if (mainImageUrl != null && mainImageUrl !== '') { images.push(mainImageUrl) } const imageUrlsRaw = item.get('image_urls') if (imageUrlsRaw != null) { try { if (Array.isArray(imageUrlsRaw)) { const arr = imageUrlsRaw as string[] if (arr.length > 0 && images.length === 0) { for (let j = 0; j < arr.length; j++) { images.push(arr[j]) } } } else { const rawUrlStr = imageUrlsRaw as string if (rawUrlStr.startsWith('[')) { const parsed = JSON.parse(rawUrlStr) if (Array.isArray(parsed) && images.length === 0) { for (let j = 0; j < parsed.length; j++) { images.push(parsed[j] as string) } } } else { if (images.indexOf(rawUrlStr) === -1) images.push(rawUrlStr) } } } catch(e) { console.error('解析图片数组失败:', e) } } if (images.length === 0) { images.push('/static/default-product.png') } let safePrice = item.getNumber('base_price') if (safePrice == null) { const p = item.getNumber('price') safePrice = p != null ? p : 0 } let safeOriginalPrice = item.getNumber('market_price') if (safeOriginalPrice == null) { const op = item.getNumber('original_price') safeOriginalPrice = op != null ? op : safePrice } let safeStock = item.getNumber('total_stock') if (safeStock == null) { let as_ = item.getNumber('available_stock') if (as_ == null) { const s = item.getNumber('stock') safeStock = s != null ? s : 0 } else { safeStock = as_ } } let safeSales = item.getNumber('sale_count') if (safeSales == null) { const s = item.getNumber('sales') safeSales = s != null ? s : 0 } const product: Product = { id: item.getString('id') ?? '', category_id: item.getString('category_id') ?? '', merchant_id: item.getString('merchant_id') ?? '', name: item.getString('name') ?? '', description: item.getString('description') ?? '', images: images, price: safePrice, original_price: safeOriginalPrice, stock: safeStock, sales: safeSales, status: item.getNumber('status') ?? 1, created_at: item.getString('created_at') ?? '', base_price: safePrice, market_price: safeOriginalPrice, main_image_url: images.length > 0 ? images[0] : '', sale_count: safeSales, total_stock: safeStock } as Product mappedData.push(product) } return { data: mappedData, total: res2.total ?? 0, page, limit, hasmore: res2.hasmore ?? false } } console.log(`Merchant products found: ${(response.data as any[]).length}`) return { data: response.data as Product[], total: response.total ?? 0, page, limit, hasmore: response.hasmore ?? false } } catch (error) { console.error('获取商户商品异常:', error) return { data: [] as Product[], total: 0, page, limit, hasmore: false } } } // 根据店铺ID获取商品列表(新增) async getProductsByShopId(shopId: string, page: number = 1, limit: number = 20): Promise> { try { console.log('getProductsByShopId querying for:', shopId) // 1. Try fetching from view with shop_id let query = supa .from('ml_products_detail_view') .select('*', { count: 'exact' }) .eq('shop_id', shopId) .order('created_at', { ascending: false }) .page(page) .limit(limit) const response = await query.execute() // 检查视图结果:如果有错误 OR 数据为空,都尝试去查原始表 if (response.error != null || (response.data != null && (response.data as any[]).length === 0)) { if (response.error != null) { console.error('获取店铺商品失败 (View):', response.error) } else { console.log('View returned 0 products, trying raw table fallback...') } // Fallback: Try raw table with shop_id console.log('Falling back to raw ml_products table with shop_id...') const query2 = supa .from('ml_products') .select('*', { count: 'exact' }) .eq('shop_id', shopId) .order('created_at', { ascending: false }) .page(page) .limit(limit) const res2 = await query2.execute() if (res2.error != null) { console.error('获取店铺商品失败 (Raw):', res2.error) return {data:[] as Product[], total:0, page, limit, hasmore:false} } console.log(`Fallback (Raw) found: ${(res2.data as any[]).length} products`) const mappedData: Product[] = [] const rawData = res2.data as any[] for(let i = 0; i < rawData.length; i++) { const item = JSON.parse(JSON.stringify(rawData[i])) as UTSJSONObject const images: string[] = [] const mainImageUrl = item.getString('main_image_url') if (mainImageUrl != null && mainImageUrl !== '') { images.push(mainImageUrl) } const imageUrlsRaw = item.get('image_urls') if (imageUrlsRaw != null) { try { if (Array.isArray(imageUrlsRaw)) { const arr = imageUrlsRaw as string[] if (arr.length > 0 && images.length === 0) { for (let j = 0; j < arr.length; j++) { images.push(arr[j]) } } } else { const rawUrlStr = imageUrlsRaw as string if (rawUrlStr.startsWith('[')) { const parsed = JSON.parse(rawUrlStr) if (Array.isArray(parsed) && images.length === 0) { for (let j = 0; j < parsed.length; j++) { images.push(parsed[j] as string) } } } else { if (images.indexOf(rawUrlStr) === -1) images.push(rawUrlStr) } } } catch(e) { console.error('解析图片数组失败:', e) } } if (images.length === 0) { images.push('/static/default-product.png') } let safePrice = item.getNumber('base_price') if (safePrice == null) { const p = item.getNumber('price') safePrice = p != null ? p : 0 } let safeOriginalPrice = item.getNumber('market_price') if (safeOriginalPrice == null) { const op = item.getNumber('original_price') safeOriginalPrice = op != null ? op : safePrice } let safeStock = item.getNumber('total_stock') if (safeStock == null) { let as_ = item.getNumber('available_stock') if (as_ == null) { const s = item.getNumber('stock') safeStock = s != null ? s : 0 } else { safeStock = as_ } } let safeSales = item.getNumber('sale_count') if (safeSales == null) { const s = item.getNumber('sales') safeSales = s != null ? s : 0 } const product: Product = { id: item.getString('id') ?? '', category_id: item.getString('category_id') ?? '', merchant_id: item.getString('merchant_id') ?? '', name: item.getString('name') ?? '', description: item.getString('description') ?? '', images: images, price: safePrice, original_price: safeOriginalPrice, stock: safeStock, sales: safeSales, status: item.getNumber('status') ?? 1, created_at: item.getString('created_at') ?? '', base_price: safePrice, market_price: safeOriginalPrice, main_image_url: images.length > 0 ? images[0] : '', sale_count: safeSales, total_stock: safeStock } as Product mappedData.push(product) } return { data: mappedData, total: res2.total ?? 0, page, limit, hasmore: res2.hasmore ?? false } } console.log(`Shop products found: ${(response.data as any[]).length}`) return { data: response.data as Product[], total: response.total ?? 0, page, limit, hasmore: response.hasmore ?? false } } catch (error) { console.error('获取店铺商品异常:', error) return { data: [] as Product[], total: 0, page, limit, hasmore: false } } } // 获取热销商品(按销量排序) async getHotProducts(limit: number = 10): Promise { try { console.log('[getHotProducts] 开始获取热销商品...') // 在数据库层面过滤 status,获取更多数据以便手动过滤 is_hot const response = await supa .from('ml_products_detail_view') .select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot') .eq('status', '1') // 使用字符串 '1' .order('sale_count', { ascending: false }) .limit(limit * 5) // 获取更多数据以便过滤 .execute() if (response.error != null) { console.error('获取热销商品失败:', response.error) return [] } const rawData = response.data console.log('[getHotProducts] 原始数据条数:', rawData != null ? (rawData as any[]).length : 0) if (rawData == null) { console.log('[getHotProducts] 数据为空') return [] } const products: Product[] = [] const rawList = rawData as any[] for (let i: number = 0; i < rawList.length; i++) { const item = rawList[i] const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject // 手动过滤 is_hot const rawIsHot = prodObj.get('is_hot') let isHotBool: boolean = false if (typeof rawIsHot == 'boolean') { isHotBool = rawIsHot as boolean } else if (typeof rawIsHot == 'number') { isHotBool = (rawIsHot as number) == 1 } if (!isHotBool) continue products.push(parseProductFromRaw(item)) // 达到目标数量就停止 if (products.length >= limit) break } console.log('[getHotProducts] 最终返回商品数:', products.length) return products } catch (error) { console.error('获取热销商品异常:', error) return [] } } // 获取按销量排序的商品(所有商品,不限制 is_hot) async getProductsBySales(limit: number = 10): Promise { try { console.log('[getProductsBySales] 开始获取销量排序商品...') const response = await supa .from('ml_products_detail_view') .select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot') .eq('status', '1') .order('sale_count', { ascending: false }) .limit(limit) .execute() if (response.error != null) { console.error('获取销量排序商品失败:', response.error) return [] } const rawData = response.data if (rawData == null) { return [] } const products: Product[] = [] const rawList = rawData as any[] for (let i: number = 0; i < rawList.length; i++) { const item = rawList[i] products.push(parseProductFromRaw(item)) } console.log('[getProductsBySales] 返回商品数:', products.length) return products } 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('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot') .eq('status', '1') // 在数据库层面过滤 .order('base_price', { ascending }) .limit(limit) .execute() if (response.error != null) { console.error('获取价格排序商品失败:', response.error) return [] } const rawData = response.data if (rawData == null) { return [] } const products: Product[] = [] const rawList = rawData as any[] for (let i: number = 0; i < rawList.length; i++) { const item = rawList[i] products.push(parseProductFromRaw(item)) } return products } catch (error) { console.error('获取价格排序商品异常:', error) return [] } } // 获取新品(按创建时间排序,最新的在前) async getProductsByNewest(limit: number = 10): Promise { try { console.log('[getProductsByNewest] 开始获取新品...') const response = await supa .from('ml_products_detail_view') .select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot') .eq('status', '1') .order('created_at', { ascending: false }) .limit(limit * 5) .execute() if (response.error != null) { console.error('获取新品失败:', response.error) return [] } const rawData = response.data if (rawData == null) { return [] } const products: Product[] = [] const rawList = rawData as any[] for (let i: number = 0; i < rawList.length; i++) { const item = rawList[i] const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject // 手动过滤 is_new const rawIsNew = prodObj.get('is_new') let isNewBool: boolean = false if (typeof rawIsNew == 'boolean') { isNewBool = rawIsNew as boolean } else if (typeof rawIsNew == 'number') { isNewBool = (rawIsNew as number) == 1 } if (!isNewBool) continue products.push(parseProductFromRaw(item)) if (products.length >= limit) break } // 如果 is_new 商品不足,补充普通商品 if (products.length < limit) { console.log('[getProductsByNewest] is_new商品不足,补充普通商品') const addedIds = new Set() for (let i = 0; i < products.length; i++) { addedIds.add(products[i].id) } for (let i: number = 0; i < rawList.length; i++) { const item = rawList[i] const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const rawId = prodObj.get('id') const itemId = (typeof rawId == 'string') ? (rawId as string) : '' if (!addedIds.has(itemId)) { products.push(parseProductFromRaw(item)) addedIds.add(itemId) if (products.length >= limit) break } } } console.log('[getProductsByNewest] 返回商品数:', products.length) return products } catch (error) { console.error('获取新品异常:', error) return [] } } // 获取推荐商品(is_featured=true) async getRecommendedProducts(limit: number = 10): Promise { try { console.log('[getRecommendedProducts] 开始获取推荐商品...') const response = await supa .from('ml_products_detail_view') .select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot') .eq('status', '1') // 在数据库层面过滤 .order('sale_count', { ascending: false }) .limit(limit * 5) // 获取更多数据以便过滤 is_featured .execute() console.log('[getRecommendedProducts] 查询完成') if (response.error != null) { console.error('获取推荐商品失败:', response.error) return [] } const rawData = response.data if (rawData == null) { console.log('[getRecommendedProducts] 数据为空') return [] } const products: Product[] = [] const rawList = rawData as any[] console.log('[getRecommendedProducts] 数据条数:', rawList.length) for (let i: number = 0; i < rawList.length; i++) { const item = rawList[i] const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const rawFeatured = prodObj.get('is_featured') let isFeaturedBool: boolean = false if (typeof rawFeatured == 'boolean') { isFeaturedBool = rawFeatured as boolean } else if (typeof rawFeatured == 'number') { isFeaturedBool = (rawFeatured as number) == 1 } if (!isFeaturedBool) { continue } products.push(parseProductFromRaw(item)) if (products.length >= limit) break } return products } 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 [] as Product[] // 暂无特价字段 } // 获取当前用户的购物车商品(关联商品和店铺信息) async getCartItems(): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { 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 != null) { console.error('获取购物车失败:', response.error) return [] } const cartData = response.data as any[] // console.log('Raw Cart Data:', JSON.stringify(cartData)) if (cartData == null || 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 { const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject pid = itemObj.getString('product_id') ?? '' sid = itemObj.getString('sku_id') ?? '' } 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) { // Convert string array to any array for .in() const productIdsAny: any[] = [] for(let i=0; i() if (skuIds.length > 0) { const skuIdsAny: any[] = [] for(let i=0; i 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 sObj = JSON.parse(JSON.stringify(sku)) as UTSJSONObject const skuPrice = sObj.getNumber('price') ?? 0 if (skuPrice > 0) productPrice = skuPrice const skuImg = sObj.getString('image_url') ?? '' if (skuImg !== '') productImage = skuImg const specRaw = sObj.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) {} } } } } let shopIdStr = merchantId != '' ? 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 } as CartItem) } } return cartItems } catch (error) { console.error('获取购物车异常:', error) return [] } } // 获取用户通知 (系统、活动、订单) async getUserNotifications(type: string | null = null): Promise { try { console.log('[getUserNotifications] 开始获取通知') const userId = this.getCurrentUserId() if (userId == null) return [] let query = supa .from('ml_notifications') .select('*') .eq('user_id', userId) if (type != null) { query = query.eq('type', type) } const response = await query.order('created_at', { ascending: false }).execute() if (response.error != null) { console.error('获取通知失败:', response.error) return [] } const rawData = response.data if (rawData == null) return [] const notifications: Notification[] = [] const rawList = rawData as any[] console.log('[getUserNotifications] 获取到通知数量:', rawList.length) for (let i = 0; i < rawList.length; i++) { const item = rawList[i] const noteObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const getSafeString = (key: string): string => { const val = noteObj.get(key) if (val == null) return '' if (typeof val == 'string') return val return '' } const getSafeNumber = (key: string): number => { const val = noteObj.get(key) if (val == null) return 0 if (typeof val == 'number') return val return 0 } const getSafeBoolean = (key: string): boolean => { const val = noteObj.get(key) if (val == null) return false if (typeof val == 'boolean') return val if (typeof val == 'number') return (val as number) == 1 return false } const note: Notification = { id: getSafeString('id'), user_id: getSafeString('user_id'), type: getSafeString('type'), title: getSafeString('title'), content: getSafeString('content'), is_read: getSafeBoolean('is_read'), icon_url: getSafeString('icon_url'), link_url: getSafeString('link_url'), extra_data: getSafeString('extra_data'), created_at: getSafeString('created_at') } notifications.push(note) } return notifications } catch (e) { console.error('获取通知异常:', e) return [] } } // 获取聊天会话列表 async getChatRooms(): Promise { try { console.log('[getChatRooms] 开始获取聊天会话') const userId = this.getCurrentUserId() if (userId == null) return [] const response = await supa .from('ml_chat_rooms') .select('*') .eq('user_id', userId) .order('updated_at', { ascending: false }) .execute() if (response.error != null) { console.error('获取聊天会话失败:', response.error) return [] } const rawData = response.data if (rawData == null) return [] const rooms: ChatRoom[] = [] const rawList = rawData as any[] console.log('[getChatRooms] 获取到会话数量:', rawList.length) for (let i = 0; i < rawList.length; i++) { const item = rawList[i] const roomObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const getSafeString = (key: string): string => { const val = roomObj.get(key) if (val == null) return '' if (typeof val == 'string') return val return '' } const getSafeNumber = (key: string): number => { const val = roomObj.get(key) if (val == null) return 0 if (typeof val == 'number') return val return 0 } const getSafeBoolean = (key: string): boolean => { const val = roomObj.get(key) if (val == null) return false if (typeof val == 'boolean') return val if (typeof val == 'number') return (val as number) == 1 return false } const room: ChatRoom = { id: getSafeString('id'), user_id: getSafeString('user_id'), merchant_id: getSafeString('merchant_id'), shop_name: getSafeString('shop_name'), shop_logo: getSafeString('shop_logo'), last_message: getSafeString('last_message'), last_message_at: getSafeString('last_message_at'), unread_count: getSafeNumber('unread_count'), is_top: getSafeBoolean('is_top'), created_at: getSafeString('created_at'), updated_at: getSafeString('updated_at') } rooms.push(room) } return rooms } catch (e) { console.error('获取聊天会话异常:', e) return [] } } // 获取用户聊天消息 async getUserChatMessages(): Promise { try { const userId = this.getCurrentUserId() if (userId == null) 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 != null) { console.error('获取聊天记录失败:', response.error) return [] } return response.data as ChatMessage[] } catch (e) { console.error('获取聊天记录异常:', e) return [] } } // 获取与特定商家的聊天记录 async getChatMessages(merchantId: string): Promise { try { console.log('[getChatMessages] 开始获取聊天记录,merchantId:', merchantId) const userId = this.getCurrentUserId() if (userId == null) return [] const response = await supa .from('ml_chat_messages') .select('*') .or(`and(sender_id.eq.${userId},receiver_id.eq.${merchantId}),and(sender_id.eq.${merchantId},receiver_id.eq.${userId})`) .order('created_at', { ascending: false }) .limit(50) .execute() if (response.error != null) { console.error('获取聊天记录失败:', response.error) return [] } const rawData = response.data if (rawData == null) return [] const messages: ChatMessage[] = [] const rawList = rawData as any[] console.log('[getChatMessages] 获取到消息数量:', rawList.length) for (let i = 0; i < rawList.length; i++) { const item = rawList[i] const msgObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const getSafeString = (key: string): string => { const val = msgObj.get(key) if (val == null) return '' if (typeof val == 'string') return val return '' } const getSafeBoolean = (key: string): boolean => { const val = msgObj.get(key) if (val == null) return false if (typeof val == 'boolean') return val if (typeof val == 'number') return (val as number) == 1 return false } const msg: ChatMessage = { id: getSafeString('id'), session_id: getSafeString('session_id'), sender_id: getSafeString('sender_id'), receiver_id: getSafeString('receiver_id'), content: getSafeString('content'), msg_type: getSafeString('msg_type'), is_read: getSafeBoolean('is_read'), is_from_user: getSafeBoolean('is_from_user'), extra_data: getSafeString('extra_data'), created_at: getSafeString('created_at') } messages.push(msg) } return messages } catch (e) { console.error('获取聊天记录异常:', e) return [] } } // 发送聊天消息 async sendChatMessage(content: string, toId: string | null = null, type: string = 'text'): Promise { try { const userId = this.getCurrentUserId() if (userId == null) return false const payload = { sender_id: userId, content: content, msg_type: type, is_from_user: true, created_at: new Date().toISOString() } as UTSJSONObject if (toId != null) { payload.set('receiver_id', toId) } const response = await supa .from('ml_chat_messages') .insert(payload) .execute() if (response.error != null) { console.error('发送消息失败:', response.error) return false } return true } catch (e) { console.error('发送消息异常:', e) return false } } // 模拟客服回复 async simulateServiceReply(content: string): Promise { try { const userId = this.getCurrentUserId() if (userId == null) return false const response = await supa .from('ml_chat_messages') .insert({ receiver_id: userId, content: content, msg_type: 'text', is_from_user: false, created_at: new Date().toISOString() }) .execute() return response.error == null } catch (e) { return false } } // 添加商品到购物车 async addToCart(productId: string, quantity: number = 1, skuId: string = '', merchantId: string = ''): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { console.error('用户未登录,无法添加商品到购物车') return false } const realSkuId = (skuId != null && skuId.length > 0) ? skuId : null const realMerchantId = (merchantId != null && merchantId.length > 0) ? merchantId : null // 检查商品是否已在购物车中 // 注意:必须处理 sku_id 为空的情况,使用 is.null 过滤器 let query = supa .from('ml_shopping_cart') .select('*') .eq('user_id', userId) .eq('product_id', productId) if (realSkuId != null) { query = query.eq('sku_id', realSkuId) } 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: AkReqResponse if (existingItem != null) { // 商品已存在,更新数量 console.log('Found existing cart item:', JSON.stringify(existingItem)) // 确保 existingItem.id 存在 let itemId: string | null = null let itemQty: any | null = null if (existingItem instanceof UTSJSONObject) { itemId = existingItem.getString('id') itemQty = existingItem.getNumber('quantity') } else { const obj = JSON.parse(JSON.stringify(existingItem)) as UTSJSONObject itemId = obj.getString('id') itemQty = obj.getNumber('quantity') } if (itemId != null) { let currentQty = 0 if (typeof itemQty === 'number') { currentQty = itemQty as number } else { const qStr = `${itemQty ?? 0}` currentQty = parseInt(qStr) } const newQty = currentQty + quantity response = await supa .from('ml_shopping_cart') .update({ quantity: newQty, merchant_id: realMerchantId, updated_at: new Date().toISOString() }) .eq('id', itemId) .execute() } else { console.error('购物车已有商品但缺少ID,无法更新. Data:', JSON.stringify(existingItem)) return false } } else { // 商品不存在,添加新记录 const cartPayload = new UTSJSONObject() cartPayload.set('user_id', userId) cartPayload.set('product_id', productId) cartPayload.set('sku_id', realSkuId) cartPayload.set('quantity', quantity) cartPayload.set('selected', true) cartPayload.set('created_at', new Date().toISOString()) cartPayload.set('updated_at', new Date().toISOString()) if (realMerchantId != null) { cartPayload.set('merchant_id', realMerchantId) } response = await supa .from('ml_shopping_cart') .insert(cartPayload) .execute() } if (response.error != null) { 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 == null) { 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 != null) { 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 == null) { 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 != null) { 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 == null) { 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 as any[]) .execute() if (response.error != null) { 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 == null) { console.error('用户未登录,无法删除购物车商品') return false } const response = await supa .from('ml_shopping_cart') .eq('id', cartItemId) .eq('user_id', userId) .delete() .execute() if (response.error != null) { console.error('删除购物车商品失败:', response.error) return false } return true } catch (error) { console.error('删除购物车商品异常:', error) return false } } // 批量删除购物车商品 async batchDeleteCartItems(cartItemIds: string[]): Promise { try { console.log('[batchDeleteCartItems] 开始删除, ids:', cartItemIds.length) const userId = this.getCurrentUserId() if (userId == null) { console.error('用户未登录,无法删除购物车商品') return false } console.log('[batchDeleteCartItems] userId:', userId) const response = await supa .from('ml_shopping_cart') .eq('user_id', userId) .in('id', cartItemIds as any[]) .delete() .execute() console.log('[batchDeleteCartItems] response.error:', response.error) if (response.error != null) { console.error('批量删除购物车商品失败:', response.error) return false } console.log('[batchDeleteCartItems] 删除成功') return true } catch (error) { console.error('批量删除购物车商品异常:', error) return false } } // 清空购物车 async clearCart(): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { console.error('用户未登录,无法清空购物车') return false } const response = await supa .from('ml_shopping_cart') .eq('user_id', userId) .delete() .execute() if (response.error != null) { console.error('清空购物车失败:', response.error) return false } return true } catch (error) { console.error('清空购物车异常:', error) return false } } // 获取当前用户的所有地址 async getAddresses(): Promise { const userId = this.getCurrentUserId() if (userId == null) { console.warn('[getAddresses] 用户未登录,无法获取地址') return [] } try { console.log('[getAddresses] 查询地址, userId:', userId) const response = await supa .from('ml_user_addresses') .select('*') .eq('user_id', userId) .order('is_default', { ascending: false }) .order('created_at', { ascending: false }) .execute() console.log('[getAddresses] response.error:', response.error) console.log('[getAddresses] response.data:', JSON.stringify(response.data)) if (response.error != null) { console.error('[getAddresses] 获取地址失败:', response.error) return [] } const data = response.data if (data == null) { return [] } const result: UserAddress[] = [] const rawData = data as any[] for (let i = 0; i < rawData.length; i++) { const item = rawData[i] let itemObj: UTSJSONObject if (item instanceof UTSJSONObject) { itemObj = item as UTSJSONObject } else { itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject } const addr: UserAddress = { id: itemObj.getString('id') ?? '', user_id: itemObj.getString('user_id') ?? '', recipient_name: itemObj.getString('receiver_name') ?? itemObj.getString('recipient_name') ?? '', phone: itemObj.getString('receiver_phone') ?? itemObj.getString('phone') ?? '', province: itemObj.getString('province') ?? '', city: itemObj.getString('city') ?? '', district: itemObj.getString('district') ?? '', detail_address: itemObj.getString('address_detail') ?? itemObj.getString('detail_address') ?? '', is_default: itemObj.getBoolean('is_default') ?? false, label: itemObj.getString('label') ?? '', created_at: itemObj.getString('created_at') ?? '', updated_at: itemObj.getString('updated_at') ?? '' } result.push(addr) } console.log('[getAddresses] 返回地址数量:', result.length) return result } catch (error) { console.error('[getAddresses] 获取地址异常:', error) return [] } } // 根据ID获取地址详情 async getAddressById(addressId: string): Promise { const userId = this.getCurrentUserId() if (userId == null) { console.warn('用户未登录,无法获取地址') return null } try { const query = 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() const response = await query.execute() if (response.error != null) { console.error('获取地址详情失败:', response.error) return null } return response.data as UserAddress } catch (error) { console.error('获取地址详情异常:', error) return null } } // 添加新地址 async addAddress(address: AddAddressParams): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { console.error('用户未登录,无法添加地址') return false } // 如果设置为默认地址,需要先取消其他默认地址 if (address.is_default == true) { 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 != null) { console.error('添加地址失败:', response.error) return false } return true } catch (error) { console.error('添加地址异常:', error) return false } } // 更新地址 async updateAddress(addressId: string, address: UpdateAddressParams): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { console.error('用户未登录,无法更新地址') return false } // 如果设置为默认地址,需要先取消其他默认地址 if (address.is_default == true) { 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 if (address.label != null) updateData['label'] = address.label 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 != null) { console.error('更新地址失败:', response.error) return false } return true } catch (error) { console.error('更新地址异常:', error) return false } } // 确认收货 async confirmReceipt(orderId: string): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { return { success: false, error: '用户未登录' } } const response = await supa .from('ml_orders') .update({ order_status: 4, // 4: 已完成 delivered_at: new Date().toISOString(), completed_at: new Date().toISOString(), // 也更新完成时间 updated_at: new Date().toISOString() }) .eq('id', orderId) .eq('user_id', userId) .execute() if (response.error != null) { return { success: false, error: response.error.message } } return { success: true } } catch (e: any) { return { success: false, error: e.message } } } // 删除地址 async deleteAddress(addressId: string): Promise { try { console.log('正在执行删除地址,ID:', addressId) const userId = this.getCurrentUserId() if (userId == null) { console.error('用户未登录,无法删除地址') return false } const response = await supa .from('ml_user_addresses') .eq('id', addressId) .eq('user_id', userId) .delete() .execute() if (response.error != null) { 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 == null) return null // 联合查询 auth user 和 profile // 由于 Supabase auth table 不可直接访问,这里查询 ml_user_profiles // 注意:使用 limit(1) 替代 single() 以避免 Android 端类型转换错误 const response = await supa .from('ml_user_profiles') .select('*') .eq('user_id', userId) .limit(1) .execute() if (response.error != null) { // 如果不存在 profile,可能只有 auth user,这里暂时返回空或创建默认 return null } const rawData = response.data if (rawData == null) return null // 作为数组处理 const rawList = rawData as any[] if (rawList.length == 0) return null return rawList[0] } catch (e) { return null } } // 创建订单 async createOrder(orderData: CreateOrderParams): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { console.error('CreateOrder: User not logged in') return null } const orderNo = 'ML' + Date.now() + Math.floor(Math.random() * 1000) let merchantId = orderData.merchant_id console.log('[CreateOrder] 原始 merchant_id:', merchantId) if (merchantId == null || merchantId == '' || merchantId == 'unknown') { console.warn('[CreateOrder] merchant_id 为空或无效,将使用 userId 作为 fallback') merchantId = userId } console.log('[CreateOrder] 最终使用的 merchant_id:', merchantId) let shippingAddrStr = '{}' if (orderData.shipping_address != null) { if (typeof orderData.shipping_address === 'string') { shippingAddrStr = orderData.shipping_address } else { shippingAddrStr = JSON.stringify(orderData.shipping_address) } } const orderPayload = new UTSJSONObject() orderPayload.set('user_id', userId) orderPayload.set('merchant_id', merchantId) orderPayload.set('order_no', orderNo) orderPayload.set('product_amount', orderData.product_amount) orderPayload.set('shipping_fee', orderData.shipping_fee) orderPayload.set('total_amount', orderData.total_amount) orderPayload.set('paid_amount', 0) orderPayload.set('shipping_address', shippingAddrStr) orderPayload.set('order_status', 1) orderPayload.set('payment_status', 1) orderPayload.set('shipping_status', 1) orderPayload.set('created_at', new Date().toISOString()) orderPayload.set('updated_at', new Date().toISOString()) console.log('[CreateOrder] 插入订单数据:', JSON.stringify(orderPayload)) console.log('[CreateOrder] 期望的订单号:', orderNo) const orderResponse = await supa .from('ml_orders') .insert(orderPayload) .execute() console.log('[CreateOrder] insert 完成') console.log('[CreateOrder] orderResponse.error:', orderResponse.error) if (orderResponse.error != null) { console.error('[CreateOrder] 创建订单失败:', orderResponse.error) return null } console.log('[CreateOrder] 开始查询新创建的订单, order_no:', orderNo) const queryResponse = await supa .from('ml_orders') .select('id, order_no') .eq('order_no', orderNo) .execute() console.log('[CreateOrder] queryResponse.error:', queryResponse.error) console.log('[CreateOrder] queryResponse.data:', JSON.stringify(queryResponse.data)) if (queryResponse.error != null) { console.error('[CreateOrder] 查询订单失败:', queryResponse.error) return null } const queryData = queryResponse.data as any let orderId = '' console.log('[CreateOrder] queryData 类型:', typeof queryData, '是否数组:', Array.isArray(queryData)) if (Array.isArray(queryData) && queryData.length > 0) { console.log('[CreateOrder] queryData 长度:', queryData.length) const firstItemRaw = queryData[0] console.log('[CreateOrder] firstItemRaw 类型:', typeof firstItemRaw) // 将 firstItemRaw 转换为可访问的对象 const firstItemStr = JSON.stringify(firstItemRaw) const firstItemParsed = JSON.parse(firstItemStr) if (firstItemParsed == null) { console.error('[CreateOrder] 解析订单数据失败') return null } const firstItem = firstItemParsed as UTSJSONObject orderId = (firstItem.getString('id') ?? '') as string console.log('[CreateOrder] 找到新创建的订单, id:', orderId) } else { console.error('[CreateOrder] 未找到新创建的订单,插入可能失败') return null } console.log('[CreateOrder] 订单创建成功, orderId:', orderId) const orderItems: UTSJSONObject[] = [] console.log('[CreateOrder] orderData.items 类型:', typeof orderData.items, '是否数组:', Array.isArray(orderData.items)) const rawItems = orderData.items as any[] console.log('[CreateOrder] rawItems 长度:', rawItems.length) for(let i = 0; i < rawItems.length; i++) { console.log('[CreateOrder] 处理商品项', i, '类型:', typeof rawItems[i]) const rawItem = rawItems[i] const itemStr = JSON.stringify(rawItem) console.log('[CreateOrder] 商品项 JSON:', itemStr) const itemParsed = JSON.parse(itemStr) if (itemParsed == null) { console.error('[CreateOrder] 商品项解析失败') continue } const item = itemParsed as UTSJSONObject const itemJson = new UTSJSONObject() let pId = item.get('product_id') if (pId == null) { pId = item.get('id') } const productId = (pId ?? '') as string itemJson.set('order_id', orderId) itemJson.set('product_id', productId) const skuIdVal = item.get('sku_id') if (skuIdVal != null && skuIdVal !== '') { itemJson.set('sku_id', skuIdVal as string) } const productName = (item.get('product_name') ?? '') as string itemJson.set('product_name', productName) const sName = item.get('sku_name') itemJson.set('sku_name', (sName ?? '') as string) const specVal = item.get('specifications') let skuSnapshot = '{}' if (specVal != null) { if (typeof specVal === 'string') { skuSnapshot = specVal as string } else { skuSnapshot = JSON.stringify(specVal) } } itemJson.set('sku_snapshot', skuSnapshot) itemJson.set('specifications', skuSnapshot) const img1 = item.get('product_image') const img2 = item.get('image_url') let imgUrl = ((img1 ?? img2 ?? '') as string) while (imgUrl.indexOf('`') >= 0) { imgUrl = imgUrl.replace('`', '') } itemJson.set('image_url', imgUrl) const iPrice = item.getNumber('price') ?? 0 const iQty = item.getNumber('quantity') ?? 1 itemJson.set('price', iPrice) itemJson.set('quantity', iQty) itemJson.set('total_amount', iPrice * iQty) itemJson.set('created_at', new Date().toISOString()) orderItems.push(itemJson) } console.log('[CreateOrder] 插入订单项数量:', orderItems.length) for (let j: number = 0; j < orderItems.length; j++) { console.log('[CreateOrder] 开始插入订单项', j) const itemJson = orderItems[j] // 将 UTSJSONObject 转换为普通对象 console.log('[CreateOrder] 序列化订单项...') const itemObjStr = JSON.stringify(itemJson) console.log('[CreateOrder] 订单项 JSON:', itemObjStr) const itemObjParsed = JSON.parse(itemObjStr) console.log('[CreateOrder] itemObjParsed 类型:', typeof itemObjParsed) if (itemObjParsed == null) { console.error('[CreateOrder] 订单项转换失败') continue } // 使用 UTSJSONObject 而不是 Record const itemObj = itemObjParsed as UTSJSONObject console.log('[CreateOrder] 执行 insert...') const itemsResponse = await supa .from('ml_order_items') .insert(itemObj) .execute() console.log('[CreateOrder] insert 完成, error:', itemsResponse.error) if (itemsResponse.error != null) { console.error('[CreateOrder] 创建订单项失败:', itemsResponse.error) } } console.log('[CreateOrder] 订单项创建成功') const cartItemIds: string[] = [] for(let i = 0; i < rawItems.length; i++) { const rawItem = rawItems[i] const itemParsed = JSON.parse(JSON.stringify(rawItem)) if (itemParsed == null) continue const item = itemParsed as UTSJSONObject const iid = item.getString('id') if (iid != null && iid.length > 10) { cartItemIds.push(iid) } } if (cartItemIds.length > 0) { await this.batchDeleteCartItems(cartItemIds) } return orderId } catch (error) { console.error('[CreateOrder] 创建订单异常:', error) return null } } // 批量通过店铺创建订单 async createOrdersByShop(params: ShopOrderParams): Promise { try { const orderIds: string[] = [] const groups = params.shopGroups as any[] let grandTotal = 0.0 for(let k = 0; k < groups.length; k++) { const g = JSON.parse(JSON.stringify(groups[k])) as UTSJSONObject // 安全获取 items 数组 const gItemsRaw = g.get('items') if (gItemsRaw == null) continue const gItems = gItemsRaw as any[] for(let gi = 0; gi < gItems.length; gi++) { const it = JSON.parse(JSON.stringify(gItems[gi])) as UTSJSONObject const itPrice = it.getNumber('price') ?? 0 const itQty = it.getNumber('quantity') ?? 1 grandTotal += itPrice * itQty } } // 为每个店铺创建一个订单 for (let i = 0; i < groups.length; i++) { const group = JSON.parse(JSON.stringify(groups[i])) as UTSJSONObject const shopItemsRaw = group.get('items') if (shopItemsRaw == null) continue const shopItems = shopItemsRaw as any[] let productAmount = 0.0 for(let j = 0; j < shopItems.length; j++) { const sItem = JSON.parse(JSON.stringify(shopItems[j])) as UTSJSONObject const siPrice = sItem.getNumber('price') ?? 0 const siQty = sItem.getNumber('quantity') ?? 1 productAmount += siPrice * siQty } // 简单平摊运费和优惠 (实际逻辑可能更复杂) const ratio = grandTotal > 0 ? (productAmount / grandTotal) : 0 const shopShippingFee = params.deliveryFee * ratio const shopDiscount = params.discountAmount * ratio const shopTotal = productAmount + shopShippingFee - shopDiscount const mId = group.getString('merchant_id') const sId = group.getString('shopId') const shopName = group.getString('shopName') console.log('[createOrdersByShop] 店铺组信息:', { merchant_id: mId, shopId: sId, shopName: shopName }) const finalMerchantId = (mId != null && mId != '') ? mId : (sId ?? '') console.log('[createOrdersByShop] 最终使用的 merchant_id:', finalMerchantId) // 将 shopItems 转换为普通对象数组 const plainItems: any[] = [] for(let k = 0; k < shopItems.length; k++) { const plainItemRaw = JSON.parse(JSON.stringify(shopItems[k])) if (plainItemRaw != null) { plainItems.push(plainItemRaw as any) } } console.log('[createOrdersByShop] plainItems 数量:', plainItems.length) const orderId = await this.createOrder({ merchant_id: finalMerchantId, product_amount: productAmount, shipping_fee: shopShippingFee, total_amount: shopTotal, shipping_address: params.shipping_address, items: plainItems }) if (orderId != null) { orderIds.push(orderId) } else { return { success: false, orderIds, error: `店铺 ${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 == null) { const empty: any[] = [] return empty } // 关联查询店铺表获取店铺名称 let query = supa .from('ml_orders') .select('*, ml_order_items(*), ml_shops(shop_name)') .eq('user_id', userId) .order('created_at', { ascending: false }) if (status > 0) { query = query.eq('order_status', status) } const response = await query.execute() console.log('[getOrders] response.error:', response.error) if (response.data != null && Array.isArray(response.data)) { console.log('[getOrders] 订单数量:', response.data.length) } if (response.error != null) { console.error('获取订单列表失败:', response.error) const empty: any[] = [] return empty } const data = response.data if (data == null) { const empty: any[] = [] return empty } return data as any[] } catch (error) { console.error('获取订单列表异常:', error) const empty: any[] = [] return empty } } // 获取订单详情 async getOrderDetail(orderId: string): Promise { try { console.log('[getOrderDetail] 开始获取订单详情,orderId:', orderId) const userId = this.getCurrentUserId() if (userId == null) return null const response = await supa .from('ml_orders') .select('*, ml_order_items(*)') .eq('id', orderId) .eq('user_id', userId!) .limit(1) .execute() console.log('[getOrderDetail] response.error:', response.error) console.log('[getOrderDetail] response.data:', JSON.stringify(response.data)) if (response.error != null) { console.error('[getOrderDetail] 获取订单详情失败:', response.error) return null } const rawData = response.data if (rawData == null) { console.log('[getOrderDetail] 数据为空') return null } const rawList = rawData as any[] if (rawList.length == 0) { console.log('[getOrderDetail] 未找到订单') return null } const orderData = rawList[0] console.log('[getOrderDetail] 成功获取订单') const orderObj = JSON.parse(JSON.stringify(orderData)) as UTSJSONObject const result = new UTSJSONObject() result.set('id', orderObj.get('id') ?? '') result.set('order_no', orderObj.get('order_no') ?? '') result.set('order_status', orderObj.get('order_status') ?? 1) result.set('user_id', orderObj.get('user_id') ?? '') result.set('merchant_id', orderObj.get('merchant_id') ?? '') result.set('product_amount', orderObj.get('product_amount') ?? 0) result.set('shipping_fee', orderObj.get('shipping_fee') ?? 0) result.set('total_amount', orderObj.get('total_amount') ?? 0) result.set('paid_amount', orderObj.get('paid_amount') ?? 0) result.set('discount_amount', orderObj.get('discount_amount') ?? 0) result.set('payment_method', orderObj.get('payment_method') ?? '') result.set('payment_status', orderObj.get('payment_status') ?? 1) result.set('shipping_status', orderObj.get('shipping_status') ?? 1) result.set('created_at', orderObj.get('created_at') ?? '') result.set('paid_at', orderObj.get('paid_at') ?? '') result.set('shipped_at', orderObj.get('shipped_at') ?? '') result.set('completed_at', orderObj.get('completed_at') ?? '') result.set('shipping_address', orderObj.get('shipping_address')) result.set('ml_order_items', orderObj.get('ml_order_items')) // 添加物流信息 result.set('tracking_no', orderObj.get('tracking_no') ?? '') result.set('carrier_name', orderObj.get('carrier_name') ?? '') result.set('delivered_at', orderObj.get('delivered_at') ?? '') return result } catch (e) { console.error('[getOrderDetail] 获取订单详情异常:', e) return null } } // 支付订单 async payOrder(orderId: string, paymentMethod: string, amount: number): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { console.error('[payOrder] 用户未登录') return false } console.log('[payOrder] 开始更新订单状态, orderId:', orderId, 'userId:', userId) const updatePayload = new UTSJSONObject() updatePayload.set('order_status', 2) updatePayload.set('payment_status', 1) updatePayload.set('payment_method', paymentMethod) updatePayload.set('payment_time', new Date().toISOString()) updatePayload.set('paid_amount', amount) updatePayload.set('updated_at', new Date().toISOString()) console.log('[payOrder] 更新数据:', JSON.stringify(updatePayload)) const response = await supa .from('ml_orders') .update(updatePayload) .eq('id', orderId) .eq('user_id', userId) .execute() if (response.error != null) { console.error('[payOrder] 更新订单失败:', response.error) return false } console.log('[payOrder] 订单状态更新成功') if (paymentMethod === 'balance') { console.log('[payOrder] 余额支付,暂不扣减余额') } return true } catch (e) { console.error('[payOrder] 支付异常:', e) return false } } // 根据ID获取订单信息 async getOrderById(orderId: string): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { console.error('[getOrderById] 用户未登录') return null } console.log('[getOrderById] 查询订单, orderId:', orderId) const response = await supa .from('ml_orders') .select('*') .eq('id', orderId) .eq('user_id', userId) .execute() if (response.error != null) { console.error('[getOrderById] 查询订单失败:', response.error) return null } const data = response.data as any[] if (data == null || data.length === 0) { console.log('[getOrderById] 未找到订单') return null } const orderRaw = data[0] let orderObj: UTSJSONObject if (orderRaw instanceof UTSJSONObject) { orderObj = orderRaw as UTSJSONObject } else { orderObj = JSON.parse(JSON.stringify(orderRaw)) as UTSJSONObject } console.log('[getOrderById] 订单数据:', JSON.stringify(orderObj)) return orderObj } catch (e) { console.error('[getOrderById] 查询异常:', e) return null } } // 提交售后申请 async createRefund(data: any): Promise { try { const userId = this.getCurrentUserId() if (userId == null) return { success: false, message: '请先登录' } const d = JSON.parse(JSON.stringify(data)) as UTSJSONObject const orderId = d.getString('order_id') const refundType = d.getNumber('refund_type') const refundReason = d.getString('refund_reason') const refundAmount = d.getNumber('refund_amount') const description = d.getString('description') const images = d.getArray('images') const payload = { user_id: userId, order_id: orderId, refund_no: 'REF' + Date.now() + Math.floor(Math.random() * 1000), refund_type: refundType, refund_reason: refundReason, refund_amount: refundAmount, description: description ?? '', images: images ?? ([] as any[]), status: 1 // Pending } const response = await supa .from('ml_refunds') .insert(payload) .execute() if (response.error != null) { console.error('提交售后失败:', response.error) return { success: false, message: '提交失败: ' + (response.error.message ?? '未知错误') } } return { success: true, message: '申请提交成功' } } catch (e) { console.error('提交售后异常:', e) return { success: false, message: '系统异常' } } } // 再次购买 async rePurchase(order: any): Promise { try { // 将 order 转换为 UTSJSONObject 以安全访问属性 const orderObj = JSON.parse(JSON.stringify(order)) as UTSJSONObject // 尝试获取 ml_order_items 或 items let itemsKey = 'ml_order_items' let itemsRaw = orderObj.get(itemsKey) if (itemsRaw == null) { itemsKey = 'items' itemsRaw = orderObj.get(itemsKey) } if (itemsRaw == null) return false // 断言为数组 const items = itemsRaw as any[] if (items.length === 0) return false // 简单的循环添加,实际项目中可以优化为批量插入 for (let i = 0; i < items.length; i++) { // 同样,item 也是 UTSJSONObject 或支持访问的对象 const item = JSON.parse(JSON.stringify(items[i])) as UTSJSONObject const productId = item.getString('product_id') const skuId = item.getString('sku_id') // 数量可能是数字或字符串 const quantity = item.getNumber('quantity') ?? 1 if (productId != null) { await this.addToCart(productId, quantity, skuId ?? '', '') } } return true } catch (e) { console.error('rePurchase error', e) return false } } // 申请售后 (Legacy/Simple update) async applyRefund(orderId: string, reason: string): Promise { try { // 更新订单状态为 退款中 (6) const response = await supa .from('ml_orders') .update({ order_status: 6, cancel_reason: reason, updated_at: new Date().toISOString() }) .eq('id', orderId) .execute() return response.error === null } catch (e) { return false } } // 获取售后记录列表 async getRefunds(statusList: number[] = [], page: number = 1, pageSize: number = 10): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { const empty: any[] = [] return empty } let query = supa .from('ml_refunds') .select(` *, order:ml_orders!inner ( order_no, created_at, ml_order_items ( product_id, product_name, image_url ) ) `) .eq('user_id', userId) .order('created_at', { ascending: false }) if (statusList.length > 0) { // 显式转换为 any[] 以匹配 .in 方法的参数要求 const anyList = statusList as any[] query = query.in('status', anyList) } query = query.range((page - 1) * pageSize, page * pageSize - 1) const response = await query.execute() if (response.error != null) { console.error('获取售后列表失败:', response.error) const empty: any[] = [] return empty } const data = response.data if (data == null) { const empty: any[] = [] return empty } return data } catch (e) { console.error('获取售后列表异常:', e) const empty: any[] = [] return empty } } async deleteRefund(refundId: string): Promise { try { const response = await supa .from('ml_refunds') .delete() .eq('id', refundId) .execute() if (response.error != null) { console.error('删除退款记录失败:', response.error) return false } return true } catch (e) { console.error('删除退款记录异常:', e) return false } } async getUserBalance(): Promise { try { const userId = this.getCurrentUserId() console.log('[Supabase] getUserBalance userId:', userId) if (userId == null) return 0 // 优先查 ml_user_wallets const walletRes = await supa .from('ml_user_wallets') .select('balance') .eq('user_id', userId!) .single() .execute() if (walletRes.error != null) { console.error('[Supabase] getUserBalance error:', walletRes.error) } else { console.log('[Supabase] getUserBalance data:', walletRes.data) } if (walletRes.error == null && walletRes.data != null) { let data = walletRes.data // 如果是数组,取第一项 if (Array.isArray(data)) { const arr = data as any[] if (arr.length > 0) { data = arr[0] } } let val:number = 0 if (data instanceof UTSJSONObject) { val = data.getNumber('balance') ?? 0 // 尝试字符串转换,防止精度丢失导致转为string if (val === 0 && data.getString('balance') != null) { val = parseFloat(data.getString('balance')!) } return val } else { // 对于 Map 或 loose object const jsonObj = JSON.parse(JSON.stringify(data)) as UTSJSONObject val = jsonObj.getNumber('balance') ?? 0 if (val === 0 && jsonObj.getString('balance') != null) { val = parseFloat(jsonObj.getString('balance')!) } return val } } console.log('[Supabase] Wallet table empty, checking profile...') // Fallback to profile const profile = await this.getUserProfile() if (profile != null) { if (profile instanceof UTSJSONObject) { return profile.getNumber('balance') ?? 0 } else { const pObj = JSON.parse(JSON.stringify(profile)) as UTSJSONObject return pObj.getNumber('balance') ?? 0 } } return 0 } catch(e) { console.error('[Supabase] getUserBalance exception:', e) return 0 } } // 获取用户积分 async getUserPoints(): Promise { try { const userId = this.getCurrentUserId() console.log('[Supabase] getUserPoints userId:', userId) if (userId == null) return 0 // 查 ml_user_points const res = await supa .from('ml_user_points') .select('points') .eq('user_id', userId!) .single() .execute() if (res.error != null) { console.error('[Supabase] getUserPoints error:', res.error) } else { console.log('[Supabase] getUserPoints data:', res.data) } if (res.error == null && res.data != null) { let data = res.data // 如果是数组,取第一项 if (Array.isArray(data)) { const arr = data as any[] if (arr.length > 0) { data = arr[0] } } if (data instanceof UTSJSONObject) { return data.getNumber('points') ?? 0 } else { // 尝试转为 UTSJSONObject const jsonObj = JSON.parse(JSON.stringify(data)) as UTSJSONObject const val = jsonObj.getNumber('points') if (val != null) return val return 0 } } // Fallback check profile if needed const profile = await this.getUserProfile() if (profile != null) { if (profile instanceof UTSJSONObject) { return profile.getNumber('points') ?? 0 } else { const pObj = JSON.parse(JSON.stringify(profile)) as UTSJSONObject return pObj.getNumber('points') ?? 0 } } return 0 } catch (e) { console.error('[Supabase] getUserPoints exception:', e) return 0 } } // 获取钱包交易记录 async getTransactions(page: number = 1, limit: number = 20): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { const empty: any[] = [] return empty } const from = (page - 1) * limit const to = from + limit - 1 const response = await supa .from('ml_wallet_transactions') .select('*') .eq('user_id', userId!) .order('created_at', { ascending: false }) .range(from, to) .execute() if (response.error != null) { console.error('获取交易记录失败:', response.error) const empty: any[] = [] return empty } const data = response.data if (data == null) { const empty: any[] = [] return empty } return data as any[] } catch (e) { console.error('获取交易记录异常:', e) const empty: any[] = [] return empty } } // 获取积分记录 async getPointRecords(): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { const empty: any[] = [] return empty } const res = await supa .from('ml_point_records') .select('*') .eq('user_id', userId!) .order('created_at', { ascending: false }) .execute() if (res.error != null) { const empty: any[] = [] return empty } const data = res.data if (data == null) { const empty: any[] = [] return empty } return data as any[] } catch (e) { const empty: any[] = [] return empty } } // 获取用户红包 async getUserRedPackets(): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { const empty: any[] = [] return empty } const res = await supa .from('ml_user_red_packets') .select('*') .eq('user_id', userId!) .order('created_at', { ascending: false }) .execute() if (res.error != null) { console.error('获取红包失败:', res.error) const empty: any[] = [] return empty } const data = res.data if (data == null) { const empty: any[] = [] return empty } return data as any[] } catch (e) { console.error('获取红包异常:', e) const empty: any[] = [] return empty } } // 获取用户银行卡 async getUserBankCards(): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { const empty: any[] = [] return empty } const res = await supa .from('ml_user_bank_cards') .select('*') .eq('user_id', userId!) .order('created_at', { ascending: false }) .execute() if (res.error != null) { console.error('获取银行卡失败:', res.error) const empty: any[] = [] return empty } const data = res.data if (data == null) { const empty: any[] = [] return empty } return data as any[] } catch (e) { console.error('获取银行卡异常:', e) const empty: any[] = [] return empty } } // 余额充值 (调用 RPC) async rechargeBalance(amount: number): Promise { try { const userId = this.getCurrentUserId() if (userId == null) return false const res = await supa.rpc('recharge_wallet', { p_user_id: userId, p_amount: amount }) if (res.error != null) { console.error('充值失败RPC:', res.error) return false } // 简单判断: 如果没有error且data里success为true const data = res.data if (data instanceof UTSJSONObject) { return data.getBoolean('success') ?? false } // 如果返回不是对象,作为失败处理 return false } catch (e) { console.error('充值异常:', e) return false } } // 余额提现 (调用 RPC) async withdrawBalance(amount: number): Promise { try { const userId = this.getCurrentUserId() if (userId == null) return false const res = await supa.rpc('withdraw_wallet', { p_user_id: userId, p_amount: amount }) if (res.error != null) { console.error('提现失败RPC:', res.error) return false } const data = res.data if (data instanceof UTSJSONObject) { return data.getBoolean('success') ?? false } return false } catch (e) { console.error('提现异常:', e) return false } } // 添加银行卡 async addBankCard(card: UTSJSONObject): Promise { try { const userId = this.getCurrentUserId() if (userId == null) return false // 补全 user_id card.set('user_id', userId) const res = await supa .from('ml_user_bank_cards') .insert(card) .execute() if (res.error != null) { console.error('添加银行卡失败:', res.error) return false } return true } catch (e) { console.error('添加银行卡异常:', e) return false } } // 删除银行卡 async deleteBankCard(cardId: string): Promise { try { const userId = this.getCurrentUserId() if (userId == null) return false const res = await supa .from('ml_user_bank_cards') .eq('id', cardId) .eq('user_id', userId!) .delete() .execute() if (res.error != null) { console.error('删除银行卡失败:', res.error) return false } return true } catch (e) { console.error('删除银行卡异常:', e) return false } } // 收藏相关 async checkFavorite(productId: string): Promise { try { const userId = this.getCurrentUserId() console.log(`[CheckFav] Checking for User: ${userId}, Product: ${productId}`) if (userId == null) 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' .limit(1) .execute() // console.log(`[CheckFav] Response: ${JSON.stringify(response)}`) if (response.error != null) { console.error(`[CheckFav] Error: ${JSON.stringify(response.error)}`) return false } const data = response.data if (Array.isArray(data)) { if ((data as any[]).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 { const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject targetId = itemObj.getString('target_id') ?? '' } 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 == null) 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) { 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 != null) { console.error('取消收藏失败:', response.error) return true // 仍然是收藏状态 } return false // 已取消收藏 } else { 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 != null) { 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 == null) { const empty: any[] = [] return empty } // 第一步:查询收藏列表 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 != null) { const empty: any[] = [] return empty } const favorites = response.data as any[] if (favorites == null || favorites.length === 0) { const empty: any[] = [] return empty } // 第二步:收集商品ID const productIds: string[] = [] for (let i = 0; i < favorites.length; i++) { let item: any = favorites[i] let itemObj: UTSJSONObject if (item instanceof UTSJSONObject) { itemObj = item as UTSJSONObject } else { itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject } // target_id 可能是 Integer 或 String 类型,需要安全转换 const targetIdRaw = itemObj.get('target_id') let pid = '' if (targetIdRaw != null) { if (typeof targetIdRaw === 'string') { pid = targetIdRaw as string } else if (typeof targetIdRaw === 'number') { pid = (targetIdRaw as number).toString() } } if (pid !== '') productIds.push(pid) } if (productIds.length === 0) return [] // 第三步:批量查询商品详情 const anyProductIds = productIds as any[] const productRes = await supa .from('ml_products') .select('id, name, main_image_url, base_price, sale_count') .in('id', anyProductIds) .execute() if (productRes.error != null) { const empty: any[] = [] return empty } const products = productRes.data as any[] const productMap = new Map() for (let i = 0; i < products.length; i++) { // 显式声明类型为 any let p: any = products[i] let pid = '' if (p instanceof UTSJSONObject) { pid = p.getString('id') ?? '' } else { const pObj = JSON.parse(JSON.stringify(p)) as UTSJSONObject pid = pObj.getString('id') ?? '' } if (pid !== '') productMap.set(pid, p) } // 第四步:组合数据 const result: any[] = [] for (let i = 0; i < favorites.length; i++) { let item: any = favorites[i] let newItem: UTSJSONObject if (item instanceof UTSJSONObject) { newItem = JSON.parse(JSON.stringify(item)) as UTSJSONObject } else { newItem = JSON.parse(JSON.stringify(item)) as UTSJSONObject } // target_id 可能是 Integer 或 String 类型,需要安全转换 const targetIdRaw = newItem.get('target_id') let targetId = '' if (targetIdRaw != null) { if (typeof targetIdRaw === 'string') { targetId = targetIdRaw as string } else if (typeof targetIdRaw === 'number') { targetId = (targetIdRaw as number).toString() } } if (targetId !== '') { const product = productMap.get(targetId) if (product != null) { newItem.set('ml_products', product) result.push(newItem) } } } return result } catch (e) { console.error('获取收藏列表异常:', e) return [] } } // 获取足迹列表 async getFootprints(): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { console.log('[getFootprints] 用户未登录') const empty: any[] = [] return empty } console.log('[getFootprints] 查询足迹, userId:', userId) // 1. 获取足迹记录 const response = await supa .from('ml_user_footprints') .select('*') .eq('user_id', userId!) .order('updated_at', { ascending: false }) .limit(50) .execute() console.log('[getFootprints] 足迹查询 error:', response.error) console.log('[getFootprints] 足迹查询 data:', JSON.stringify(response.data)) if (response.error != null) { console.error('[getFootprints] 获取足迹失败:', response.error) const empty: any[] = [] return empty } const footprints = response.data as any[] if (footprints == null || footprints.length === 0) { console.log('[getFootprints] 没有足迹记录') const empty: any[] = [] return empty } console.log('[getFootprints] 足迹记录数量:', footprints.length) // 2. 收集商品ID const productIds: string[] = [] for (let i = 0; i < footprints.length; i++) { let item = footprints[i] let pid = '' if (item instanceof UTSJSONObject) { pid = item.getString('product_id') ?? '' } else { const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject pid = itemObj.getString('product_id') ?? '' } if (pid !== '' && !productIds.includes(pid)) productIds.push(pid) } if (productIds.length === 0) return [] const productIdsAny: any[] = [] for(let i=0; i() for(let i=0; i { try { const userId = this.getCurrentUserId() if (userId == null) { console.log('[addFootprint] 用户未登录') return false } console.log('[addFootprint] 添加足迹, userId:', userId, 'productId:', productId) // 检查是否已存在 const checkRes = await supa .from('ml_user_footprints') .select('id') .eq('user_id', userId!) .eq('product_id', productId) .execute() console.log('[addFootprint] 检查结果 error:', checkRes.error) console.log('[addFootprint] 检查结果 data:', JSON.stringify(checkRes.data)) const checkData = checkRes.data as any[] const exists = checkData != null && Array.isArray(checkData) && checkData.length > 0 if (checkRes.error == null && exists) { console.log('[addFootprint] 足迹已存在,更新时间') // 更新时间 const updateRes = await supa .from('ml_user_footprints') .update({ updated_at: new Date().toISOString() }) .eq('user_id', userId!) .eq('product_id', productId) .execute() console.log('[addFootprint] 更新结果 error:', updateRes.error) } else { console.log('[addFootprint] 足迹不存在,插入新记录') // 插入新记录 const insertPayload = new UTSJSONObject() insertPayload.set('user_id', userId!) insertPayload.set('product_id', productId) insertPayload.set('created_at', new Date().toISOString()) insertPayload.set('updated_at', new Date().toISOString()) const insertRes = await supa .from('ml_user_footprints') .insert(insertPayload) .execute() console.log('[addFootprint] 插入结果 error:', insertRes.error) } return true } catch (e) { console.error('[addFootprint] 添加足迹异常:', e) return false } } // 删除单个足迹 async deleteFootprint(productId: string): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { console.log('[deleteFootprint] 用户未登录') return false } const response = await supa .from('ml_user_footprints') .eq('user_id', userId) .eq('product_id', productId) .delete() .execute() if (response.error != null) { console.error('[deleteFootprint] 删除足迹失败:', response.error) return false } console.log('[deleteFootprint] 删除足迹成功') return true } catch (e) { console.error('[deleteFootprint] 删除足迹异常:', e) return false } } // 批量删除足迹 async deleteFootprints(productIds: string[]): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { console.log('[deleteFootprints] 用户未登录') return false } const idsAny: any[] = [] for (let i = 0; i < productIds.length; i++) { idsAny.push(productIds[i]) } const response = await supa .from('ml_user_footprints') .eq('user_id', userId) .in('product_id', idsAny) .delete() .execute() if (response.error != null) { console.error('[deleteFootprints] 批量删除足迹失败:', response.error) return false } console.log('[deleteFootprints] 批量删除足迹成功') return true } catch (e) { console.error('[deleteFootprints] 批量删除足迹异常:', e) return false } } // 清空所有足迹 async clearFootprints(): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { console.log('[clearFootprints] 用户未登录') return false } const response = await supa .from('ml_user_footprints') .eq('user_id', userId) .delete() .execute() if (response.error != null) { console.error('[clearFootprints] 清空足迹失败:', response.error) return false } console.log('[clearFootprints] 清空足迹成功') return true } catch (e) { console.error('[clearFootprints] 清空足迹异常:', e) return false } } async getAddressList(): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { const empty: UserAddress[] = [] return empty } 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 != null) { console.error('获取地址列表失败:', response.error) const empty: UserAddress[] = [] return empty } return response.data as UserAddress[] } catch (e) { console.error('获取地址列表异常:', e) const empty: UserAddress[] = [] return empty } } // 设置默认地址 async setDefaultAddress(addressId: string): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { 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 != null) { console.error('设置默认地址失败:', response.error) return false } return true } catch (error) { console.error('设置默认地址异常:', error) return false } } // 获取用户优惠券列表 async getUserCoupons(status: number = 1): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { const empty: UserCoupon[] = [] return empty } // 假设有一个视图或者直接关联 ml_user_coupons 和 ml_coupon_templates // 这里简化处理,尝试直接从 ml_user_coupons 读取,并且加入 template 信息 // 如果没有 view,可能需要改为两个查询或者使用 left join const response = await supa .from('ml_user_coupons') .select('*, template:ml_coupon_templates(name, amount, min_spend)') .eq('user_id', userId!) .eq('status', status.toString()) .order('expire_at', { ascending: true }) .execute() if (response.error != null) { console.error('获取优惠券失败:', response.error) const empty: UserCoupon[] = [] return empty } // 映射数据,将 template 的字段展平 const coupons: UserCoupon[] = [] const rawData = response.data as any[] for (let i = 0; i < rawData.length; i++) { const item = rawData[i] let template: any | null = null let itemId = '' let itemUserId = '' let itemTmplId = '' let itemCode = '' let itemStatus = 0 let itemRecv = '' let itemExpire = '' if (item instanceof UTSJSONObject) { template = item.get('template') as any | null itemId = item.getString('id') ?? '' itemUserId = item.getString('user_id') ?? '' itemTmplId = item.getString('template_id') ?? '' itemCode = item.getString('coupon_code') ?? '' itemStatus = item.getNumber('status') ?? 0 itemRecv = item.getString('received_at') ?? '' itemExpire = item.getString('expire_at') ?? '' } else { const iObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject template = iObj.get('template') as any | null itemId = iObj.getString('id') ?? '' itemUserId = iObj.getString('user_id') ?? '' itemTmplId = iObj.getString('template_id') ?? '' itemCode = iObj.getString('coupon_code') ?? '' itemStatus = iObj.getNumber('status') ?? 0 itemRecv = iObj.getString('received_at') ?? '' itemExpire = iObj.getString('expire_at') ?? '' } if (template == null) template = new UTSJSONObject() let tName = '' let tAmount = 0 let tMin = 0 if (template instanceof UTSJSONObject) { tName = template.getString('name') ?? '优惠券' tAmount = template.getNumber('amount') ?? 0 tMin = template.getNumber('min_spend') ?? 0 } else { const tObj = JSON.parse(JSON.stringify(template)) as UTSJSONObject tName = tObj.getString('name') ?? '优惠券' tAmount = tObj.getNumber('amount') ?? 0 tMin = tObj.getNumber('min_spend') ?? 0 } const couponObj = new UTSJSONObject() couponObj.set('id', itemId) couponObj.set('user_id', itemUserId) couponObj.set('template_id', itemTmplId) couponObj.set('coupon_code', itemCode) couponObj.set('status', itemStatus) couponObj.set('received_at', itemRecv) couponObj.set('expire_at', itemExpire) couponObj.set('template_name', tName) couponObj.set('amount', tAmount) couponObj.set('min_spend', tMin) coupons.push(couponObj as UserCoupon) } return coupons } catch (e) { console.error('获取优惠券异常:', e) const empty: UserCoupon[] = [] return empty } } // 获取可用优惠券数量 async getUserCouponCount(): Promise { try { const userId = this.getCurrentUserId() if (userId == null) return 0 const response = await supa .from('ml_user_coupons') .select('id', { count: 'exact' }) .eq('user_id', userId!) .eq('status', '1') .gt('expire_at', new Date().toISOString()) .limit(1) .execute() if (response.error != null) { return 0 } return response.total ?? 0 } catch (e) { return 0 } } // 获取店铺/商品可用优惠券 async getAvailableCoupons(merchantId: string): Promise { return this.fetchShopCoupons(merchantId) } // ALIAS for Cache busting: 获取店铺优惠券 async fetchShopCoupons(merchantId: string): Promise { try { console.log('[fetchShopCoupons] 开始获取优惠券,merchantId:', merchantId) // 查询该商家的优惠券 + 平台通用券 (merchant_id is null) // 注意:这里简化逻辑,实际可能需要联合查询用户是否已领取 const response = await supa .from('ml_coupon_templates') .select('*') .or(`merchant_id.eq.${merchantId},merchant_id.is.null`) .eq('status', '1') // 使用字符串 '1' .gt('end_time', new Date().toISOString()) .order('discount_value', { ascending: false }) .execute() if (response.error != null) { console.error('Fetch coupons failed:', response.error) const empty: any[] = [] return empty } const data = response.data if (data == null) { const empty: any[] = [] return empty } console.log('[fetchShopCoupons] 获取到优惠券数量:', (data as any[]).length) return data as any[] } catch (e) { console.error('Fetch coupons error:', e) const empty: any[] = [] return empty } } // 领取优惠券 async claimCoupon(templateId: string, userId: string): Promise { return this.claimShopCoupon(templateId, userId) } // ALIAS for Cache busting async claimShopCoupon(templateId: string, userId: string): Promise { try { console.log('Claiming coupon templateId:', templateId, 'userId:', userId) // 1. Fetch template details to get merchant_id and validity const tmplRes = await supa .from('ml_coupon_templates') .select('*') .eq('id', templateId) .limit(1) .execute() if (tmplRes.error != null) { console.error('Claim Coupon: Template query error', tmplRes.error) return false } // Null check for data if (tmplRes.data == null) { console.error('Claim Coupon: Template data response is null') return false } const dataList = tmplRes.data as any[] if (dataList.length === 0) { console.error('Claim Coupon: Template not found (empty list)') return false } const template = dataList[0] // Safe property access let validDays = 0 let endTimeStr: string | null = null let merchantId: string | null = null if (template instanceof UTSJSONObject) { validDays = template.getNumber('valid_days') ?? 0 endTimeStr = template.getString('end_time') merchantId = template.getString('merchant_id') } else { const tJson = JSON.parse(JSON.stringify(template)) as UTSJSONObject validDays = tJson.getNumber('valid_days') ?? 0 endTimeStr = tJson.getString('end_time') merchantId = tJson.getString('merchant_id') } // Calculate expire_at let expireAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString() if (validDays > 0) { expireAt = new Date(Date.now() + (validDays * 24 * 60 * 60 * 1000)).toISOString() } else if (endTimeStr != null && endTimeStr !== '') { expireAt = endTimeStr } // Handle UUID fields: Empty string is not valid UUID, must be null if (merchantId != null && merchantId.length === 0) { merchantId = null } // 2. Insert into user coupons with merchant_id const insertData = { user_id: userId, template_id: templateId, merchant_id: merchantId, // Important for shop filtering: null for platform coupons coupon_code: 'C' + Date.now() + Math.floor(Math.random() * 1000), status: 1, expire_at: expireAt, received_at: new Date().toISOString() } console.log('Claim Coupon Insert Payload:', JSON.stringify(insertData)) const response = await supa .from('ml_user_coupons') .insert(insertData) .execute() if (response.error != null) { console.error('Claim Coupon: Insert failed:', JSON.stringify(response.error)) // 尝试降级:如果 merchant_id 报错,尝试不带 merchant_id (仅调试用,或兼容旧表结构) if (JSON.stringify(response.error).includes('merchant_id')) { console.log('Retrying without merchant_id...') const fallbackData = { user_id: userId, template_id: templateId, coupon_code: 'C' + Date.now() + Math.random().toString().substring(2,6), status: 1, expire_at: expireAt, received_at: new Date().toISOString() } const res2 = await supa.from('ml_user_coupons').insert(fallbackData).execute() if (res2.error == null) return true } return false } return true } catch(e) { console.error('Claim coupon error:', e) return false } } // ========================================== // 聊天相关方法 // ========================================== // 获取特定会话的消息历史 async getChatMessages(merchantId: string, page: number = 1, pageSize: number = 20): Promise { console.log('[getChatMessages] 开始获取聊天记录,merchantId:', merchantId, 'page:', page) const userId = this.getCurrentUserId() if (userId == null) { const empty: ChatMessage[] = [] return empty } // 计算分页 range const fromIndex = (page - 1) * pageSize const toIndex = fromIndex + pageSize - 1 try { // 使用 or 组合条件查询:(sender_id=me AND receiver_id=merchant) OR (sender_id=merchant AND receiver_id=me) // 注意:Supabase postgrest-js 的 .or() 语法如果是针对同一列很简单,针对复杂逻辑用 string syntax // 这里简化处理,如果不加 userId 过滤,全靠 RLS const response = await supa .from('ml_chat_messages') .select('*') .or(`sender_id.eq.${merchantId},receiver_id.eq.${merchantId}`) .order('created_at', { ascending: false }) .range(fromIndex, toIndex) .execute() if (response.error != null) { console.error('getChatMessages error:', response.error) const empty: ChatMessage[] = [] return empty } const rawData = response.data if (rawData == null) { const empty: ChatMessage[] = [] return empty } const messages: ChatMessage[] = [] const rawList = rawData as any[] console.log('[getChatMessages] 获取到消息数量:', rawList.length) for (let i = 0; i < rawList.length; i++) { const item = rawList[i] const msgObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const getSafeString = (key: string): string => { const val = msgObj.get(key) if (val == null) return '' if (typeof val == 'string') return val return '' } const getSafeBoolean = (key: string): boolean => { const val = msgObj.get(key) if (val == null) return false if (typeof val == 'boolean') return val if (typeof val == 'number') return (val as number) == 1 return false } const msg: ChatMessage = { id: getSafeString('id'), session_id: getSafeString('session_id'), sender_id: getSafeString('sender_id'), receiver_id: getSafeString('receiver_id'), content: getSafeString('content'), msg_type: getSafeString('msg_type'), is_read: getSafeBoolean('is_read'), is_from_user: getSafeBoolean('is_from_user'), extra_data: getSafeString('extra_data'), created_at: getSafeString('created_at') } messages.push(msg) } return messages } catch (e) { console.error('getChatMessages exception:', e) const empty: ChatMessage[] = [] return empty } } // 发送消息 async sendMessage(merchantId: string, content: string, msgType: string = 'text'): Promise { // 确保 session 有效 const userId = this.getCurrentUserId() if (userId == null) { console.error("sendMessage failed: user not logged in or session lost") return false } try { // Debug check // const session = supa.getSession() // console.log("Sending check: UserID", userId, "AuthID:", session.user?.getString('id')) const msg = { sender_id: userId!, receiver_id: merchantId, content: content, msg_type: msgType, is_read: false, is_from_user: true } const response = await supa .from('ml_chat_messages') .insert(msg) .execute() if (response.error != null) { console.error('sendMessage error:', response.error) return false } return true } catch (e) { console.error('sendMessage exception:', e) return false } } // 标记会话已读 async markRead(merchantId: string): Promise { const userId = this.getCurrentUserId() if (userId == null) return false try { const response = await supa .from('ml_chat_messages') .update({ is_read: true }) .eq('sender_id', merchantId) .eq('receiver_id', userId) .eq('is_read', false) .execute() if (response.error != null) return false } catch (e) { return false } return true } // 提交商品评价 async submitProductReviews(reviews: Array): Promise { try { for (let i: number = 0; i < reviews.length; i++) { const review = reviews[i] const response = await supa .from('ml_product_reviews') .insert(review) .execute() if (response.error != null) { console.error('提交商品评价失败:', response.error) return false } } return true } catch (e) { console.error('提交商品评价失败:', e) return false } } // 提交店铺评价 async submitShopReview(review: UTSJSONObject): Promise { try { const response = await supa .from('ml_shop_reviews') .insert(review) .execute() return response.error == null } catch (e) { console.error('提交店铺评价失败:', e) return false } } // 更新订单状态 async updateOrderStatus(orderId: string, status: number): Promise { try { const updateData = new UTSJSONObject() updateData.set('order_status', status) const response = await supa .from('ml_orders') .update(updateData) .eq('id', orderId) .execute() return response.error == null } catch (e) { console.error('更新订单状态失败:', e) return false } } // ==================== 智能推荐相关API ==================== // 获取热搜词(全站搜索频率最高的关键词) async getHotKeywords(limit: number = 10): Promise { try { const response = await supa .from('ml_search_history') .select('keyword') .order('created_at', { ascending: false }) .limit(100) .execute() if (response.error != null || response.data == null) { return [] as string[] } // 统计关键词频率 const keywordCount = new Map() const rawList = response.data as any[] for (let i = 0; i < rawList.length; i++) { const item = rawList[i] const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const keyword = safeGetString(itemObj, 'keyword').toLowerCase().trim() if (keyword.length > 0) { const count = keywordCount.get(keyword) ?? 0 keywordCount.set(keyword, count + 1) } } // 按频率排序并返回前N个 - UTS兼容方式 // 将Map转换为数组进行排序 type KeywordEntry = { keyword: string count: number } const entryArray: KeywordEntry[] = [] // 使用forEach遍历Map(UTS支持) keywordCount.forEach((value: number, key: string) => { entryArray.push({ keyword: key, count: value }) }) // 按count降序排序 entryArray.sort((a: KeywordEntry, b: KeywordEntry): number => { return b.count - a.count }) // 取前limit个并提取关键词 const sortedKeywords: string[] = [] const maxCount = Math.min(entryArray.length, limit) for (let i = 0; i < maxCount; i++) { sortedKeywords.push(entryArray[i].keyword) } return sortedKeywords } catch (e) { console.error('获取热搜词失败:', e) return [] as string[] } } // 获取用户搜索历史 async getUserSearchHistory(limit: number = 10): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { return [] as string[] } const response = await supa .from('ml_search_history') .select('keyword') .order('created_at', { ascending: false }) .limit(limit * 2) .execute() if (response.error != null || response.data == null) { return [] as string[] } const keywords: string[] = [] const rawList = response.data as any[] const seen = new Set() for (let i = 0; i < rawList.length; i++) { const item = rawList[i] const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const rawUserId = itemObj.get('user_id') const itemUserId = (typeof rawUserId == 'string') ? (rawUserId as string) : '' // 只获取当前用户的搜索历史 if (itemUserId !== userId) continue const keyword = safeGetString(itemObj, 'keyword').trim() if (keyword.length > 0 && !seen.has(keyword)) { keywords.push(keyword) seen.add(keyword) if (keywords.length >= limit) break } } return keywords } catch (e) { console.error('获取用户搜索历史失败:', e) return [] as string[] } } // 获取用户浏览历史中的商品分类 async getUserBrowseCategories(limit: number = 5): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { return [] as string[] } const response = await supa .from('ml_browse_history') .select('product_id') .order('created_at', { ascending: false }) .limit(20) .execute() if (response.error != null || response.data == null) { return [] as string[] } // 获取浏览过的商品ID const productIds: string[] = [] const rawList = response.data as any[] for (let i = 0; i < rawList.length; i++) { const item = rawList[i] const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject // 手动过滤 user_id const rawUserId = itemObj.get('user_id') const itemUserId = (typeof rawUserId == 'string') ? (rawUserId as string) : '' if (itemUserId !== userId) continue const productId = safeGetString(itemObj, 'product_id') if (productId.length > 0) { productIds.push(productId) } } if (productIds.length === 0) { return [] as string[] } // 查询这些商品的分类 const prodResponse = await supa .from('ml_products') .select('category_id') .limit(50) .execute() if (prodResponse.error != null || prodResponse.data == null) { return [] as string[] } const categoryIds: string[] = [] const prodList = prodResponse.data as any[] for (let i = 0; i < prodList.length; i++) { const prodItem = prodList[i] const prodObj = JSON.parse(JSON.stringify(prodItem)) as UTSJSONObject const prodId = safeGetString(prodObj, 'id') // 只统计浏览过的商品 let found = false for (let j = 0; j < productIds.length; j++) { if (productIds[j] == prodId) { found = true break } } if (!found) continue const catId = safeGetString(prodObj, 'category_id') if (catId.length > 0 && categoryIds.indexOf(catId) < 0) { categoryIds.push(catId) if (categoryIds.length >= limit) break } } return categoryIds } catch (e) { console.error('获取用户浏览分类失败:', e) return [] as string[] } } // 智能推荐:综合用户搜索历史、浏览历史、热销商品 async getSmartRecommendations(limit: number = 10): Promise { try { console.log('[getSmartRecommendations] 开始获取智能推荐...') const products: Product[] = [] const addedIds = new Set() // 1. 根据用户搜索历史推荐商品(权重最高) const searchHistory = await this.getUserSearchHistory(5) console.log('[getSmartRecommendations] 用户搜索历史:', searchHistory) if (searchHistory.length > 0) { // 根据搜索关键词查找商品 const keywordProducts = await this.searchProductsByKeywords(searchHistory, limit) for (let i = 0; i < keywordProducts.length; i++) { const prod = keywordProducts[i] if (!addedIds.has(prod.id)) { products.push(prod) addedIds.add(prod.id) } } } // 2. 根据用户浏览历史推荐相似分类商品 if (products.length < limit) { const browseCategories = await this.getUserBrowseCategories(3) console.log('[getSmartRecommendations] 用户浏览分类:', browseCategories) if (browseCategories.length > 0) { const categoryProducts = await this.getProductsByCategories(browseCategories, limit - products.length) for (let i = 0; i < categoryProducts.length; i++) { const prod = categoryProducts[i] if (!addedIds.has(prod.id)) { products.push(prod) addedIds.add(prod.id) } } } } // 3. 补充热销商品 if (products.length < limit) { const hotProducts = await this.getHotProducts(limit - products.length + 5) for (let i = 0; i < hotProducts.length; i++) { const prod = hotProducts[i] if (!addedIds.has(prod.id)) { products.push(prod) addedIds.add(prod.id) if (products.length >= limit) break } } } // 4. 如果还不够,用普通商品补充 if (products.length < limit) { const moreProducts = await this.getProductsByPrice(limit - products.length + 5, false) for (let i = 0; i < moreProducts.length; i++) { const prod = moreProducts[i] if (!addedIds.has(prod.id)) { products.push(prod) addedIds.add(prod.id) if (products.length >= limit) break } } } console.log('[getSmartRecommendations] 返回商品数量:', products.length) return products.slice(0, limit) } catch (e) { console.error('获取智能推荐失败:', e) return [] as Product[] } } // 根据关键词列表搜索商品 async searchProductsByKeywords(keywords: string[], limit: number): Promise { try { const response = await supa .from('ml_products_detail_view') .select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot') .order('sale_count', { ascending: false }) .limit(limit * 2) .execute() if (response.error != null || response.data == null) { return [] as Product[] } const products: Product[] = [] const rawList = response.data as any[] for (let i = 0; i < rawList.length; i++) { const item = rawList[i] const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject // 手动过滤 status const rawStatus = prodObj.get('status') let statusNum: number = 0 if (typeof rawStatus == 'number') { statusNum = rawStatus as number } if (statusNum !== 1) continue // 检查是否匹配任何关键词 const name = safeGetString(prodObj, 'name').toLowerCase() const desc = safeGetString(prodObj, 'description').toLowerCase() let matched = false for (let j = 0; j < keywords.length; j++) { const keyword = keywords[j].toLowerCase() if (name.indexOf(keyword) >= 0 || desc.indexOf(keyword) >= 0) { matched = true break } } if (!matched) continue products.push(parseProductFromRaw(item)) if (products.length >= limit) break } return products } catch (e) { console.error('根据关键词搜索商品失败:', e) return [] as Product[] } } // 根据分类列表获取商品 async getProductsByCategories(categoryIds: string[], limit: number): Promise { try { const response = await supa .from('ml_products_detail_view') .select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot') .order('sale_count', { ascending: false }) .limit(limit * 2) .execute() if (response.error != null || response.data == null) { return [] as Product[] } const products: Product[] = [] const rawList = response.data as any[] for (let i = 0; i < rawList.length; i++) { const item = rawList[i] const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject // 手动过滤 status const rawStatus = prodObj.get('status') let statusNum: number = 0 if (typeof rawStatus == 'number') { statusNum = rawStatus as number } if (statusNum !== 1) continue // 手动过滤 category_id const rawCatId = prodObj.get('category_id') const itemCatId = (typeof rawCatId == 'string') ? (rawCatId as string) : '' let matched = false for (let j = 0; j < categoryIds.length; j++) { if (itemCatId == categoryIds[j]) { matched = true break } } if (!matched) continue products.push(parseProductFromRaw(item)) if (products.length >= limit) break } return products } catch (e) { console.error('根据分类获取商品失败:', e) return [] as Product[] } } // 记录用户搜索行为 async recordSearch(keyword: string, resultCount: number): Promise { try { const userId = this.getCurrentUserId() const searchRecord = new UTSJSONObject() searchRecord.set('keyword', keyword) searchRecord.set('result_count', resultCount) if (userId != null) { searchRecord.set('user_id', userId) } await supa .from('ml_search_history') .insert(searchRecord) .execute() } catch (e) { console.error('记录搜索失败:', e) } } // 记录用户浏览行为 async recordBrowse(productId: string, duration: number = 0): Promise { try { const userId = this.getCurrentUserId() if (userId == null) return const browseRecord = new UTSJSONObject() browseRecord.set('user_id', userId) browseRecord.set('product_id', productId) browseRecord.set('browse_duration', duration) browseRecord.set('created_at', new Date().toISOString()) // UTS Android不支持upsert,使用insert await supa .from('ml_browse_history') .insert(browseRecord) .execute() } catch (e) { console.error('记录浏览失败:', e) } } } // 导出单例实例 export const supabaseService = new SupabaseService() // 默认导出 export default supabaseService