import supa from '@/components/supadb/aksupainstance.uts' import type { AkReqResponse } from '@/uni_modules/ak-req/index.uts' import { APP_ROLE, CURRENT_CLIENT, SUPA_KEY, SUPA_URL } from '@/ak/config.uts' import { ORDER_PAY_TIMEOUT_SECONDS, ORDER_STATUS_CANCELLED, ORDER_STATUS_PAID_OR_SHIPPING, ORDER_STATUS_PENDING, ORDER_STATUS_TIMEOUT_LEGACY, ORDER_TIMEOUT_CANCEL_REASON, PAYMENT_STATUS_PAID, PAYMENT_STATUS_TIMEOUT, PAYMENT_STATUS_UNPAID, type UnifiedOrderSource } from '@/utils/orderStatus.uts' // 导出 supa 实例,供 services 层统一使用 export { supa } import type { OrderOptions } from '@/components/supadb/aksupa.uts' const OLD_URL = '192.168.1.61:18000' const NEW_URL = '119.146.131.237:9126' function fixImageUrl(url: string | null): string { if (url == null) return '' if (url.indexOf(OLD_URL) >= 0) { return url.replace(OLD_URL, NEW_URL) } return url } function fixImageUrls(urls: any): string[] { if (urls == null) return [] if (Array.isArray(urls)) { const result: string[] = [] const arr = urls as any[] for (let i = 0; i < arr.length; i++) { try { const urlStr = JSON.stringify(arr[i]) if (urlStr != null && urlStr.startsWith('"') && urlStr.endsWith('"')) { const fixed = fixImageUrl(urlStr.substring(1, urlStr.length - 1)) if (fixed !== '') result.push(fixed) } } catch (e) {} } return result } return [] } function canUseConsumerData(): boolean { return CURRENT_CLIENT === 'consumer' || CURRENT_CLIENT === 'full' } function maskConsumerKey(key: string): string { const keyLen = key.length if (keyLen <= 20) { return '(too short)' } return key.substring(0, 10) + '...' + key.substring(keyLen - 8) } function logConsumerQueryStart(action: string, tableName: string, fieldList: string = '*'): boolean { if (!canUseConsumerData()) { console.error('[consumer-db] 已阻止非 consumer 数据请求') console.error('[consumer-db] 当前端类型:', CURRENT_CLIENT) console.error('[consumer-db] 当前应用角色:', APP_ROLE) console.error('[consumer-db] 查询表:', tableName) console.error('[consumer-db] 查询动作:', action) return false } console.log('[consumer-db] 当前端类型:', CURRENT_CLIENT) console.log('[consumer-db] 当前应用角色:', APP_ROLE) console.log('[consumer-db] supabaseUrl 已加载:', SUPA_URL) console.log('[consumer-db] supabaseKey 已加载:', maskConsumerKey(SUPA_KEY)) console.log('[consumer-db] 首页开始加载数据') console.log('[consumer-db] 查询表:', tableName) console.log('[consumer-db] 查询字段:', fieldList) console.log('[consumer-db] 查询动作:', action) return true } function logConsumerQuerySuccess(action: string, tableName: string, count: number): void { console.log('[consumer-db] 查询成功,动作:', action) console.log('[consumer-db] 查询成功,表:', tableName) console.log('[consumer-db] 查询成功,数量:', count) } function logConsumerQueryFailure(action: string, tableName: string, error: any): void { console.error('[consumer-db] 查询失败') console.error('[consumer-db] 查询动作:', action) console.error('[consumer-db] 查询表:', tableName) if (error != null) { try { console.error('[consumer-db] error.message:', error.message) } catch (e) {} try { console.error('[consumer-db] error.code:', error.code) } catch (e) {} try { console.error('[consumer-db] error.details:', error.details) } catch (e) {} try { console.error('[consumer-db] error.hint:', error.hint) } catch (e) {} } console.error('[consumer-db] error.raw:', error) } // 使用单例 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 '' const strVal = JSON.stringify(rawVal) if (strVal == null) return '' if (strVal.startsWith('"') && strVal.endsWith('"')) { return strVal.substring(1, strVal.length - 1) } return strVal } 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 try { const numVal = rawVal as number if (!isNaN(numVal)) return numVal } catch (e) {} 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 try { const boolVal = rawVal as boolean return boolVal } catch (e) {} 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 { console.log('[parseProductFromRaw] 开始解析商品') const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject console.log('[parseProductFromRaw] JSON转换成功') const mainImageUrl = fixImageUrl(safeGetString(itemObj, 'main_image_url')) const imageUrls = fixImageUrls(safeGetStringArray(itemObj, 'image_urls')) console.log('[parseProductFromRaw] 图片处理完成') const result: Product = { id: safeGetString(itemObj, 'id'), name: safeGetString(itemObj, 'name'), short_title: safeGetString(itemObj, 'short_title'), subtitle: safeGetString(itemObj, 'subtitle'), description: safeGetString(itemObj, 'description'), base_price: safeGetNumber(itemObj, 'base_price'), price: safeGetNumber(itemObj, 'base_price'), original_price: safeGetNumber(itemObj, 'market_price'), market_price: safeGetNumber(itemObj, 'market_price'), main_image_url: mainImageUrl, image_url: mainImageUrl, images: imageUrls, category_id: safeGetString(itemObj, 'category_id'), brand_id: safeGetString(itemObj, 'brand_id'), merchant_id: safeGetString(itemObj, 'merchant_id'), total_stock: safeGetNumber(itemObj, 'total_stock'), stock: safeGetNumber(itemObj, 'total_stock'), sale_count: safeGetNumber(itemObj, 'sale_count'), status: safeGetNumber(itemObj, 'status'), is_featured: safeGetBoolean(itemObj, 'is_featured'), is_new: safeGetBoolean(itemObj, 'is_new'), is_hot: safeGetBoolean(itemObj, 'is_hot'), card_tags: safeGetStringArray(itemObj, 'card_tags'), service_tags: safeGetStringArray(itemObj, 'service_tags'), selling_points: safeGetStringArray(itemObj, 'selling_points'), display_sales_text: safeGetString(itemObj, 'display_sales_text'), compliance_type: safeGetString(itemObj, 'compliance_type'), device_class: safeGetString(itemObj, 'device_class'), specification: safeGetString(itemObj, 'specification'), usage: safeGetString(itemObj, 'usage'), side_effects: safeGetString(itemObj, 'side_effects'), precautions: safeGetString(itemObj, 'precautions'), expiry_date: safeGetString(itemObj, 'expiry_date'), storage_conditions: safeGetString(itemObj, 'storage_conditions'), approval_number: safeGetString(itemObj, 'approval_number'), created_at: safeGetString(itemObj, 'created_at') } console.log('[parseProductFromRaw] 商品解析成功:', result.name) return result } 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 sort_order?: number image_url?: string scene?: string category_type?: string compliance_type?: string is_active?: boolean 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 short_title?: string card_tags?: string[] service_tags?: string[] selling_points?: string[] display_sales_text?: string compliance_type?: string device_class?: 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 latitude?: number longitude?: number coordinate_type?: 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 latitude?: number longitude?: number coordinate_type?: 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 latitude?: number longitude?: number coordinate_type?: 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 } function emptyProductPage(page: number, limit: number): PaginatedResponse { return { data: [] as Product[], total: 0, page, limit, hasmore: false } } function buildAnyStringList(values: string[]): any[] { const result: any[] = [] for (let i = 0; i < values.length; i++) { if (values[i] != '') { result.push(values[i]) } } return result } function parseMedicalMallCategory(item: UTSJSONObject): Category { const icon = safeGetString(item, 'icon') const imageUrl = safeGetString(item, 'image_url') const color = safeGetString(item, 'color') return { id: safeGetString(item, 'id'), name: safeGetString(item, 'name'), icon: icon != '' ? icon : imageUrl, description: safeGetString(item, 'description'), color: color != '' ? color : '#16a085', parent_id: safeGetString(item, 'parent_id'), level: safeGetNumber(item, 'level'), slug: safeGetString(item, 'category_type'), sort_order: safeGetNumber(item, 'sort_order'), image_url: imageUrl, scene: safeGetString(item, 'scene'), category_type: safeGetString(item, 'category_type'), compliance_type: safeGetString(item, 'compliance_type'), is_active: safeGetBoolean(item, 'is_active'), created_at: safeGetString(item, 'created_at') } as Category } function getMedicalMallCategoryKeywords(categoryId: string): string[] { if (categoryId == 'med_device') return ['医疗器械', '血压计', '血糖仪', '血氧仪', '体温计', '制氧机', '雾化器', '轮椅', '拐杖', '护理床'] if (categoryId == 'blood_pressure_monitor') return ['血压计', '血压监测', '电子血压计'] if (categoryId == 'blood_glucose_meter') return ['血糖仪', '血糖测试', '采血笔'] if (categoryId == 'oximeter') return ['血氧仪', '血氧'] if (categoryId == 'thermometer') return ['体温计', '额温枪', '测温'] if (categoryId == 'oxygen_concentrator') return ['制氧机', '吸氧'] if (categoryId == 'nebulizer') return ['雾化器', '雾化'] if (categoryId == 'wheelchair_crutch') return ['轮椅', '拐杖', '助行器'] if (categoryId == 'nursing_bed') return ['护理床', '病床'] if (categoryId == 'otc_medicine') return ['感冒药', '退烧药', '止咳', '咽喉', '肠胃', '皮肤', '止痛', '跌打', '儿童用药', '家庭常备药'] if (categoryId == 'cold_fever') return ['感冒药', '退烧', '发烧', '退热贴', '感冒'] if (categoryId == 'cough_throat') return ['咳嗽', '咽喉', '润喉'] if (categoryId == 'stomach_medicine') return ['肠胃', '胃药', '益生菌'] if (categoryId == 'skin_external') return ['皮肤', '外用', '软膏', '喷剂'] if (categoryId == 'eye_ear_nose') return ['眼药', '滴眼', '鼻炎', '口腔', '耳'] if (categoryId == 'pain_relief') return ['止痛', '镇痛'] if (categoryId == 'trauma_sprain') return ['跌打', '扭伤', '损伤', '喷雾'] if (categoryId == 'child_medicine') return ['儿童', '小儿'] if (categoryId == 'rehab_care') return ['康复', '护理', '敷料', '护具', '支具', '理疗', '热敷', '训练'] if (categoryId == 'postop_rehab') return ['术后', '恢复带', '康复'] if (categoryId == 'wound_dressing' || categoryId == 'wound_care') return ['伤口', '敷料', '纱布', '创可贴', '护理包'] if (categoryId == 'brace_support') return ['护具', '支具', '护腰', '护膝', '支撑带'] if (categoryId == 'rehab_training') return ['康复训练', '训练器'] if (categoryId == 'physiotherapy_hot') return ['理疗', '热敷', '暖贴', '热敷贴'] if (categoryId == 'mobility_training') return ['行动训练', '步行训练'] if (categoryId == 'chronic_monitor') return ['慢病', '血压', '血糖', '心脑血管', '呼吸', '体脂', '家庭检测'] if (categoryId == 'hypertension') return ['高血压', '血压'] if (categoryId == 'diabetes') return ['糖尿病', '血糖'] if (categoryId == 'cardiovascular') return ['心脑血管', '心率', '心电'] if (categoryId == 'respiratory_health') return ['呼吸', '雾化', '氧', '制氧'] if (categoryId == 'weight_bodyfat') return ['体重', '体脂'] if (categoryId == 'home_testing') return ['检测', '试纸'] if (categoryId == 'elderly_aid') return ['适老', '长者', '防滑', '扶手', '助浴', '失禁', '生活辅助'] if (categoryId == 'anti_slip') return ['防滑', '防跌'] if (categoryId == 'elderly_bathroom') return ['卫浴', '扶手', '浴室'] if (categoryId == 'eating_aid') return ['助餐', '餐具'] if (categoryId == 'bathing_aid') return ['助浴', '洗澡椅', '沐浴'] if (categoryId == 'incontinence_care') return ['失禁', '护理垫', '纸尿裤'] if (categoryId == 'daily_living_aid') return ['生活辅助', '起身', '坐便'] if (categoryId == 'nutrition_health') return ['维生素', '钙片', '蛋白粉', '营养', '益生菌', '免疫'] if (categoryId == 'vitamin') return ['维生素', '维C', '维D'] if (categoryId == 'calcium') return ['钙片', '钙', '钙铁锌', '锌', '硒'] if (categoryId == 'protein') return ['蛋白', '蛋白粉'] if (categoryId == 'elderly_nutrition') return ['老人营养', '中老年营养'] if (categoryId == 'gut_health') return ['益生菌', '肠道'] if (categoryId == 'immune_support') return ['免疫', '免疫支持'] if (categoryId == 'protection_disinfection') return ['口罩', '消毒', '酒精', '湿巾', '手套', '急救包', '棉签', '纱布'] if (categoryId == 'mask') return ['口罩'] if (categoryId == 'disinfectant') return ['消毒液', '消毒', '酒精'] if (categoryId == 'alcohol_wipes') return ['酒精湿巾', '湿巾'] if (categoryId == 'gloves') return ['手套'] if (categoryId == 'first_aid') return ['急救包'] if (categoryId == 'dressing_tools' || categoryId == 'nursing_consumables') return ['棉签', '纱布', '耗材'] if (categoryId == 'tcm_health') return ['中医', '艾灸', '拔罐', '养生茶', '贴敷', '药膳', '按摩', '泡脚'] if (categoryId == 'moxibustion') return ['艾灸', '拔罐', '艾条', '温灸'] if (categoryId == 'herbal_drink') return ['养生茶', '茶饮', '草本'] if (categoryId == 'tcm_patch') return ['贴敷', '理疗贴', '草本贴'] if (categoryId == 'medicated_diet') return ['药膳', '滋补'] if (categoryId == 'massage_tools') return ['按摩', '理疗', '刮痧'] if (categoryId == 'foot_bath') return ['泡脚', '足浴'] if (categoryId == 'home_care_daily') return ['护理', '伤口', '口腔', '皮肤', '清洁', '耗材', '健康工具'] if (categoryId == 'oral_care') return ['口腔', '牙', '漱口'] if (categoryId == 'skin_care') return ['皮肤护理', '修护', '护肤'] if (categoryId == 'cleaning_care') return ['清洁护理', '清洁'] if (categoryId == 'home_health_tool') return ['健康工具', '家庭健康'] if (categoryId == 'all_medical' || categoryId == 'recommend') return ['血压计', '血糖仪', '血氧仪', '体温计', '制氧机', '雾化器', '轮椅', '护理床', '感冒药', '退热贴', '止咳', '胃药', '创可贴', '敷料', '纱布', '护具', '康复', '理疗', '慢病', '口罩', '消毒', '酒精', '急救包', '艾灸', '拔罐', '维生素', '钙片', '蛋白粉', '益生菌', '老人营养', '适老', '助浴', '护理'] return [] as string[] } function containsMedicalMallKeyword(source: string, keywords: string[]): boolean { if (source == '' || keywords.length == 0) { return false } for (let i = 0; i < keywords.length; i++) { if (keywords[i] != '' && source.indexOf(keywords[i]) != -1) { return true } } return false } function buildMedicalMallProductText(item: UTSJSONObject): string { const tags = safeGetStringArray(item, 'tags') let tagText = '' for (let i = 0; i < tags.length; i++) { if (tags[i] != '') { tagText += ' ' + tags[i] } } return ( safeGetString(item, 'name') + ' ' + safeGetString(item, 'subtitle') + ' ' + safeGetString(item, 'description') + ' ' + safeGetString(item, 'category_name') + ' ' + safeGetString(item, 'specification') + ' ' + safeGetString(item, 'usage') + ' ' + tagText ) } function isRxHiddenProduct(item: UTSJSONObject): boolean { return safeGetString(item, 'compliance_type') == 'rx_hidden' } function matchesMedicalMallFallback(item: UTSJSONObject, categoryId: string): boolean { if (isRxHiddenProduct(item)) { return false } const sourceText = buildMedicalMallProductText(item) return containsMedicalMallKeyword(sourceText, getMedicalMallCategoryKeywords(categoryId)) } function sortProductsByIdOrder(products: Product[], orderedIds: string[]): Product[] { const productMap = new Map() for (let i = 0; i < products.length; i++) { productMap.set(products[i].id, products[i]) } const sorted: Product[] = [] for (let i = 0; i < orderedIds.length; i++) { const item = productMap.get(orderedIds[i]) if (item != null) { sorted.push(item) } } return sorted } function dedupeProducts(products: Product[]): Product[] { const deduped: Product[] = [] const seenIds: string[] = [] for (let i = 0; i < products.length; i++) { const productId = products[i].id if (productId != '' && seenIds.indexOf(productId) != -1) { continue } if (productId != '') { seenIds.push(productId) } deduped.push(products[i]) } return deduped } export type ShopOrderResponse = { success: boolean orderIds: string[] error?: string } export type RefundResponse = { success: boolean message: string } export type ConfirmReceiptResponse = { success: boolean error?: string } export type GetOrdersByCursorParams = { cursor: string limit: number status: number keyword: string } export type GetOrdersByCursorResult = { list: UTSJSONObject[] nextCursor: string hasMore: boolean } export type GetUnifiedOrdersByCursorParams = { cursor: string limit: number bizType: string statusTab: string keyword: string } export type GetUnifiedOrdersByCursorResult = { list: UTSJSONObject[] nextCursor: string hasMore: boolean } export type OrderCountsResult = { all: number pending: number shipping: number delivering: number completed: number aftersale: number cancelled: number } 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') } // 移除基于 storage 的后备获取,严格只认当前 Tab 独立 session return null //cyh // 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 { if (!logConsumerQueryStart('getParentCategories', 'ml_categories', '*')) { return [] } const response = await supa .from('ml_categories') .select('*') .eq('level', 1) .order('sort_order', { ascending: true }) .execute() if (response.error != null) { logConsumerQueryFailure('getParentCategories', 'ml_categories', response.error) 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) : '#ff5000', level: 1, slug: (typeof slugVal == 'string') ? (slugVal as string) : '' } categories.push(cat) } logConsumerQuerySuccess('getParentCategories', 'ml_categories', categories.length) return categories } catch (error) { logConsumerQueryFailure('getParentCategories', 'ml_categories', error) console.error('获取一级分类异常:', error) return [] } } // 获取子分类 async getSubCategories(parentId: string): Promise { try { if (!logConsumerQueryStart('getSubCategories', 'ml_categories', '*')) { return [] } console.log('[getSubCategories] 开始获取子分类, parentId:', parentId) const response = await supa .from('ml_categories') .select('*') .eq('level', 2) .eq('parent_id', parentId) .order('sort_order', { ascending: true }) .execute() console.log('[getSubCategories] 查询完成') if (response.error != null) { logConsumerQueryFailure('getSubCategories', 'ml_categories', response.error) console.error('获取子分类失败:', response.error) return [] } const rawData = response.data if (rawData == null) { console.log('[getSubCategories] 数据为空') return [] } const categories: Category[] = [] const rawList = rawData as UTSJSONObject[] 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 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') : '#ff5000', level: 2, parent_id: safeGetString(itemObj, 'parent_id'), slug: safeGetString(itemObj, 'slug') } categories.push(cat) } console.log('[getSubCategories] 返回分类数量:', categories.length) logConsumerQuerySuccess('getSubCategories', 'ml_categories', categories.length) return categories } catch (error) { logConsumerQueryFailure('getSubCategories', 'ml_categories', 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 { if (!logConsumerQueryStart('getBrands', 'ml_brands', 'id, name, logo_url, description, is_active')) { return [] } 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) { logConsumerQueryFailure('getBrands', 'ml_brands', response.error) 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) logConsumerQuerySuccess('getBrands', 'ml_brands', brands.length) return brands } catch (error) { logConsumerQueryFailure('getBrands', 'ml_brands', error) console.error('获取品牌异常:', error) return [] } } // 获取指定分类的商品 async getProductsByCategory( categoryId: string, page: number = 1, limit: number = 20 ): Promise> { try { if (!logConsumerQueryStart('getProductsByCategory', 'ml_products_detail_view', '*')) { return emptyProductPage(page, limit) } 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) { logConsumerQueryFailure('getProductsByCategory', 'ml_products_detail_view', response.error) 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)) } logConsumerQuerySuccess('getProductsByCategory', 'ml_products_detail_view', products.length) return { data: products, total: response.total ?? products.length, page, limit, hasmore: response.hasmore ?? false } } catch (error) { logConsumerQueryFailure('getProductsByCategory', 'ml_products_detail_view', error) console.error('获取商品异常:', error) return { data: [] as Product[], total: 0, page, limit, hasmore: false } } } async getMedicalMallParentCategories(): Promise { try { if (!logConsumerQueryStart('getMedicalMallParentCategories', 'medical_mall_categories', '*')) { return [] } const response = await supa .from('medical_mall_categories') .select('*') .is('parent_id', null) .eq('is_active', true) .is('deleted_at', null) .order('sort_order', { ascending: true }) .execute() if (response.error != null || response.data == null) { logConsumerQueryFailure('getMedicalMallParentCategories', 'medical_mall_categories', response.error) return [] } const rows = response.data as UTSJSONObject[] const categories: Category[] = [] for (let i = 0; i < rows.length; i++) { categories.push(parseMedicalMallCategory(JSON.parse(JSON.stringify(rows[i])) as UTSJSONObject)) } logConsumerQuerySuccess('getMedicalMallParentCategories', 'medical_mall_categories', categories.length) return categories } catch (error) { logConsumerQueryFailure('getMedicalMallParentCategories', 'medical_mall_categories', error) return [] } } async getMedicalMallCategoryById(categoryId: string): Promise { try { if (categoryId == '') { return null } const response = await supa .from('medical_mall_categories') .select('*') .eq('id', categoryId) .eq('is_active', true) .is('deleted_at', null) .limit(1) .execute() if (response.error != null || response.data == null) { logConsumerQueryFailure('getMedicalMallCategoryById', 'medical_mall_categories', response.error) return null } const rows = response.data as UTSJSONObject[] if (rows.length == 0) { return null } return parseMedicalMallCategory(JSON.parse(JSON.stringify(rows[0])) as UTSJSONObject) } catch (error) { logConsumerQueryFailure('getMedicalMallCategoryById', 'medical_mall_categories', error) return null } } async getMedicalMallSubCategories(parentId: string): Promise { try { if (!logConsumerQueryStart('getMedicalMallSubCategories', 'medical_mall_categories', '*')) { return [] } const response = await supa .from('medical_mall_categories') .select('*') .eq('parent_id', parentId) .eq('is_active', true) .is('deleted_at', null) .order('sort_order', { ascending: true }) .execute() if (response.error != null || response.data == null) { logConsumerQueryFailure('getMedicalMallSubCategories', 'medical_mall_categories', response.error) return [] } const rows = response.data as UTSJSONObject[] const categories: Category[] = [] for (let i = 0; i < rows.length; i++) { categories.push(parseMedicalMallCategory(JSON.parse(JSON.stringify(rows[i])) as UTSJSONObject)) } logConsumerQuerySuccess('getMedicalMallSubCategories', 'medical_mall_categories', categories.length) return categories } catch (error) { logConsumerQueryFailure('getMedicalMallSubCategories', 'medical_mall_categories', error) return [] } } async getMedicalMallCategoryChildrenIds(parentId: string): Promise { try { let response: any if (parentId == 'all_medical') { response = await supa .from('medical_mall_categories') .select('id') .eq('is_active', true) .is('deleted_at', null) .eq('level', 2) .order('sort_order', { ascending: true }) .execute() } else { response = await supa .from('medical_mall_categories') .select('id') .eq('parent_id', parentId) .eq('is_active', true) .is('deleted_at', null) .order('sort_order', { ascending: true }) .execute() } if (response.error != null || response.data == null) { return [] } const rows = response.data as UTSJSONObject[] const ids: string[] = [] for (let i = 0; i < rows.length; i++) { const row = JSON.parse(JSON.stringify(rows[i])) as UTSJSONObject const id = safeGetString(row, 'id') if (id != '') { ids.push(id) } } return ids } catch (error) { console.error('获取医疗商城子分类 ID 失败:', error) return [] } } async getMedicalMallProductsByCategory(categoryId: string, page: number = 1, limit: number = 20): Promise> { try { if (categoryId == 'recommend') { return await this.getMedicalMallSmartRecommendations(page, limit) } if (!logConsumerQueryStart('getMedicalMallProductsByCategory', 'medical_mall_product_categories', '*')) { return emptyProductPage(page, limit) } const targetCategoryIds: string[] = [] if (categoryId == 'all_medical') { const allChildren = await this.getMedicalMallCategoryChildrenIds('all_medical') for (let i = 0; i < allChildren.length; i++) { targetCategoryIds.push(allChildren[i]) } } else { const categoryResponse = await supa .from('medical_mall_categories') .select('id, level') .eq('id', categoryId) .eq('is_active', true) .is('deleted_at', null) .limit(1) .execute() targetCategoryIds.push(categoryId) if (categoryResponse.error == null && categoryResponse.data != null) { const categoryRows = categoryResponse.data as UTSJSONObject[] if (categoryRows.length > 0) { const categoryObj = JSON.parse(JSON.stringify(categoryRows[0])) as UTSJSONObject if (safeGetNumber(categoryObj, 'level') == 1) { const childIds = await this.getMedicalMallCategoryChildrenIds(categoryId) for (let i = 0; i < childIds.length; i++) { if (targetCategoryIds.indexOf(childIds[i]) == -1) { targetCategoryIds.push(childIds[i]) } } } } } } const linkedIds: string[] = [] if (targetCategoryIds.length > 0) { const linkResponse = await supa .from('medical_mall_product_categories') .select('product_id, sort_order, created_at') .in('category_id', buildAnyStringList(targetCategoryIds)) .is('deleted_at', null) .order('sort_order', { ascending: true }) .order('created_at', { ascending: false }) .limit(500) .execute() if (linkResponse.error == null && linkResponse.data != null) { const linkRows = linkResponse.data as UTSJSONObject[] for (let i = 0; i < linkRows.length; i++) { const row = JSON.parse(JSON.stringify(linkRows[i])) as UTSJSONObject const productId = safeGetString(row, 'product_id') if (productId != '' && linkedIds.indexOf(productId) == -1) { linkedIds.push(productId) } } } } let finalProducts: Product[] = [] if (linkedIds.length > 0) { const productResponse = await supa .from('ml_products_detail_view') .select('*') .in('id', buildAnyStringList(linkedIds)) .eq('status', '1') .execute() if (productResponse.error == null && productResponse.data != null) { const rows = productResponse.data as any[] const parsed: Product[] = [] for (let i = 0; i < rows.length; i++) { const rowObj = JSON.parse(JSON.stringify(rows[i])) as UTSJSONObject if (isRxHiddenProduct(rowObj)) { continue } parsed.push(parseProductFromRaw(rows[i])) } finalProducts = sortProductsByIdOrder(parsed, linkedIds) } } if (finalProducts.length == 0) { const fallbackResponse = await supa .from('ml_products_detail_view') .select('*') .eq('status', '1') .order('sale_count', { ascending: false }) .order('created_at', { ascending: false }) .limit(300) .execute() if (fallbackResponse.error == null && fallbackResponse.data != null) { const fallbackRows = fallbackResponse.data as any[] for (let i = 0; i < fallbackRows.length; i++) { const rowObj = JSON.parse(JSON.stringify(fallbackRows[i])) as UTSJSONObject if (!matchesMedicalMallFallback(rowObj, categoryId)) { continue } finalProducts.push(parseProductFromRaw(fallbackRows[i])) } } } const deduped = dedupeProducts(finalProducts) const startIndex = (page - 1) * limit const endIndex = startIndex + limit return { data: deduped.slice(startIndex, endIndex), total: deduped.length, page, limit, hasmore: deduped.length > endIndex } } catch (error) { logConsumerQueryFailure('getMedicalMallProductsByCategory', 'medical_mall_product_categories', error) console.error('获取医疗商城分类商品失败:', error) return emptyProductPage(page, limit) } } async getMedicalMallSmartRecommendations(page: number = 1, limit: number = 10): Promise> { try { if (!logConsumerQueryStart('getMedicalMallSmartRecommendations', 'medical_mall_product_categories', '*')) { return emptyProductPage(page, limit) } const linkedIds: string[] = [] const linkResponse = await supa .from('medical_mall_product_categories') .select('product_id, is_primary, sort_order, created_at') .is('deleted_at', null) .order('is_primary', { ascending: false }) .order('sort_order', { ascending: true }) .order('created_at', { ascending: false }) .limit(500) .execute() if (linkResponse.error == null && linkResponse.data != null) { const linkRows = linkResponse.data as UTSJSONObject[] for (let i = 0; i < linkRows.length; i++) { const row = JSON.parse(JSON.stringify(linkRows[i])) as UTSJSONObject const productId = safeGetString(row, 'product_id') if (productId != '' && linkedIds.indexOf(productId) == -1) { linkedIds.push(productId) } } } const merged: Product[] = [] const addedIds = new Set() if (linkedIds.length > 0) { const linkedProductResponse = await supa .from('ml_products_detail_view') .select('*') .in('id', buildAnyStringList(linkedIds)) .eq('status', '1') .execute() if (linkedProductResponse.error == null && linkedProductResponse.data != null) { const rows = linkedProductResponse.data as any[] const parsed: Product[] = [] for (let i = 0; i < rows.length; i++) { const rowObj = JSON.parse(JSON.stringify(rows[i])) as UTSJSONObject if (isRxHiddenProduct(rowObj)) { continue } parsed.push(parseProductFromRaw(rows[i])) } const ordered = sortProductsByIdOrder(parsed, linkedIds) for (let i = 0; i < ordered.length; i++) { if (!addedIds.has(ordered[i].id)) { merged.push(ordered[i]) addedIds.add(ordered[i].id) } } } } if (merged.length < page * limit + limit) { const fallbackResponse = await supa .from('ml_products_detail_view') .select('*') .eq('status', '1') .order('sale_count', { ascending: false }) .order('created_at', { ascending: false }) .limit(300) .execute() if (fallbackResponse.error == null && fallbackResponse.data != null) { const rows = fallbackResponse.data as any[] for (let i = 0; i < rows.length; i++) { const rowObj = JSON.parse(JSON.stringify(rows[i])) as UTSJSONObject if (!matchesMedicalMallFallback(rowObj, 'recommend')) { continue } const product = parseProductFromRaw(rows[i]) if (!addedIds.has(product.id)) { merged.push(product) addedIds.add(product.id) } } } } const startIndex = (page - 1) * limit const endIndex = startIndex + limit return { data: merged.slice(startIndex, endIndex), total: merged.length, page, limit, hasmore: merged.length > endIndex } } catch (error) { logConsumerQueryFailure('getMedicalMallSmartRecommendations', 'medical_mall_product_categories', error) console.error('获取医疗商城推荐商品失败:', error) return emptyProductPage(page, limit) } } // 根据商品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 { const keywordLower = keyword.toLowerCase() const encodedKeyword = encodeURIComponent(keywordLower) const orString = `name.ilike.%${encodedKeyword}%,description.ilike.%${encodedKeyword}%,subtitle.ilike.%${encodedKeyword}%,brand_name.ilike.%${encodedKeyword}%` console.log('[searchProducts] 搜索关键词:', keyword, '编码后:', encodedKeyword) console.log('[searchProducts] or条件:', orString) let query = supa .from('ml_products_detail_view') .select('*', { count: 'exact' }) .eq('status', 1) .or(orString) 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() let dataLength = 0 try { const respData = response.data if (respData != null && Array.isArray(respData)) { dataLength = (respData as any[]).length } } catch (e) { console.error('[searchProducts] 获取数据长度失败:', e) } let statusNum = 0 try { statusNum = response.status as number } catch (e) {} console.log('[searchProducts] 响应状态:', statusNum, '数据条数:', dataLength) let hasError = false try { hasError = response.error != null } catch (e) {} if (hasError) { console.error('[searchProducts] 搜索商品失败:', response.error) return { data: [] as Product[], total: 0, page, limit, hasmore: false } } const rawData = response.data console.log('[searchProducts] rawData:', rawData != null ? 'not null' : 'null') if (rawData == null) { return { data: [] as Product[], total: 0, page, limit, hasmore: false } } const products: Product[] = [] let rawList: any[] = [] try { rawList = rawData as any[] console.log('[searchProducts] rawList长度:', rawList.length) } catch (e) { console.error('[searchProducts] 转换rawList失败:', e) return { data: [] as Product[], total: 0, page, limit, hasmore: false } } for (let i = 0; i < rawList.length; i++) { const item = rawList[i] console.log('[searchProducts] 处理第', i + 1, '个商品') products.push(parseProductFromRaw(item)) } let totalNum = 0 try { totalNum = response.total as number } catch (e) {} let hasmoreVal = false try { hasmoreVal = response.hasmore as boolean } catch (e) {} return { data: products, total: totalNum > 0 ? totalNum : products.length, page, limit, hasmore: hasmoreVal } } 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 encodedKeyword = encodeURIComponent(keyword) const response = await supa .from('ml_shops') .select('*', { count: 'exact' }) .ilike('shop_name', `%${encodedKeyword}%`) .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 // 手动创建 Shop 对象,避免安卓端类型转换错误 const shop: Shop = { id: shopObj.getString('id') ?? '', merchant_id: shopObj.getString('merchant_id') ?? '', shop_name: shopObj.getString('shop_name') ?? '', shop_logo: shopObj.getString('shop_logo'), shop_banner: shopObj.getString('shop_banner'), description: shopObj.getString('description'), contact_name: shopObj.getString('contact_name'), contact_phone: shopObj.getString('contact_phone'), rating_avg: shopObj.getNumber('rating_avg'), total_sales: shopObj.getNumber('total_sales'), product_count: shopObj.getNumber('product_count'), total_sales_count: shopObj.getNumber('total_sales_count'), created_at: shopObj.getString('created_at') } shops.push(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 = fixImageUrl(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++) { const fixedUrl = fixImageUrl(arr[j]) if (fixedUrl !== '') images.push(fixedUrl) } } } 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++) { const fixedUrl = fixImageUrl(parsed[j] as string) if (fixedUrl !== '') images.push(fixedUrl) } } } else { const fixedUrl = fixImageUrl(rawUrlStr) if (fixedUrl !== '' && images.indexOf(fixedUrl) === -1) images.push(fixedUrl) } } } 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}`) const viewData = response.data as any[] const parsedProducts: Product[] = [] for (let i = 0; i < viewData.length; i++) { parsedProducts.push(parseProductFromRaw(viewData[i])) } return { data: parsedProducts, 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 = fixImageUrl(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++) { const fixedUrl = fixImageUrl(arr[j]) if (fixedUrl !== '') images.push(fixedUrl) } } } 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++) { const fixedUrl = fixImageUrl(parsed[j] as string) if (fixedUrl !== '') images.push(fixedUrl) } } } else { const fixedUrl = fixImageUrl(rawUrlStr) if (fixedUrl !== '' && images.indexOf(fixedUrl) === -1) images.push(fixedUrl) } } } 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}`) const viewData = response.data as any[] const parsedProducts: Product[] = [] for (let i = 0; i < viewData.length; i++) { parsedProducts.push(parseProductFromRaw(viewData[i])) } return { data: parsedProducts, 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('*') .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(page: number = 1, limit: number = 10): Promise> { try { if (!logConsumerQueryStart('getProductsBySales', 'ml_products_detail_view', '*')) { return emptyProductPage(page, limit) } console.log('[getProductsBySales] 开始获取销量排序商品...') const response = await supa .from('ml_products_detail_view') .select('*', { count: 'exact' }) .eq('status', '1') .order('sale_count', { ascending: false }) .page(page) .limit(limit) .execute() if (response.error != null) { logConsumerQueryFailure('getProductsBySales', 'ml_products_detail_view', response.error) console.error('获取销量排序商品失败:', response.error) return emptyProductPage(page, limit) } const rawData = response.data if (rawData == null) { return emptyProductPage(page, limit) } 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) logConsumerQuerySuccess('getProductsBySales', 'ml_products_detail_view', products.length) return { data: products, total: response.total ?? products.length, page, limit, hasmore: response.hasmore ?? false } } catch (error) { logConsumerQueryFailure('getProductsBySales', 'ml_products_detail_view', error) console.error('获取销量排序商品异常:', error) return emptyProductPage(page, limit) } } // 获取按价格排序的商品(升序:从低到高) async getProductsByPrice(page: number = 1, limit: number = 10, ascending: boolean = true): Promise> { try { if (!logConsumerQueryStart('getProductsByPrice', 'ml_products_detail_view', '*')) { return emptyProductPage(page, limit) } const response = await supa .from('ml_products_detail_view') .select('*', { count: 'exact' }) .eq('status', '1') // 在数据库层面过滤 .order('base_price', { ascending }) .page(page) .limit(limit) .execute() if (response.error != null) { logConsumerQueryFailure('getProductsByPrice', 'ml_products_detail_view', response.error) console.error('获取价格排序商品失败:', response.error) return emptyProductPage(page, limit) } const rawData = response.data if (rawData == null) { return emptyProductPage(page, limit) } 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)) } logConsumerQuerySuccess('getProductsByPrice', 'ml_products_detail_view', products.length) return { data: products, total: response.total ?? products.length, page, limit, hasmore: response.hasmore ?? false } } catch (error) { logConsumerQueryFailure('getProductsByPrice', 'ml_products_detail_view', error) console.error('获取价格排序商品异常:', error) return emptyProductPage(page, limit) } } // 获取新品(按创建时间排序,最新的在前) async getProductsByNewest(page: number = 1, limit: number = 10): Promise> { try { if (!logConsumerQueryStart('getProductsByNewest', 'ml_products_detail_view', '*')) { return emptyProductPage(page, limit) } console.log('[getProductsByNewest] 开始获取新品...') const response = await supa .from('ml_products_detail_view') .select('*', { count: 'exact' }) .eq('status', '1') .order('is_new', { ascending: false }) .order('created_at', { ascending: false }) .page(page) .limit(limit) .execute() if (response.error != null) { logConsumerQueryFailure('getProductsByNewest', 'ml_products_detail_view', response.error) console.error('获取新品失败:', response.error) return emptyProductPage(page, limit) } const rawData = response.data if (rawData == null) { return emptyProductPage(page, limit) } 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('[getProductsByNewest] 返回商品数:', products.length) logConsumerQuerySuccess('getProductsByNewest', 'ml_products_detail_view', products.length) return { data: products, total: response.total ?? products.length, page, limit, hasmore: response.hasmore ?? false } } catch (error) { logConsumerQueryFailure('getProductsByNewest', 'ml_products_detail_view', error) console.error('获取新品异常:', error) return emptyProductPage(page, limit) } } // 获取推荐商品(is_featured=true) async getRecommendedProducts(limit: number = 10): Promise { try { if (!logConsumerQueryStart('getRecommendedProducts', 'ml_products_detail_view', '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')) { return [] } 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) { logConsumerQueryFailure('getRecommendedProducts', 'ml_products_detail_view', response.error) 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 } logConsumerQuerySuccess('getRecommendedProducts', 'ml_products_detail_view', products.length) return products } catch (error) { logConsumerQueryFailure('getRecommendedProducts', 'ml_products_detail_view', error) console.error('获取推荐商品异常:', error) return [] } } async getRecommendProducts(params: UTSJSONObject): Promise> { try { const categoryId = params.getString('categoryId') ?? '' const limitRaw = params.getNumber('limit') const offsetRaw = params.getNumber('offset') const limit = limitRaw != null && limitRaw > 0 ? limitRaw : 8 const offset = offsetRaw != null && offsetRaw >= 0 ? offsetRaw : 0 const excludeProductIdsRaw = params.get('excludeProductIds') const excludeProductIds: Array = [] if (excludeProductIdsRaw != null) { const normalized = JSON.parse(JSON.stringify(excludeProductIdsRaw)) if (Array.isArray(normalized)) { for (let i = 0; i < normalized.length; i++) { const item = normalized[i] if (item != null) { const itemText = '' + item if (itemText !== '') { excludeProductIds.push(itemText) } } } } } const collected: Array = [] const collectedIds: Array = [] const targetCount = offset + limit const fetchLimit = limit > 20 ? limit : 20 const appendRows = (rows: any[]): void => { for (let i = 0; i < rows.length; i++) { const product = parseProductFromRaw(rows[i]) const productId = product.id ?? '' if (productId === '') { continue } if (excludeProductIds.indexOf(productId) >= 0) { continue } if (collectedIds.indexOf(productId) >= 0) { continue } collectedIds.push(productId) collected.push(JSON.parse(JSON.stringify(product)) as UTSJSONObject) } } const fetchBatch = async (preferCategory: boolean): Promise => { let page = 1 while (collected.length < targetCount && page <= 10) { let rows: any[] = [] if (preferCategory && categoryId !== '') { const response = await this.getProductsByCategory(categoryId, page, fetchLimit) rows = response.data as any[] } else { const response = await this.getProductsBySales(page, fetchLimit) rows = response.data as any[] } if (rows == null) { return true } if (rows.length === 0) { return true } appendRows(rows) if (rows.length < fetchLimit) { return true } page += 1 } return true } if (categoryId !== '') { await fetchBatch(true) } if (collected.length < targetCount) { await fetchBatch(false) } const result: Array = [] const end = collected.length < targetCount ? collected.length : targetCount for (let i = offset; i < end; i++) { result.push(collected[i]) } return result } 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 { if (!logConsumerQueryStart('getDiscountProducts', 'ml_products_detail_view', '(none)')) { return [] as Product[] } logConsumerQuerySuccess('getDiscountProducts', 'ml_products_detail_view', 0) 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() const merchantIds: string[] = [] // 遍历 productMap 获取 merchant_id productMap.forEach((p: any, pid: string) => { let mid: string = '' if (p instanceof UTSJSONObject) { mid = p.getString('merchant_id') ?? '' } else { const pObj = JSON.parse(JSON.stringify(p)) as UTSJSONObject mid = pObj.getString('merchant_id') ?? '' } if (mid !== '' && !merchantIds.includes(mid)) { merchantIds.push(mid) } }) if (merchantIds.length > 0) { const merchantIdsAny: 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) { if (typeof specRaw === 'string') { productSpec = specRaw } else if (specRaw instanceof UTSJSONObject) { const keys = ['规格', '颜色', '尺码', '容量', '版本', '型号'] const result: string[] = [] for (let k = 0; k < keys.length; k++) { const key = keys[k] const val = specRaw.get(key) if (val != null && val !== '') { result.push(`${val}`) } } if (result.length > 0) { productSpec = result.join(' ') } else { const allKeys = UTSJSONObject.keys(specRaw) const parts: string[] = [] for(let k = 0; k < allKeys.length; k++) { let val = specRaw.get(allKeys[k]) if (val != null) { parts.push(`${val}`) } } productSpec = parts.join(' ') } } else { try { let jsonStr = JSON.stringify(specRaw) productSpec = jsonStr.replace(/["{}]/g, '').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) { if (typeof specRaw === 'string') { productSpec = specRaw } else if (specRaw instanceof UTSJSONObject) { const keys = ['规格', '颜色', '尺码', '容量', '版本', '型号'] const result: string[] = [] for (let k = 0; k < keys.length; k++) { const key = keys[k] const val = specRaw.get(key) if (val != null && val !== '') { result.push(`${val}`) } } if (result.length > 0) { productSpec = result.join(' ') } else { const allKeys = UTSJSONObject.keys(specRaw) const parts: string[] = [] for(let k = 0; k < allKeys.length; k++) { let val = specRaw.get(allKeys[k]) if (val != null) { parts.push(`${val}`) } } productSpec = parts.join(' ') } } else { try { let jsonStr = JSON.stringify(specRaw) productSpec = jsonStr.replace(/["{}]/g, '').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, page: number = 1, pageSize: number = 20): Promise { try { console.log('[getChatMessages] 开始获取聊天记录,merchantId:', merchantId, 'page:', page) const userId = this.getCurrentUserId() if (userId == null) return [] const fromIndex = (page - 1) * pageSize const toIndex = fromIndex + pageSize - 1 // 使用 or 组合精确条件查询:(我发给商家) OR (商家发给我) const queryStr = `and(sender_id.eq.${userId},receiver_id.eq.${merchantId}),and(sender_id.eq.${merchantId},receiver_id.eq.${userId})` const response = await supa .from('ml_chat_messages') .select('*') .or(queryStr) .order('created_at', { ascending: false }) // 最新在前 .range(fromIndex, toIndex) .execute() if (response.error != null) { console.error('getChatMessages 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 '' return val.toString() } const getSafeBoolean = (key: string): boolean => { const val = msgObj.get(key) if (val == null) return false if (typeof val == 'boolean') return val as boolean return (val.toString() == '1' || val.toString() == 'true') } 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 { console.log('[batchUpdateCartItemSelection] 开始批量更新') console.log('[batchUpdateCartItemSelection] cartItemIds:', JSON.stringify(cartItemIds)) console.log('[batchUpdateCartItemSelection] cartItemIds length:', cartItemIds.length) console.log('[batchUpdateCartItemSelection] selected:', selected) 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() console.log('[batchUpdateCartItemSelection] response.error:', response.error) console.log('[batchUpdateCartItemSelection] response.data:', JSON.stringify(response.data)) 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') ?? '', latitude: itemObj.getNumber('latitude') ?? 0, longitude: itemObj.getNumber('longitude') ?? 0, coordinate_type: itemObj.getString('coordinate_type') ?? 'gcj02', 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, latitude: address.latitude ?? null, longitude: address.longitude ?? null, coordinate_type: address.coordinate_type ?? 'gcj02', 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 if (address.latitude != null) updateData['latitude'] = address.latitude if (address.longitude != null) updateData['longitude'] = address.longitude if (address.coordinate_type != null) updateData['coordinate_type'] = address.coordinate_type 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 { console.log('[confirmReceipt] 开始确认收货, orderId:', orderId) try { const userId = this.getCurrentUserId() console.log('[confirmReceipt] userId:', userId) if (userId == null) { return { success: false, error: '用户未登录' } } const updateData = new UTSJSONObject() updateData.set('order_status', 4) updateData.set('delivered_at', new Date().toISOString()) updateData.set('completed_at', new Date().toISOString()) updateData.set('updated_at', new Date().toISOString()) console.log('[confirmReceipt] 准备更新订单状态, updateData:', JSON.stringify(updateData)) const response = await supa .from('ml_orders') .update(updateData) .eq('id', orderId) .eq('user_id', userId) .execute() console.log('[confirmReceipt] response.status:', response.status) console.log('[confirmReceipt] response.error:', response.error) console.log('[confirmReceipt] response.data:', JSON.stringify(response.data)) // 检查 HTTP 状态码 if (response.status != null && response.status >= 400) { // 尝试从 response.data 中提取错误信息 let errorMsg = '请求失败' if (response.data != null) { try { const errorData = response.data as UTSJSONObject const msg = errorData.getString('message') if (msg != null) { errorMsg = msg } } catch (e) { // ignore } } console.log('[confirmReceipt] HTTP错误:', response.status, errorMsg) return { success: false, error: errorMsg } } if (response.error != null) { return { success: false, error: response.error.message } } // 检查 response.data 是否包含错误代码 if (response.data != null) { try { const respData = response.data as UTSJSONObject const errorCode = respData.getString('code') if (errorCode != null) { const errorMsg = respData.getString('message') ?? '数据库错误' console.log('[confirmReceipt] 数据库错误:', errorCode, errorMsg) return { success: false, error: errorMsg } } } catch (e) { // ignore } } // 检查是否有数据被更新 if (response.data == null || (Array.isArray(response.data) && response.data.length === 0)) { console.log('[confirmReceipt] 没有数据被更新,可能订单不存在或无权限') return { success: false, error: '订单不存在或无权限更新' } } console.log('[confirmReceipt] 确认收货成功') return { success: true } } catch (e: any) { console.error('[confirmReceipt] 异常:', e) return { success: false, error: e.message } } } // 取消订单 async cancelOrder(orderId: string, reason: string = '用户取消订单'): Promise { return await this.cancelUnifiedOrder(orderId, 'goods', reason) } // 删除订单 async deleteOrder(orderId: string): Promise { return await this.softDeleteUnifiedOrderForConsumer(orderId, 'goods') } async softDeleteOrderForConsumer(orderId: string): Promise { return await this.softDeleteUnifiedOrderForConsumer(orderId, 'goods') } // 确认收货 async confirmOrderReceived(orderId: string): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { return false } const response = await supa .from('ml_orders') .update({ order_status: 4, shipping_status: 3, updated_at: new Date().toISOString() }) .eq('id', orderId) .eq('user_id', userId) .execute() if (response.error != null) { console.error('确认收货失败:', response.error) return false } return true } catch (e) { console.error('确认收货异常:', e) return false } } // 删除地址 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', ORDER_STATUS_PENDING) orderPayload.set('payment_status', PAYMENT_STATUS_UNPAID) orderPayload.set('pay_expire_at', new Date(Date.now() + ORDER_PAY_TIMEOUT_SECONDS * 1000).toISOString()) orderPayload.set('consumer_deleted_at', null) 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)) if (orderData.items == null) { console.error('[CreateOrder] orderData.items 为 null!') return orderId } const rawItems = orderData.items as any[] console.log('[CreateOrder] rawItems 长度:', rawItems.length) if (rawItems.length === 0) { console.warn('[CreateOrder] rawItems 为空数组,没有商品项需要插入') return orderId } for(let i = 0; i < rawItems.length; i++) { const rawItem = rawItems[i] const itemStr = JSON.stringify(rawItem) 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 iPriceRaw = item.getNumber('price') ?? 0 const iMemberPrice = item.getNumber('member_price') ?? 0 // 优先使用会员价 const iPrice = (iMemberPrice > 0 && iMemberPrice < iPriceRaw) ? iMemberPrice : iPriceRaw 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 ensurePayExpireAt(orderId: string): Promise { return await this.ensureUnifiedPayExpireAt(orderId, 'goods') } async markOrderPaymentCancelled(orderId: string): Promise { return await this.markUnifiedOrderPaymentCancelled(orderId, 'goods') } async expireOrder(orderId: string): Promise { return await this.expireUnifiedOrder(orderId, 'goods') } // 批量通过店铺创建订单 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 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 // 优先使用会员价 let itPrice = it.getNumber('price') ?? 0 const itMemberPrice = it.getNumber('member_price') ?? 0 if (itMemberPrice > 0 && itMemberPrice < itPrice) { itPrice = itMemberPrice } 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 // 优先使用会员价 let siPrice = sItem.getNumber('price') ?? 0 const siMemberPrice = sItem.getNumber('member_price') ?? 0 if (siMemberPrice > 0 && siMemberPrice < siPrice) { siPrice = siMemberPrice } 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) .is('consumer_deleted_at', null) .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 } // 修复订单项中的图片URL const orders = data as any[] for (let i = 0; i < orders.length; i++) { const order = orders[i] const orderStr = JSON.stringify(order) const orderObj = JSON.parse(orderStr) as UTSJSONObject const itemsRaw = orderObj.get('ml_order_items') if (itemsRaw != null && Array.isArray(itemsRaw)) { const items = itemsRaw as any[] for (let j = 0; j < items.length; j++) { const item = items[j] const itemStr = JSON.stringify(item) const itemObj = JSON.parse(itemStr) as UTSJSONObject const imgUrl = itemObj.getString('image_url') if (imgUrl != null) { itemObj['image_url'] = fixImageUrl(imgUrl) } const prodImg = itemObj.getString('product_image') if (prodImg != null) { itemObj['product_image'] = fixImageUrl(prodImg) } items[j] = itemObj } orderObj['ml_order_items'] = items orders[i] = orderObj } } return orders } catch (error) { console.error('获取订单列表异常:', error) const empty: any[] = [] return empty } } private normalizeOrderList(orderList: UTSJSONObject[]): UTSJSONObject[] { for (let i = 0; i < orderList.length; i++) { const order = orderList[i] const orderStr = JSON.stringify(order) const orderObj = JSON.parse(orderStr) as UTSJSONObject const itemsRaw = orderObj.get('ml_order_items') if (itemsRaw != null && Array.isArray(itemsRaw)) { const items = itemsRaw as UTSJSONObject[] for (let j = 0; j < items.length; j++) { const item = items[j] const itemStr = JSON.stringify(item) const itemObj = JSON.parse(itemStr) as UTSJSONObject const imgUrl = itemObj.getString('image_url') if (imgUrl != null) { itemObj['image_url'] = fixImageUrl(imgUrl) } const prodImg = itemObj.getString('product_image') if (prodImg != null) { itemObj['product_image'] = fixImageUrl(prodImg) } items[j] = itemObj } orderObj['ml_order_items'] = items orderList[i] = orderObj } } return orderList } private appendUniqueString(list: string[], value: string | null): void { if (value == null || value == '') { return } for (let i = 0; i < list.length; i++) { if (list[i] == value) { return } } list.push(value) } private async getKeywordMatchedOrderIds(userId: string, keyword: string): Promise { const matchedIds: string[] = [] const keywordPattern = '%' + keyword + '%' try { const orderResponse = await supa .from('ml_orders') .select('id') .eq('user_id', userId) .ilike('order_no', keywordPattern) .limit(200) .execute() if (orderResponse.error == null && orderResponse.data != null && Array.isArray(orderResponse.data)) { const orderRows = orderResponse.data as UTSJSONObject[] for (let i = 0; i < orderRows.length; i++) { const rowStr = JSON.stringify(orderRows[i]) const rowObj = JSON.parse(rowStr) as UTSJSONObject this.appendUniqueString(matchedIds, rowObj.getString('id')) } } } catch (error) { console.error('[getKeywordMatchedOrderIds] 订单号搜索异常:', error) } try { const itemResponse = await supa .from('ml_order_items') .select('order_id') .ilike('product_name', keywordPattern) .limit(200) .execute() if (itemResponse.error == null && itemResponse.data != null && Array.isArray(itemResponse.data)) { const itemRows = itemResponse.data as UTSJSONObject[] for (let i = 0; i < itemRows.length; i++) { const rowStr = JSON.stringify(itemRows[i]) const rowObj = JSON.parse(rowStr) as UTSJSONObject this.appendUniqueString(matchedIds, rowObj.getString('order_id')) } } } catch (error) { console.error('[getKeywordMatchedOrderIds] 商品名搜索异常:', error) } return matchedIds } private normalizeServiceStatus(status: string): string { if (status == 'ORDER_CREATED') return 'created' if (status == 'ORDER_ASSIGNED') return 'assigned' if (status == 'ORDER_ACCEPTED') return 'accepted' if (status == 'ORDER_REJECTED') return 'rejected' if (status == 'ORDER_CHECKED_IN') return 'arrived' if (status == 'ORDER_IN_SERVICE') return 'in_service' if (status == 'ORDER_COMPLETED' || status == 'ACCEPTANCE_PENDING') return 'pending_acceptance' if (status == 'ACCEPTED') return 'accepted_by_user' if (status == 'SETTLEMENT_READY' || status == 'ARCHIVED') return 'settled' if (status == 'ORDER_CANCELLED') return 'cancelled' if (status == 'ORDER_EXCEPTION' || status == 'ACCEPTANCE_REJECTED') return 'exception' if (status == 'created' || status == 'submitted') return 'created' if (status == 'paid') return 'paid' if (status == 'assigned' || status == 'pending_dispatch' || status == 'pending_assignment') return 'assigned' if (status == 'accepted' || status == 'pending_accept') return 'accepted' if (status == 'rejected') return 'rejected' if (status == 'departed' || status == 'waiting_departure' || status == 'on_the_way') return 'departed' if (status == 'arrived' || status == 'checked_in') return 'arrived' if (status == 'in_service' || status == 'serving') return 'in_service' if (status == 'completed') return 'completed' if (status == 'pending_acceptance' || status == 'pending_confirm' || status == 'pending_submit') return 'pending_acceptance' if (status == 'accepted_by_user') return 'accepted_by_user' if (status == 'reviewed') return 'reviewed' if (status == 'settled') return 'settled' if (status == 'cancelled') return 'cancelled' return 'exception' } private getUnifiedServiceStatusNumber(status: string): number { const normalizedStatus = this.normalizeServiceStatus(status) if (normalizedStatus == 'created') return 1 if (normalizedStatus == 'paid' || normalizedStatus == 'assigned') return 2 if (normalizedStatus == 'accepted' || normalizedStatus == 'departed') return 3 if (normalizedStatus == 'arrived' || normalizedStatus == 'in_service') return 4 if (normalizedStatus == 'completed' || normalizedStatus == 'pending_acceptance' || normalizedStatus == 'accepted_by_user' || normalizedStatus == 'reviewed' || normalizedStatus == 'settled') return 5 if (normalizedStatus == 'cancelled' || normalizedStatus == 'rejected' || normalizedStatus == 'exception') return 8 return 3 } private matchesServiceStatusTab(status: string, statusTab: string): boolean { if (statusTab == 'all') return true const normalizedStatus = this.normalizeServiceStatus(status) if (statusTab == 'pending') return normalizedStatus == 'created' if (statusTab == 'accepted') return normalizedStatus == 'paid' || normalizedStatus == 'assigned' if (statusTab == 'scheduled') return normalizedStatus == 'accepted' || normalizedStatus == 'departed' if (statusTab == 'inservice') return normalizedStatus == 'arrived' || normalizedStatus == 'in_service' if (statusTab == 'completed') return normalizedStatus == 'completed' || normalizedStatus == 'pending_acceptance' || normalizedStatus == 'accepted_by_user' || normalizedStatus == 'reviewed' || normalizedStatus == 'settled' if (statusTab == 'aftersale') return false if (statusTab == 'inprogress') return normalizedStatus == 'paid' || normalizedStatus == 'assigned' || normalizedStatus == 'accepted' || normalizedStatus == 'departed' || normalizedStatus == 'arrived' || normalizedStatus == 'in_service' return true } private formatServiceAppointmentText(value: string): string { if (value == '') { return '' } if (value.indexOf('上午') >= 0 || value.indexOf('下午') >= 0 || value.indexOf('晚上') >= 0) { return value } const parsed = Date.parse(value) if (!isNaN(parsed)) { const date = new Date(parsed) let year = date.getFullYear() const currentYear = new Date().getFullYear() if (year < currentYear - 1) { year = currentYear } const month = String(date.getMonth() + 1).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0') const hour = String(date.getHours()).padStart(2, '0') const minute = String(date.getMinutes()).padStart(2, '0') return year + '-' + month + '-' + day + ' ' + hour + ':' + minute } const monthDayMatch = value.match(/(\d{2})\/(\d{2})/) const timeMatch = value.match(/(\d{2}:\d{2}(\s*-\s*\d{2}:\d{2})?)/) if (monthDayMatch != null) { const month = monthDayMatch[1] ?? '' const day = monthDayMatch[2] ?? '' const timeText = timeMatch != null ? (timeMatch[1] ?? '') : '' if (month != '' && day != '') { return String(new Date().getFullYear()) + '-' + month + '-' + day + (timeText != '' ? ' ' + timeText.replace(/\s+/g, '') : '') } } return value.replace('T', ' ') } private buildUnifiedServiceOrder(rawOrder: any): UTSJSONObject { const orderObj = JSON.parse(JSON.stringify(rawOrder)) as UTSJSONObject const addressSnapshotRaw = orderObj.get('address_snapshot_json') const serviceSnapshotRaw = orderObj.get('service_snapshot_json') let addressObj: UTSJSONObject | null = null let serviceObj: UTSJSONObject | null = null try { if (addressSnapshotRaw != null) { const addressText = JSON.stringify(addressSnapshotRaw) if (addressText.startsWith('"')) { addressObj = JSON.parse(orderObj.getString('address_snapshot_json') ?? '{}') as UTSJSONObject } else { addressObj = JSON.parse(addressText) as UTSJSONObject } } } catch (e) { addressObj = null } try { if (serviceSnapshotRaw != null) { const serviceText = JSON.stringify(serviceSnapshotRaw) if (serviceText.startsWith('"')) { serviceObj = JSON.parse(orderObj.getString('service_snapshot_json') ?? '{}') as UTSJSONObject } else { serviceObj = JSON.parse(serviceText) as UTSJSONObject } } } catch (e) { serviceObj = null } const normalizedStatus = this.getUnifiedServiceStatusNumber(orderObj.getString('status') ?? '') const fullAddress = addressObj != null ? (addressObj.getString('fullAddress') ?? '') : '' const providerName = orderObj.getString('staff_name') ?? '' const serviceName = orderObj.getString('service_name') ?? (serviceObj != null ? (serviceObj.getString('serviceName') ?? '') : '') const servicePrice = serviceObj != null ? (serviceObj.getNumber('price') ?? 0) : 0 const serviceInfo = new UTSJSONObject() serviceInfo.set('service_name', serviceName) serviceInfo.set('service_image', '/static/images/default.png') serviceInfo.set('appointment_time', this.formatServiceAppointmentText(orderObj.getString('appointment_time') ?? '')) serviceInfo.set('address', fullAddress) serviceInfo.set('contact_name', orderObj.getString('contact_name') ?? '') serviceInfo.set('contact_phone', orderObj.getString('contact_phone') ?? '') serviceInfo.set('provider_name', providerName) const unifiedOrder = new UTSJSONObject() const rawPaymentStatus = orderObj.getNumber('payment_status') let paymentStatus = rawPaymentStatus ?? 0 if (paymentStatus <= 0) { if (normalizedStatus == ORDER_STATUS_PENDING) { paymentStatus = PAYMENT_STATUS_UNPAID } else if (normalizedStatus == ORDER_STATUS_CANCELLED || normalizedStatus == ORDER_STATUS_TIMEOUT_LEGACY) { paymentStatus = PAYMENT_STATUS_TIMEOUT } else { paymentStatus = 2 } } unifiedOrder.set('id', orderObj.getString('id') ?? '') unifiedOrder.set('order_no', orderObj.getString('order_no') ?? '') unifiedOrder.set('biz_type', 'service') unifiedOrder.set('source', 'service') unifiedOrder.set('status', normalizedStatus) unifiedOrder.set('order_status', normalizedStatus) unifiedOrder.set('payment_status', paymentStatus) unifiedOrder.set('pay_expire_at', orderObj.getString('pay_expire_at') ?? '') unifiedOrder.set('cancel_reason', orderObj.getString('cancel_reason') ?? '') unifiedOrder.set('created_at', orderObj.getString('created_at') ?? '') unifiedOrder.set('create_time', orderObj.getString('created_at') ?? '') unifiedOrder.set('product_amount', servicePrice) unifiedOrder.set('shipping_fee', 0) unifiedOrder.set('total_amount', servicePrice) unifiedOrder.set('paid_amount', servicePrice) unifiedOrder.set('merchant_id', orderObj.getString('current_staff_id') ?? '') unifiedOrder.set('shop_name', providerName != '' ? providerName : '康养上门服务') unifiedOrder.set('service_info', serviceInfo) unifiedOrder.set('ml_order_items', [] as UTSJSONObject[]) return unifiedOrder } private async getServiceOrdersByCursor(params: GetUnifiedOrdersByCursorParams): Promise { const emptyResult: GetUnifiedOrdersByCursorResult = { list: [] as UTSJSONObject[], nextCursor: '', hasMore: false } try { const userId = this.getCurrentUserId() if (userId == null || userId == '') { return emptyResult } let query = supa .from('hss_service_orders') .select('*') .eq('user_id', userId) .is('consumer_deleted_at', null) .order('created_at', { ascending: false }) .limit(200) const cursor = params.cursor.trim() if (cursor != '') { query = query.lt('created_at', cursor) } const response = await query.execute() if (response.error != null || response.data == null || !Array.isArray(response.data)) { return emptyResult } const keyword = params.keyword.trim() const filtered = [] as UTSJSONObject[] const rawList = response.data as any[] for (let i = 0; i < rawList.length; i++) { const rawItem = rawList[i] const rawObj = JSON.parse(JSON.stringify(rawItem)) as UTSJSONObject const orderNo = rawObj.getString('order_no') ?? '' const serviceName = rawObj.getString('service_name') ?? '' const contactName = rawObj.getString('contact_name') ?? '' const appointmentTime = rawObj.getString('appointment_time') ?? '' const addressSnapshot = rawObj.getString('address_snapshot_json') ?? '' const matchedKeyword = keyword == '' || orderNo.indexOf(keyword) >= 0 || serviceName.indexOf(keyword) >= 0 || contactName.indexOf(keyword) >= 0 || appointmentTime.indexOf(keyword) >= 0 || addressSnapshot.indexOf(keyword) >= 0 if (!matchedKeyword) { continue } if (!this.matchesServiceStatusTab(rawObj.getString('status') ?? '', params.statusTab)) { continue } filtered.push(this.buildUnifiedServiceOrder(rawItem)) } const limit = params.limit <= 0 ? 10 : params.limit let hasMore = false let list = filtered if (filtered.length > limit) { hasMore = true list = filtered.slice(0, limit) } let nextCursor = '' if (list.length > 0) { const lastOrder = list[list.length - 1] nextCursor = lastOrder.getString('created_at') ?? '' } return { list, nextCursor, hasMore } } catch (error) { console.error('[getServiceOrdersByCursor] 查询异常:', error) return emptyResult } } async getUnifiedOrdersByCursor(params: GetUnifiedOrdersByCursorParams): Promise { const emptyResult: GetUnifiedOrdersByCursorResult = { list: [] as UTSJSONObject[], nextCursor: '', hasMore: false } try { if (params.bizType == 'service') { return await this.getServiceOrdersByCursor(params) } if (params.bizType == 'goods') { const goodsResult = await this.getOrdersByCursor({ cursor: params.cursor, limit: params.limit, status: params.statusTab == 'pending' ? 1 : params.statusTab == 'shipping' ? 2 : params.statusTab == 'delivering' ? 3 : params.statusTab == 'completed' ? 4 : params.statusTab == 'aftersale' ? 6 : 0, keyword: params.keyword }) return { list: goodsResult.list, nextCursor: goodsResult.nextCursor, hasMore: goodsResult.hasMore } } const goodsStatus = params.statusTab == 'pending' ? 1 : params.statusTab == 'completed' ? 4 : params.statusTab == 'aftersale' ? 6 : 0 const goodsResult = await this.getOrdersByCursor({ cursor: '', limit: 200, status: goodsStatus, keyword: params.keyword }) const serviceResult = await this.getServiceOrdersByCursor({ cursor: '', limit: 200, bizType: 'service', statusTab: params.statusTab, keyword: params.keyword }) let mergedList = [] as UTSJSONObject[] for (let i = 0; i < goodsResult.list.length; i++) { const goodsItem = goodsResult.list[i] const status = goodsItem.getNumber('order_status') ?? 0 const includeGoods = params.statusTab == 'all' || (params.statusTab == 'pending' && status == 1) || (params.statusTab == 'completed' && status == 4) || (params.statusTab == 'aftersale' && (status == 6 || status == 7)) || (params.statusTab == 'inprogress' && (status == 2 || status == 3)) if (includeGoods) { mergedList.push(goodsItem) } } for (let i = 0; i < serviceResult.list.length; i++) { mergedList.push(serviceResult.list[i]) } mergedList.sort((left: UTSJSONObject, right: UTSJSONObject): number => { const leftTime = left.getString('created_at') ?? '' const rightTime = right.getString('created_at') ?? '' if (leftTime == rightTime) return 0 return leftTime > rightTime ? -1 : 1 }) const cursor = params.cursor.trim() if (cursor != '') { const cursorFiltered = [] as UTSJSONObject[] for (let i = 0; i < mergedList.length; i++) { const createdAt = mergedList[i].getString('created_at') ?? '' if (createdAt < cursor) { cursorFiltered.push(mergedList[i]) } } mergedList = cursorFiltered } const limit = params.limit <= 0 ? 10 : params.limit let hasMore = false let list = mergedList if (mergedList.length > limit) { hasMore = true list = mergedList.slice(0, limit) } let nextCursor = '' if (list.length > 0) { nextCursor = list[list.length - 1].getString('created_at') ?? '' } return { list, nextCursor, hasMore } } catch (error) { console.error('[getUnifiedOrdersByCursor] 查询异常:', error) return emptyResult } } async getServiceOrderStatusCounts(): Promise { const counts = new UTSJSONObject() counts.set('all', 0) counts.set('pending', 0) counts.set('accepted', 0) counts.set('scheduled', 0) counts.set('inservice', 0) counts.set('completed', 0) counts.set('aftersale', 0) try { const userId = this.getCurrentUserId() if (userId == null || userId == '') { return counts } const response = await supa .from('hss_service_orders') .select('status') .eq('user_id', userId) .is('consumer_deleted_at', null) .limit(500) .execute() if (response.error != null || response.data == null || !Array.isArray(response.data)) { return counts } const rawList = response.data as any[] counts.set('all', rawList.length) for (let i = 0; i < rawList.length; i++) { const rawObj = JSON.parse(JSON.stringify(rawList[i])) as UTSJSONObject const normalizedStatus = this.normalizeServiceStatus(rawObj.getString('status') ?? '') if (normalizedStatus == 'created') { counts.set('pending', (counts.getNumber('pending') ?? 0) + 1) } else if (normalizedStatus == 'paid' || normalizedStatus == 'assigned') { counts.set('accepted', (counts.getNumber('accepted') ?? 0) + 1) } else if (normalizedStatus == 'accepted' || normalizedStatus == 'departed') { counts.set('scheduled', (counts.getNumber('scheduled') ?? 0) + 1) } else if (normalizedStatus == 'arrived' || normalizedStatus == 'in_service') { counts.set('inservice', (counts.getNumber('inservice') ?? 0) + 1) } else if (normalizedStatus == 'completed' || normalizedStatus == 'pending_acceptance' || normalizedStatus == 'accepted_by_user' || normalizedStatus == 'reviewed' || normalizedStatus == 'settled') { counts.set('completed', (counts.getNumber('completed') ?? 0) + 1) } } return counts } catch (error) { console.error('[getServiceOrderStatusCounts] 统计异常:', error) return counts } } async getOrdersByCursor(params: GetOrdersByCursorParams): Promise { const emptyResult: GetOrdersByCursorResult = { list: [] as UTSJSONObject[], nextCursor: '', hasMore: false } try { const userId = this.getCurrentUserId() if (userId == null || userId == '') { return emptyResult } const limit = params.limit <= 0 ? 10 : params.limit const queryLimit = limit + 1 let query = supa .from('ml_orders') .select('*, ml_order_items(*), ml_shops(shop_name)') .eq('user_id', userId) .is('consumer_deleted_at', null) .order('created_at', { ascending: false }) .limit(queryLimit) if (params.status > 0) { if (params.status == 6) { query = query.in('order_status', [6, 7]) } else { query = query.eq('order_status', params.status) } } const keyword = params.keyword.trim() if (keyword != '') { const matchedOrderIds = await this.getKeywordMatchedOrderIds(userId, keyword) if (matchedOrderIds.length == 0) { return emptyResult } query = query.in('id', matchedOrderIds) } const cursor = params.cursor.trim() if (cursor != '') { query = query.lt('created_at', cursor) } const response = await query.execute() if (response.error != null) { console.error('[getOrdersByCursor] 查询失败:', response.error) throw response.error } const rawData = response.data if (rawData == null || !Array.isArray(rawData)) { return emptyResult } const rawList = rawData as UTSJSONObject[] let hasMore = false let list = rawList if (rawList.length > limit) { hasMore = true list = rawList.slice(0, limit) } const normalizedList = this.normalizeOrderList(list) let nextCursor = '' if (normalizedList.length > 0) { const lastOrder = normalizedList[normalizedList.length - 1] const lastOrderStr = JSON.stringify(lastOrder) const lastOrderObj = JSON.parse(lastOrderStr) as UTSJSONObject nextCursor = lastOrderObj.getString('created_at') ?? '' } return { list: normalizedList, nextCursor: nextCursor, hasMore: hasMore } } catch (error) { console.error('[getOrdersByCursor] 查询异常:', error) return emptyResult } } async getOrderCounts(): Promise { const emptyCounts: OrderCountsResult = { all: 0, pending: 0, shipping: 0, delivering: 0, completed: 0, aftersale: 0, cancelled: 0 } try { const userId = this.getCurrentUserId() if (userId == null || userId == '') { return emptyCounts } const queryCount = async (statusList: number[]): Promise => { let query = supa .from('ml_orders') .select('*', { count: 'exact' }) .eq('user_id', userId) .is('consumer_deleted_at', null) .limit(1) if (statusList.length == 1) { query = query.eq('order_status', statusList[0]) } else if (statusList.length > 1) { query = query.in('order_status', statusList) } const response = await query.execute() if (response.error != null) { console.error('[getOrderCounts] 统计失败:', response.error) return 0 } return response.total ?? 0 } const all = await queryCount([]) const pending = await queryCount([1]) const shipping = await queryCount([2]) const delivering = await queryCount([3]) const completed = await queryCount([4]) const aftersale = await queryCount([6, 7]) const cancelled = await queryCount([5]) return { all, pending, shipping, delivering, completed, aftersale, cancelled } } catch (error) { console.error('[getOrderCounts] 统计异常:', error) return emptyCounts } } isUuidOrderId(orderId: string): boolean { if (orderId == null || orderId == '') { return false } const normalized = orderId.toLowerCase() const parts = normalized.split('-') if (parts.length != 5) { return false } const expectedLengths = [8, 4, 4, 4, 12] for (let i = 0; i < parts.length; i++) { const part = parts[i] if (part.length != expectedLengths[i]) { return false } for (let j = 0; j < part.length; j++) { const ch = part.charAt(j) const isDigit = ch >= '0' && ch <= '9' const isHexLower = ch >= 'a' && ch <= 'f' if (!isDigit && !isHexLower) { return false } } } return true } private isServiceOrderSource(orderId: string, source: string): boolean { return source == 'service' || orderId.startsWith('so-') } private getUnifiedOrderTableName(orderId: string, source: string): string { return this.isServiceOrderSource(orderId, source) ? 'hss_service_orders' : 'ml_orders' } private logUnifiedOrderRoute(action: string, orderId: string, source: string, tableName: string): void { console.log('[' + action + '] source =', source) console.log('[' + action + '] orderId =', orderId) console.log('[' + action + '] tableName =', tableName) } private logPostgrestFailure(action: string, response: any): void { console.error('[' + action + '] 请求失败:', response?.error) console.error('[' + action + '] response.data:', JSON.stringify(response?.data)) console.error('[' + action + '] response.status:', response?.status) } private buildUnifiedGoodsOrderResult(rawOrder: any): UTSJSONObject { const orderObj = JSON.parse(JSON.stringify(rawOrder)) as UTSJSONObject const result = new UTSJSONObject() result.set('id', orderObj.get('id') ?? '') result.set('order_no', orderObj.get('order_no') ?? '') result.set('biz_type', 'goods') result.set('source', 'goods') result.set('status', orderObj.get('order_status') ?? 1) 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('cancel_reason', orderObj.get('cancel_reason') ?? '') result.set('pay_expire_at', orderObj.get('pay_expire_at') ?? '') result.set('consumer_deleted_at', orderObj.get('consumer_deleted_at') ?? '') 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('items', orderObj.get('ml_order_items')) result.set('ml_shops', orderObj.get('ml_shops')) 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 } private buildUnifiedServiceOrderDetailResult(rawOrder: any): UTSJSONObject { const orderObj = JSON.parse(JSON.stringify(rawOrder)) as UTSJSONObject const unifiedOrder = this.buildUnifiedServiceOrder(rawOrder) const appointmentTime = this.formatServiceAppointmentText(orderObj.getString('appointment_time') ?? '') const fullAddress = unifiedOrder.get('service_info') let serviceInfo = new UTSJSONObject() if (fullAddress != null) { serviceInfo = JSON.parse(JSON.stringify(fullAddress)) as UTSJSONObject } const contactName = orderObj.getString('contact_name') ?? '' const contactPhone = orderObj.getString('contact_phone') ?? '' const addressText = serviceInfo.getString('address') ?? '' const shippingAddress = new UTSJSONObject() shippingAddress.set('name', contactName) shippingAddress.set('phone', contactPhone) shippingAddress.set('address', addressText) shippingAddress.set('detail', addressText) shippingAddress.set('province', '') shippingAddress.set('city', '') shippingAddress.set('district', '') const item = new UTSJSONObject() item.set('id', orderObj.getString('id') ?? '') item.set('product_id', orderObj.getString('service_id') ?? '') item.set('product_name', serviceInfo.getString('service_name') ?? (orderObj.getString('service_name') ?? '服务订单')) item.set('sku_name', appointmentTime) item.set('spec', appointmentTime) item.set('product_image', serviceInfo.getString('service_image') ?? '/static/images/default.png') item.set('image', serviceInfo.getString('service_image') ?? '/static/images/default.png') item.set('price', unifiedOrder.getNumber('total_amount') ?? 0) item.set('quantity', 1) const items = [] as UTSJSONObject[] items.push(item) unifiedOrder.set('status', unifiedOrder.getNumber('order_status') ?? 1) unifiedOrder.set('shipping_address', shippingAddress) unifiedOrder.set('items', items) unifiedOrder.set('service_info', serviceInfo) unifiedOrder.set('contact_name', contactName) unifiedOrder.set('contact_phone', contactPhone) unifiedOrder.set('appointment_time', appointmentTime) unifiedOrder.set('address', addressText) unifiedOrder.set('payment_method', '') unifiedOrder.set('discount_amount', 0) unifiedOrder.set('paid_at', '') unifiedOrder.set('shipped_at', '') unifiedOrder.set('completed_at', orderObj.getString('completed_at') ?? '') return unifiedOrder } async getUnifiedOrderDetail(orderId: string, source: string): Promise { try { const userId = this.getCurrentUserId() if (userId == null || userId == '') { return null } const tableName = this.getUnifiedOrderTableName(orderId, source) this.logUnifiedOrderRoute('getUnifiedOrderDetail', orderId, source, tableName) if (tableName == 'hss_service_orders') { const response = await supa .from('hss_service_orders') .select('*') .eq('user_id', userId) .is('consumer_deleted_at', null) .or(`id.eq.${orderId},order_no.eq.${orderId}`) .limit(1) .execute() console.log('[getUnifiedOrderDetail] response.data =', JSON.stringify(response.data)) if (response.error != null) { this.logPostgrestFailure('getUnifiedOrderDetail', response) return null } const rawList = response.data as any[] if (rawList == null || rawList.length == 0) { console.log('[getUnifiedOrderDetail] 未找到订单') return null } return this.buildUnifiedServiceOrderDetailResult(rawList[0]) } const response = await supa .from('ml_orders') .select('*, ml_order_items(*), ml_shops(shop_name)') .eq('user_id', userId) .is('consumer_deleted_at', null) .or(`id.eq.${orderId},order_no.eq.${orderId}`) .limit(1) .execute() console.log('[getUnifiedOrderDetail] response.data =', JSON.stringify(response.data)) if (response.error != null) { this.logPostgrestFailure('getUnifiedOrderDetail', response) return null } const rawList = response.data as any[] if (rawList == null || rawList.length == 0) { console.log('[getUnifiedOrderDetail] 未找到订单') return null } return this.buildUnifiedGoodsOrderResult(rawList[0]) } catch (e) { console.error('[getUnifiedOrderDetail] 获取订单详情异常:', e) return null } } async ensureUnifiedPayExpireAt(orderId: string, source: string): Promise { try { const latestOrder = await this.getUnifiedOrderDetail(orderId, source) if (latestOrder == null) { return '' } const currentExpireAt = latestOrder.getString('pay_expire_at') ?? '' if (currentExpireAt != '') { return currentExpireAt } const createdAt = latestOrder.getString('created_at') ?? '' const createdDate = new Date(createdAt) if (createdAt == '' || isNaN(createdDate.getTime())) { return '' } const expireAtIso = new Date(createdDate.getTime() + ORDER_PAY_TIMEOUT_SECONDS * 1000).toISOString() const userId = this.getCurrentUserId() if (userId == null || userId == '') { return '' } const tableName = this.getUnifiedOrderTableName(orderId, source) this.logUnifiedOrderRoute('ensureUnifiedPayExpireAt', orderId, source, tableName) const response = await supa .from(tableName) .update({ pay_expire_at: expireAtIso, updated_at: new Date().toISOString() }) .eq('user_id', userId) .or(`id.eq.${orderId},order_no.eq.${orderId}`) .is('pay_expire_at', null) .execute() if (response.error != null) { this.logPostgrestFailure('ensureUnifiedPayExpireAt', response) return '' } return expireAtIso } catch (e) { console.error('[ensureUnifiedPayExpireAt] 异常:', e) return '' } } async cancelUnifiedOrder(orderId: string, source: string, reason: string = '用户取消订单'): Promise { try { const userId = this.getCurrentUserId() if (userId == null || userId == '') { return false } const tableName = this.getUnifiedOrderTableName(orderId, source) const isService = tableName == 'hss_service_orders' this.logUnifiedOrderRoute('cancelUnifiedOrder', orderId, source, tableName) const nowIso = new Date().toISOString() const updatePayload = new UTSJSONObject() updatePayload.set('payment_status', PAYMENT_STATUS_TIMEOUT) updatePayload.set('cancel_reason', reason) updatePayload.set('cancelled_at', nowIso) updatePayload.set('updated_at', nowIso) if (isService) { updatePayload.set('status', 'cancelled') } else { updatePayload.set('order_status', ORDER_STATUS_CANCELLED) } const response = await supa .from(tableName) .update(updatePayload) .eq('user_id', userId) .or(`id.eq.${orderId},order_no.eq.${orderId}`) .execute() if (response.error != null) { this.logPostgrestFailure('cancelUnifiedOrder', response) return false } return response.data != null && (!Array.isArray(response.data) || response.data.length > 0) } catch (e) { console.error('[cancelUnifiedOrder] 异常:', e) return false } } async expireUnifiedOrder(orderId: string, source: string): Promise { try { const latestOrder = await this.getUnifiedOrderDetail(orderId, source) if (latestOrder == null) { return false } const latestStatus = latestOrder.getNumber('order_status') ?? ORDER_STATUS_PENDING const latestPaymentStatus = latestOrder.getNumber('payment_status') ?? PAYMENT_STATUS_UNPAID const latestCancelReason = latestOrder.getString('cancel_reason') ?? '' const latestExpireAt = latestOrder.getString('pay_expire_at') ?? '' if (latestStatus == ORDER_STATUS_CANCELLED || latestStatus == ORDER_STATUS_TIMEOUT_LEGACY || latestPaymentStatus == PAYMENT_STATUS_TIMEOUT || latestCancelReason.indexOf('超时') >= 0) { return true } let effectiveExpireAt = latestExpireAt if (effectiveExpireAt == '') { effectiveExpireAt = await this.ensureUnifiedPayExpireAt(orderId, source) } const expireMs = effectiveExpireAt != '' ? new Date(effectiveExpireAt).getTime() : 0 if (expireMs <= 0 || expireMs > Date.now()) { return false } return await this.cancelUnifiedOrder(orderId, source, ORDER_TIMEOUT_CANCEL_REASON) } catch (e) { console.error('[expireUnifiedOrder] 异常:', e) return false } } async softDeleteUnifiedOrderForConsumer(orderId: string, source: string): Promise { try { const userId = this.getCurrentUserId() if (userId == null || userId == '') { return false } const tableName = this.getUnifiedOrderTableName(orderId, source) this.logUnifiedOrderRoute('softDeleteUnifiedOrderForConsumer', orderId, source, tableName) const response = await supa .from(tableName) .update({ consumer_deleted_at: new Date().toISOString(), updated_at: new Date().toISOString() }) .eq('user_id', userId) .or(`id.eq.${orderId},order_no.eq.${orderId}`) .execute() if (response.error != null) { this.logPostgrestFailure('softDeleteUnifiedOrderForConsumer', response) return false } return response.data != null && (!Array.isArray(response.data) || response.data.length > 0) } catch (e) { console.error('[softDeleteUnifiedOrderForConsumer] 异常:', e) return false } } async markUnifiedOrderPaymentCancelled(orderId: string, source: string): Promise { try { const userId = this.getCurrentUserId() if (userId == null || userId == '') { return false } const tableName = this.getUnifiedOrderTableName(orderId, source) const isService = tableName == 'hss_service_orders' this.logUnifiedOrderRoute('markUnifiedOrderPaymentCancelled', orderId, source, tableName) const updatePayload = new UTSJSONObject() updatePayload.set('payment_status', PAYMENT_STATUS_UNPAID) updatePayload.set('updated_at', new Date().toISOString()) if (isService) { updatePayload.set('status', 'created') } else { updatePayload.set('order_status', ORDER_STATUS_PENDING) } const response = await supa .from(tableName) .update(updatePayload) .eq('user_id', userId) .or(`id.eq.${orderId},order_no.eq.${orderId}`) .execute() if (response.error != null) { this.logPostgrestFailure('markUnifiedOrderPaymentCancelled', response) return false } return true } catch (e) { console.error('[markUnifiedOrderPaymentCancelled] 异常:', e) return false } } async payUnifiedOrder(orderId: string, source: string, paymentMethod: string, amount: number): Promise { try { const userId = this.getCurrentUserId() if (userId == null || userId == '') { console.error('[payUnifiedOrder] 用户未登录') return false } const latestOrder = await this.getUnifiedOrderDetail(orderId, source) if (latestOrder == null) { console.error('[payUnifiedOrder] 订单不存在,无法支付') return false } const latestStatus = latestOrder.getNumber('order_status') ?? ORDER_STATUS_PENDING const latestPaymentStatus = latestOrder.getNumber('payment_status') ?? PAYMENT_STATUS_UNPAID const latestCancelReason = latestOrder.getString('cancel_reason') ?? '' const latestPayExpireAt = latestOrder.getString('pay_expire_at') ?? '' const expireMs = latestPayExpireAt != '' ? new Date(latestPayExpireAt).getTime() : 0 const isExpired = latestStatus == ORDER_STATUS_CANCELLED || latestStatus == ORDER_STATUS_TIMEOUT_LEGACY || latestPaymentStatus == PAYMENT_STATUS_TIMEOUT || latestCancelReason.indexOf('超时') >= 0 || (expireMs > 0 && expireMs <= Date.now()) if (isExpired) { await this.expireUnifiedOrder(orderId, source) console.error('[payUnifiedOrder] 订单已超时,拒绝支付') return false } if (latestStatus != ORDER_STATUS_PENDING || latestPaymentStatus != PAYMENT_STATUS_UNPAID) { console.error('[payUnifiedOrder] 订单状态已变更,拒绝支付:', latestStatus, latestPaymentStatus) return false } const tableName = this.getUnifiedOrderTableName(orderId, source) const isService = tableName == 'hss_service_orders' this.logUnifiedOrderRoute('payUnifiedOrder', orderId, source, tableName) const nowIso = new Date().toISOString() const updatePayload = new UTSJSONObject() updatePayload.set('payment_status', PAYMENT_STATUS_PAID) updatePayload.set('updated_at', nowIso) if (isService) { updatePayload.set('status', 'paid') } else { updatePayload.set('order_status', ORDER_STATUS_PAID_OR_SHIPPING) updatePayload.set('payment_method', paymentMethod) updatePayload.set('paid_at', nowIso) updatePayload.set('paid_amount', amount) } let query = supa .from(tableName) .update(updatePayload) .eq('user_id', userId) .eq('payment_status', PAYMENT_STATUS_UNPAID) .or(`id.eq.${orderId},order_no.eq.${orderId}`) if (isService) { query = query.eq('status', 'created') } else { query = query.eq('order_status', ORDER_STATUS_PENDING) } if (latestPayExpireAt != '') { query = query.gt('pay_expire_at', nowIso) } const response = await query.execute() if (response.error != null) { this.logPostgrestFailure('payUnifiedOrder', response) return false } if (response.data == null || (Array.isArray(response.data) && response.data.length === 0)) { console.error('[payUnifiedOrder] 没有订单被更新,支付前状态校验未通过') return false } return true } catch (e) { console.error('[payUnifiedOrder] 支付异常:', e) return false } } // 获取订单详情 async getOrderDetail(orderId: string): Promise { return await this.getUnifiedOrderDetail(orderId, 'goods') } // 支付订单 async payOrder(orderId: string, paymentMethod: string, amount: number): Promise { return await this.payUnifiedOrder(orderId, 'goods', paymentMethod, amount) } // 根据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 query = supa .from('ml_orders') try { const result = await this.getUnifiedOrderDetail(orderId, 'goods') if (result == null) { return null } return result } catch (e) { console.error('[getOrderById] 查询订单异常:', e) return null } 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') console.log('[createRefund] orderId:', orderId) console.log('[createRefund] refundType:', refundType) console.log('[createRefund] refundReason:', refundReason) console.log('[createRefund] refundAmount:', refundAmount) 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 } console.log('[createRefund] 准备插入 ml_refunds') const response = await supa .from('ml_refunds') .insert(payload) .execute() console.log('[createRefund] insert response.error:', response.error) if (response.error != null) { console.error('提交售后失败:', response.error) return { success: false, message: '提交失败: ' + (response.error.message ?? '未知错误') } } console.log('[createRefund] 插入成功,更新订单状态') // 更新订单状态为退款中 const updateResponse = await supa .from('ml_orders') .update({ order_status: 6, // 退款中 updated_at: new Date().toISOString() }) .eq('id', orderId) .execute() console.log('[createRefund] update response.error:', updateResponse.error) if (updateResponse.error != null) { console.error('更新订单状态失败:', updateResponse.error) // 不影响退款申请结果,只记录错误 } console.log('[createRefund] 完成,返回成功') return { success: true, message: '申请提交成功' } } catch (e) { console.error('提交售后异常:', e) return { success: false, message: '系统异常' } } } // 取消退款申请 async cancelRefund(orderId: string): Promise { try { console.log('[cancelRefund] 开始取消退款申请, orderId:', orderId) const userId = this.getCurrentUserId() if (userId == null) { return { success: false, message: '请先登录' } } // 更新退款记录状态为已取消 const refundUpdateResponse = await supa .from('ml_refunds') .update({ status: 4, // 已取消 updated_at: new Date().toISOString() }) .eq('order_id', orderId) .eq('user_id', userId) .eq('status', 1) // 只能取消待处理的退款 .execute() if (refundUpdateResponse.error != null) { console.error('取消退款记录失败:', refundUpdateResponse.error) return { success: false, message: '取消失败: ' + (refundUpdateResponse.error.message ?? '未知错误') } } // 恢复订单状态为已完成(假设之前是已完成状态) const orderUpdateResponse = await supa .from('ml_orders') .update({ order_status: 4, // 已完成 updated_at: new Date().toISOString() }) .eq('id', orderId) .execute() if (orderUpdateResponse.error != null) { console.error('恢复订单状态失败:', orderUpdateResponse.error) // 不影响取消退款结果,只记录错误 } 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 getUserBalanceNumber(): 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 = null } else { data = arr[0] } } if (data == null) { console.log('[Supabase] Wallet table returned empty array') } else { 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 if (data != null) { // 对于 Map 或 loose object const plainObj = JSON.parse(JSON.stringify(data)) const rawBalance = plainObj['balance'] if (typeof rawBalance === 'number') { val = rawBalance as number } else if (typeof rawBalance === 'string' && rawBalance != '') { val = parseFloat(rawBalance as string) } 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() 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) } 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 = null } else { data = arr[0] } } if (data instanceof UTSJSONObject) { return data.getNumber('points') ?? 0 } else if (data != null) { // 尝试转为 UTSJSONObject const plainObj = JSON.parse(JSON.stringify(data)) const rawPoints = plainObj['points'] if (typeof rawPoints === 'number') { return rawPoints as number } if (typeof rawPoints === 'string' && rawPoints != '') { return parseFloat(rawPoints as string) } 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 plainProfile = JSON.parse(JSON.stringify(profile)) const rawPoints = plainProfile['points'] if (typeof rawPoints === 'number') { return rawPoints as number } if (typeof rawPoints === 'string' && rawPoints != '') { return parseFloat(rawPoints as string) } return 0 } } return 0 } catch (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 } // 安全处理返回数据 - 安卓端可能是 UTSJSONObject 或 UTSArray const rawData: any[] = [] const respData = response.data console.log('[getUserCoupons] 原始数据类型:', typeof respData, '是否数组:', Array.isArray(respData)) if (respData != null) { if (Array.isArray(respData)) { const arr = respData as any[] console.log('[getUserCoupons] 数组长度:', arr.length) for (let i = 0; i < arr.length; i++) { rawData.push(arr[i]) } } else if (respData instanceof UTSJSONObject) { // 单个对象情况,包装成数组 console.log('[getUserCoupons] 单个对象,包装成数组') rawData.push(respData) } else { // 尝试 JSON 转换 try { const parsed = JSON.parse(JSON.stringify(respData)) console.log('[getUserCoupons] JSON转换后是否数组:', Array.isArray(parsed)) if (Array.isArray(parsed)) { console.log('[getUserCoupons] 转换后数组长度:', parsed.length) for (let i = 0; i < parsed.length; i++) { rawData.push(parsed[i]) } } } catch (parseErr) { console.error('解析优惠券数据异常:', parseErr) } } } console.log('[getUserCoupons] 最终rawData长度:', rawData.length) // 映射数据,将 template 的字段展平 const coupons: UserCoupon[] = [] 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 } // 创建真正的 UserCoupon 对象,而不是 UTSJSONObject const couponItem: UserCoupon = { id: itemId, user_id: itemUserId, template_id: itemTmplId, coupon_code: itemCode, status: itemStatus, received_at: itemRecv, expire_at: itemExpire, template_name: tName, amount: tAmount, min_spend: tMin } coupons.push(couponItem) } 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 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 uploadChatImage(filePath: string): Promise { const userId = this.getCurrentUserId() if (userId == null) { console.error("uploadChatImage failed: user not logged in") return '' } try { // 生成唯一文件名 const timestamp = Date.now() const randomStr = Math.random().toString(36).substring(2, 8) const fileName = `chat_${userId}_${timestamp}_${randomStr}.jpg` const storagePath = `chat-images/${fileName}` console.log('[uploadChatImage] 开始上传:', filePath, '->', storagePath) const response = await supa.storage .from('chat') .upload(storagePath, filePath, {}) if (response.error != null) { console.error('[uploadChatImage] 上传失败:', response.error) return '' } // 构建公开访问URL const publicUrl = `${supa.baseUrl}/storage/v1/object/public/chat/${storagePath}` console.log('[uploadChatImage] 上传成功:', publicUrl) return publicUrl } catch (e) { console.error('[uploadChatImage] 上传异常:', e) return '' } } // 标记会话已读 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 { if (!logConsumerQueryStart('getHotKeywords', 'ml_search_history', 'keyword')) { return [] as string[] } 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) { logConsumerQueryFailure('getHotKeywords', 'ml_search_history', response.error) 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) } logConsumerQuerySuccess('getHotKeywords', 'ml_search_history', sortedKeywords.length) return sortedKeywords } catch (e) { logConsumerQueryFailure('getHotKeywords', 'ml_search_history', 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_detail_view') .select('id, 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(page: number = 1, limit: number = 10): Promise> { try { if (!logConsumerQueryStart('getSmartRecommendations', 'ml_products_detail_view', 'composed: search_history + browse_history + products_detail_view')) { return emptyProductPage(page, limit) } console.log('[getSmartRecommendations] 开始获取智能推荐...') const products: Product[] = [] const addedIds = new Set() const requiredCount = page * limit + 1 // 1. 根据用户搜索历史推荐商品(权重最高) const searchHistory = await this.getUserSearchHistory(5) console.log('[getSmartRecommendations] 用户搜索历史:', searchHistory) if (searchHistory.length > 0) { // 根据搜索关键词查找商品 const keywordProducts = await this.searchProductsByKeywords(searchHistory, requiredCount) 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 < requiredCount) { const browseCategories = await this.getUserBrowseCategories(3) console.log('[getSmartRecommendations] 用户浏览分类:', browseCategories) if (browseCategories.length > 0) { const categoryProducts = await this.getProductsByCategories(browseCategories, requiredCount - 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) if (products.length >= requiredCount) break } } } } // 3. 补充热销商品 if (products.length < requiredCount) { const hotProducts = await this.getHotProducts(requiredCount - 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 >= requiredCount) break } } } // 4. 如果还不够,用普通商品补充 if (products.length < requiredCount) { const moreProducts = await this.getProductsByPrice(1, requiredCount - products.length + 5, false) for (let i = 0; i < moreProducts.data.length; i++) { const prod = moreProducts.data[i] if (!addedIds.has(prod.id)) { products.push(prod) addedIds.add(prod.id) if (products.length >= requiredCount) break } } } const startIndex = (page - 1) * limit const endIndex = startIndex + limit const pageData = products.slice(startIndex, endIndex) console.log('[getSmartRecommendations] 返回商品数量:', pageData.length) logConsumerQuerySuccess('getSmartRecommendations', 'ml_products_detail_view', pageData.length) return { data: pageData, total: products.length, page, limit, hasmore: products.length > endIndex } } catch (e) { logConsumerQueryFailure('getSmartRecommendations', 'ml_products_detail_view', e) console.error('获取智能推荐失败:', e) return emptyProductPage(page, limit) } } // 根据关键词列表搜索商品 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) } } // ==================== 签到相关API ==================== // 用户签到 async signin(): Promise { const result = new UTSJSONObject() result.set('success', false) result.set('points', 0) result.set('continuous_days', 0) result.set('bonus_points', 0) result.set('total_points', 0) result.set('message', '') try { const userId = this.getCurrentUserId() if (userId == null) { result.set('message', '请先登录') return result } const today = new Date() const todayStr = today.toISOString().split('T')[0] const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000) const yesterdayStr = yesterday.toISOString().split('T')[0] // 检查今天是否已签到 const checkRes = await supa .from('ml_signin_records') .select('*') .eq('user_id', userId!) .eq('signin_date', todayStr) .execute() if (checkRes.error != null) { result.set('message', '查询签到状态失败') return result } const checkData = checkRes.data as any[] if (checkData != null && checkData.length > 0) { result.set('message', '今天已签到') return result } // 查询昨天是否签到,计算连续天数 const yesterdayRes = await supa .from('ml_signin_records') .select('continuous_days') .eq('user_id', userId!) .eq('signin_date', yesterdayStr) .execute() let continuousDays = 1 if (yesterdayRes.error == null && yesterdayRes.data != null) { const yData = yesterdayRes.data as any[] if (yData.length > 0) { const yItem = yData[0] let yDays = 0 if (yItem instanceof UTSJSONObject) { yDays = yItem.getNumber('continuous_days') ?? 0 } else { const yObj = JSON.parse(JSON.stringify(yItem)) as UTSJSONObject yDays = yObj.getNumber('continuous_days') ?? 0 } continuousDays = yDays + 1 } } // 计算积分 let pointsEarned = 5 // 每日签到基础积分 let bonusPoints = 0 if (continuousDays >= 30) { bonusPoints = 100 } else if (continuousDays >= 7) { bonusPoints = 20 } const totalPointsEarned = pointsEarned + bonusPoints // 插入签到记录 const signinRecord = new UTSJSONObject() signinRecord.set('user_id', userId!) signinRecord.set('signin_date', todayStr) signinRecord.set('points_earned', pointsEarned) signinRecord.set('bonus_points', bonusPoints) signinRecord.set('continuous_days', continuousDays) const insertRes = await supa .from('ml_signin_records') .insert(signinRecord) .execute() if (insertRes.error != null) { result.set('message', '签到失败') return result } // 更新用户积分 await this.addPoints(userId!, totalPointsEarned, 'signin', '每日签到') // 获取最新积分 const newPoints = await this.getUserPoints() result.set('success', true) result.set('points', pointsEarned) result.set('continuous_days', continuousDays) result.set('bonus_points', bonusPoints) result.set('total_points', newPoints) result.set('message', '签到成功') return result } catch (e) { console.error('签到异常:', e) result.set('message', '签到异常') return result } } // 获取签到记录(当月) async getSigninRecords(year: number, month: number): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { const empty: any[] = [] return empty } const startDate = `${year}-${month.toString().padStart(2, '0')}-01` const endDate = month === 12 ? `${year + 1}-01-01` : `${year}-${(month + 1).toString().padStart(2, '0')}-01` const response = await supa .from('ml_signin_records') .select('*') .eq('user_id', userId!) .gte('signin_date', startDate) .lt('signin_date', endDate) .order('signin_date', { ascending: true }) .execute() if (response.error != null || response.data == null) { const empty: any[] = [] return empty } return response.data as any[] } catch (e) { console.error('获取签到记录失败:', e) const empty: any[] = [] return empty } } // 获取今日签到状态 async getTodaySigninStatus(): Promise { const result = new UTSJSONObject() result.set('signed', false) result.set('continuous_days', 0) try { const userId = this.getCurrentUserId() if (userId == null) return result const today = new Date().toISOString().split('T')[0] // 检查今天是否签到 const todayRes = await supa .from('ml_signin_records') .select('*') .eq('user_id', userId!) .eq('signin_date', today) .execute() if (todayRes.error == null && todayRes.data != null) { const tData = todayRes.data as any[] if (tData.length > 0) { const tItem = tData[0] let cDays = 0 if (tItem instanceof UTSJSONObject) { cDays = tItem.getNumber('continuous_days') ?? 0 } else { const tObj = JSON.parse(JSON.stringify(tItem)) as UTSJSONObject cDays = tObj.getNumber('continuous_days') ?? 0 } result.set('signed', true) result.set('continuous_days', cDays) return result } } // 今天未签到,获取最近的连续签到天数 const lastRes = await supa .from('ml_signin_records') .select('continuous_days, signin_date') .eq('user_id', userId!) .order('signin_date', { ascending: false }) .limit(1) .execute() if (lastRes.error == null && lastRes.data != null) { const lData = lastRes.data as any[] if (lData.length > 0) { const lItem = lData[0] let lastDate = '' let lastDays = 0 if (lItem instanceof UTSJSONObject) { lastDate = lItem.getString('signin_date') ?? '' lastDays = lItem.getNumber('continuous_days') ?? 0 } else { const lObj = JSON.parse(JSON.stringify(lItem)) as UTSJSONObject lastDate = lObj.getString('signin_date') ?? '' lastDays = lObj.getNumber('continuous_days') ?? 0 } const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().split('T')[0] if (lastDate === yesterday) { result.set('continuous_days', lastDays) } } } return result } catch (e) { console.error('获取签到状态失败:', e) return result } } // ==================== 积分兑换相关API ==================== // 获取积分兑换商品列表 async getPointProducts(): Promise { try { const response = await supa .from('ml_point_products') .select('*') .eq('status', 1) .gt('stock', 0) .order('sort_order', { ascending: true }) .execute() if (response.error != null || response.data == null) { const empty: any[] = [] return empty } return response.data as any[] } catch (e) { console.error('获取积分商品失败:', e) const empty: any[] = [] return empty } } // 积分兑换 async exchangeProduct(productId: string, quantity: number, addressSnapshot: UTSJSONObject | null): Promise { const result = new UTSJSONObject() result.set('success', false) result.set('message', '') try { const userId = this.getCurrentUserId() if (userId == null) { result.set('message', '请先登录') return result } // 获取商品信息 const productRes = await supa .from('ml_point_products') .select('*') .eq('id', productId) .single() .execute() if (productRes.error != null || productRes.data == null) { result.set('message', '商品不存在') return result } const productRaw = productRes.data let pointsRequired = 0 let stock = 0 let productType = '' // 检查是否是数组,如果是则取第一个元素 let productObj: UTSJSONObject | null = null if (Array.isArray(productRaw)) { const arr = productRaw as any[] if (arr.length > 0) { const firstItem = arr[0] if (firstItem instanceof UTSJSONObject) { productObj = firstItem } else { productObj = JSON.parse(JSON.stringify(firstItem)) as UTSJSONObject } } } else { if (productRaw instanceof UTSJSONObject) { productObj = productRaw } else { productObj = JSON.parse(JSON.stringify(productRaw)) as UTSJSONObject } } // 使用 UTSJSONObject 方法访问属性 if (productObj != null) { pointsRequired = productObj.getNumber('points_required') ?? 0 stock = productObj.getNumber('stock') ?? 0 productType = productObj.getString('product_type') ?? '' } const totalPoints = pointsRequired * quantity // 检查库存 if (stock < quantity) { result.set('message', '库存不足') return result } // 检查积分 const userPoints = await this.getUserPoints() if (userPoints < totalPoints) { result.set('message', '积分不足') return result } // 创建兑换记录 const exchangeRecord = new UTSJSONObject() exchangeRecord.set('user_id', userId!) exchangeRecord.set('product_id', productId) exchangeRecord.set('quantity', quantity) exchangeRecord.set('points_used', totalPoints) exchangeRecord.set('status', 0) if (addressSnapshot != null && productType === 'physical') { exchangeRecord.set('address_snapshot', JSON.stringify(addressSnapshot)) } const insertRes = await supa .from('ml_point_exchanges') .insert(exchangeRecord) .execute() if (insertRes.error != null) { console.error('[exchangeProduct] 创建兑换记录失败:', insertRes.error) result.set('message', '兑换失败') return result } console.log('[exchangeProduct] 兑换记录创建成功') // 扣减库存 console.log('[exchangeProduct] 准备扣减库存') console.log('[exchangeProduct] productId 类型:', typeof productId) console.log('[exchangeProduct] productId 值:', productId) console.log('[exchangeProduct] 当前库存:', stock, ', 扣减数量:', quantity) // 使用 UTSJSONObject 替代 Record const stockUpdateData = new UTSJSONObject() stockUpdateData.set('stock', stock - quantity) console.log('[exchangeProduct] stockUpdateData:', stockUpdateData) console.log('[exchangeProduct] stockUpdateData 类型:', typeof stockUpdateData) // 先查询确认商品存在 const checkProduct = await supa .from('ml_point_products') .select('id, stock') .eq('id', productId) .execute() console.log('[exchangeProduct] 查询商品结果:', checkProduct.data, 'error:', checkProduct.error) const stockUpdateRes = await supa .from('ml_point_products') .update(stockUpdateData) .eq('id', productId) .execute() console.log('[exchangeProduct] 库存更新结果 error:', stockUpdateRes.error) console.log('[exchangeProduct] 库存更新结果 data:', stockUpdateRes.data) if (stockUpdateRes.error != null) { console.error('[exchangeProduct] 扣减库存失败:', stockUpdateRes.error) } // 扣减积分 console.log('[exchangeProduct] 准备扣减积分, userId:', userId, ', 积分:', totalPoints) const deductResult = await this.deductPoints(userId!, totalPoints, 'redeem', '积分兑换商品') console.log('[exchangeProduct] 积分扣减结果:', deductResult) if (!deductResult) { console.error('[exchangeProduct] 扣减积分失败') } console.log('[exchangeProduct] 兑换流程完成') result.set('success', true) result.set('message', '兑换成功') return result } catch (e) { console.error('积分兑换异常:', e) result.set('message', '兑换异常') return result } } // 获取兑换记录 async getExchangeRecords(): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { const empty: any[] = [] return empty } const response = await supa .from('ml_point_exchanges') .select('*, product:ml_point_products(name, image_url, product_type)') .eq('user_id', userId!) .order('created_at', { ascending: false }) .execute() if (response.error != null || response.data == null) { const empty: any[] = [] return empty } return response.data as any[] } catch (e) { console.error('获取兑换记录失败:', e) const empty: any[] = [] return empty } } // ==================== 评价相关API ==================== // 获取商品评价列表 async getProductReviews(productId: string, page: number = 1, limit: number = 10, rating: number = 0, hasImage: boolean = false): Promise { const result = new UTSJSONObject() result.set('total', 0) result.set('page', page) result.set('limit', limit) result.set('data', [] as any[]) try { const userId = this.getCurrentUserId() let query = supa .from('ml_product_reviews') .select('*, user:auth.users!ml_product_reviews_user_id_fkey(raw_user_meta_data)', { count: 'exact' }) .eq('product_id', productId) if (rating > 0) { query = query.eq('rating', rating) } if (hasImage) { query = query.neq('images', '[]') } const offset = (page - 1) * limit const response = await query .order('created_at', { ascending: false }) .range(offset, offset + limit - 1) .execute() if (response.error != null) { console.error('获取评价列表失败:', response.error) return result } const total = response.total ?? 0 const reviews = response.data as any[] // 处理评价数据 const processedReviews: any[] = [] for (let i = 0; i < reviews.length; i++) { const review = reviews[i] const processed = JSON.parse(JSON.stringify(review)) as UTSJSONObject // 处理用户信息 const userRaw = processed.get('user') let userName = '匿名用户' let userAvatar = '' if (userRaw != null) { let userData: UTSJSONObject if (userRaw instanceof UTSJSONObject) { userData = userRaw as UTSJSONObject } else { userData = JSON.parse(JSON.stringify(userRaw)) as UTSJSONObject } const metaData = userData.get('raw_user_meta_data') if (metaData != null) { let metaObj: UTSJSONObject if (metaData instanceof UTSJSONObject) { metaObj = metaData as UTSJSONObject } else { metaObj = JSON.parse(JSON.stringify(metaData)) as UTSJSONObject } userName = metaObj.getString('nickname') ?? metaObj.getString('name') ?? '匿名用户' userAvatar = metaObj.getString('avatar_url') ?? '' } } // 检查是否匿名 const isAnonymous = processed.getBoolean('is_anonymous') ?? false if (isAnonymous) { userName = '匿名用户' userAvatar = '' } processed.set('user_name', userName) processed.set('user_avatar', userAvatar) // 检查当前用户是否点赞 let isLiked = false if (userId != null) { const likeRes = await supa .from('ml_review_likes') .select('id') .eq('review_id', processed.getString('id') ?? '') .eq('user_id', userId!) .limit(1) .execute() if (likeRes.error == null && likeRes.data != null) { const likeData = likeRes.data as any[] isLiked = likeData.length > 0 } } processed.set('is_liked', isLiked) processedReviews.push(processed) } result.set('total', total) result.set('data', processedReviews) return result } catch (e) { console.error('获取评价列表异常:', e) return result } } // 获取商品评价统计 async getReviewStats(productId: string): Promise { const result = new UTSJSONObject() result.set('total_count', 0) result.set('avg_rating', 0) result.set('good_rate', 0) result.set('rating_distribution', new UTSJSONObject()) result.set('tags', [] as any[]) try { const response = await supa .from('ml_product_reviews') .select('rating') .eq('product_id', productId) .execute() if (response.error != null || response.data == null) { return result } const reviews = response.data as any[] const totalCount = reviews.length if (totalCount === 0) return result let totalRating = 0 let goodCount = 0 const distribution: Map = new Map() distribution.set(1, 0) distribution.set(2, 0) distribution.set(3, 0) distribution.set(4, 0) distribution.set(5, 0) for (let i = 0; i < reviews.length; i++) { const review = reviews[i] let rating = 0 if (review instanceof UTSJSONObject) { rating = review.getNumber('rating') ?? 0 } else { const rObj = JSON.parse(JSON.stringify(review)) as UTSJSONObject rating = rObj.getNumber('rating') ?? 0 } totalRating += rating if (rating >= 4) goodCount++ const currentCount = distribution.get(rating) ?? 0 distribution.set(rating, currentCount + 1) } const avgRating = Math.round((totalRating / totalCount) * 10) / 10 const goodRate = Math.round((goodCount / totalCount) * 100) const distObj = new UTSJSONObject() distribution.forEach((value: number, key: number) => { distObj.set(key.toString(), value) }) result.set('total_count', totalCount) result.set('avg_rating', avgRating) result.set('good_rate', goodRate) result.set('rating_distribution', distObj) return result } catch (e) { console.error('获取评价统计异常:', e) return result } } // 评价点赞 async toggleReviewLike(reviewId: string): Promise { const result = new UTSJSONObject() result.set('success', false) result.set('is_liked', false) result.set('like_count', 0) try { const userId = this.getCurrentUserId() if (userId == null) { return result } // 检查是否已点赞 const checkRes = await supa .from('ml_review_likes') .select('id') .eq('review_id', reviewId) .eq('user_id', userId!) .limit(1) .execute() let isLiked = false if (checkRes.error == null && checkRes.data != null) { const checkData = checkRes.data as any[] isLiked = checkData.length > 0 } if (isLiked) { // 取消点赞 await supa .from('ml_review_likes') .eq('review_id', reviewId) .eq('user_id', userId!) .delete() .execute() // 更新点赞数 - 直接查询并更新 const currentCountRes = await supa .from('ml_product_reviews') .select('like_count') .eq('id', reviewId) .single() .execute() if (currentCountRes.error == null && currentCountRes.data != null) { let currentCount = 0 if (currentCountRes.data instanceof UTSJSONObject) { currentCount = currentCountRes.data.getNumber('like_count') ?? 0 } else { const countObj = JSON.parse(JSON.stringify(currentCountRes.data)) as UTSJSONObject currentCount = countObj.getNumber('like_count') ?? 0 } const updateData = new UTSJSONObject() updateData.set('like_count', Math.max(0, currentCount - 1)) await supa .from('ml_product_reviews') .update(updateData) .eq('id', reviewId) .execute() } result.set('is_liked', false) } else { // 添加点赞 const likeRecord = new UTSJSONObject() likeRecord.set('review_id', reviewId) likeRecord.set('user_id', userId!) await supa .from('ml_review_likes') .insert(likeRecord) .execute() // 更新点赞数 - 直接查询并更新 const currentCountRes = await supa .from('ml_product_reviews') .select('like_count') .eq('id', reviewId) .single() .execute() if (currentCountRes.error == null && currentCountRes.data != null) { let currentCount = 0 if (currentCountRes.data instanceof UTSJSONObject) { currentCount = currentCountRes.data.getNumber('like_count') ?? 0 } else { const countObj = JSON.parse(JSON.stringify(currentCountRes.data)) as UTSJSONObject currentCount = countObj.getNumber('like_count') ?? 0 } const updateData = new UTSJSONObject() updateData.set('like_count', currentCount + 1) await supa .from('ml_product_reviews') .update(updateData) .eq('id', reviewId) .execute() } result.set('is_liked', true) } // 获取最新点赞数 const reviewRes = await supa .from('ml_product_reviews') .select('like_count') .eq('id', reviewId) .single() .execute() if (reviewRes.error == null && reviewRes.data != null) { let likeCount = 0 if (reviewRes.data instanceof UTSJSONObject) { likeCount = reviewRes.data.getNumber('like_count') ?? 0 } else { const rObj = JSON.parse(JSON.stringify(reviewRes.data)) as UTSJSONObject likeCount = rObj.getNumber('like_count') ?? 0 } result.set('like_count', likeCount) } result.set('success', true) return result } catch (e) { console.error('评价点赞异常:', e) return result } } // 获取我的评价列表 async getMyReviews(): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { const empty: any[] = [] return empty } const response = await supa .from('ml_product_reviews') .select(` *, product:ml_products!ml_product_reviews_product_id_fkey(name, main_image_url) `) .eq('user_id', userId!) .order('created_at', { ascending: false }) .execute() if (response.error != null || response.data == null) { const empty: any[] = [] return empty } const reviews = response.data as any[] const result: any[] = [] for (let i = 0; i < reviews.length; i++) { const review = reviews[i] const processed = JSON.parse(JSON.stringify(review)) as UTSJSONObject // 处理商品信息 const productRaw = processed.get('product') let productName = '' let productImage = '' if (productRaw != null) { let productObj: UTSJSONObject if (productRaw instanceof UTSJSONObject) { productObj = productRaw as UTSJSONObject } else { productObj = JSON.parse(JSON.stringify(productRaw)) as UTSJSONObject } productName = productObj.getString('name') ?? '' productImage = productObj.getString('main_image_url') ?? '' } processed.set('product_name', productName) processed.set('product_image', productImage) // 计算是否可追加评价(7天内) const createdAt = processed.getString('created_at') ?? '' const createdTime = new Date(createdAt).getTime() const now = Date.now() const sevenDays = 7 * 24 * 60 * 60 * 1000 const canAppend = (now - createdTime) < sevenDays && (processed.getString('append_content') ?? '') === '' processed.set('can_append', canAppend) // 计算是否可编辑(24小时内) const oneDay = 24 * 60 * 60 * 1000 const canEdit = (now - createdTime) < oneDay processed.set('can_edit', canEdit) result.push(processed) } return result } catch (e) { console.error('获取我的评价失败:', e) const empty: any[] = [] return empty } } // 追加评价 async appendReview(reviewId: string, content: string, images: string[]): Promise { try { const userId = this.getCurrentUserId() if (userId == null) return false const updateData = new UTSJSONObject() updateData.set('append_content', content) updateData.set('append_images', JSON.stringify(images)) updateData.set('append_at', new Date().toISOString()) const response = await supa .from('ml_product_reviews') .update(updateData) .eq('id', reviewId) .eq('user_id', userId!) .execute() return response.error == null } catch (e) { console.error('追加评价失败:', e) return false } } // 删除评价 async deleteReview(reviewId: string): Promise { try { const userId = this.getCurrentUserId() if (userId == null) return false const response = await supa .from('ml_product_reviews') .delete() .eq('id', reviewId) .eq('user_id', userId!) .execute() return response.error == null } catch (e) { console.error('删除评价失败:', e) return false } } // ==================== 积分辅助方法 ==================== // 增加积分 private async addPoints(userId: string, points: number, type: string, description: string): Promise { try { // 获取当前积分 const currentPoints = await this.getUserPoints() const newPoints = currentPoints + points const totalEarned = await this.getTotalEarned() // 检查用户积分记录是否存在 const checkRes = await supa .from('ml_user_points') .select('user_id') .eq('user_id', userId) .limit(1) .execute() const exists = checkRes.error == null && checkRes.data != null && (checkRes.data as any[]).length > 0 if (exists) { // 更新现有记录 const updateData = new UTSJSONObject() updateData.set('points', newPoints) updateData.set('total_earned', totalEarned + points) updateData.set('updated_at', new Date().toISOString()) await supa .from('ml_user_points') .update(updateData) .eq('user_id', userId) .execute() } else { // 插入新记录 const insertData = new UTSJSONObject() insertData.set('user_id', userId) insertData.set('points', newPoints) insertData.set('total_earned', points) insertData.set('updated_at', new Date().toISOString()) await supa .from('ml_user_points') .insert(insertData) .execute() } // 记录积分变动 const record = new UTSJSONObject() record.set('user_id', userId) record.set('points', points) record.set('type', type) record.set('description', description) await supa .from('ml_point_records') .insert(record) .execute() return true } catch (e) { console.error('增加积分失败:', e) return false } } // 扣减积分 private async deductPoints(userId: string, points: number, type: string, description: string): Promise { try { const currentPoints = await this.getUserPoints() const newPoints = currentPoints - points if (newPoints < 0) return false const updateData = new UTSJSONObject() updateData.set('points', newPoints) updateData.set('updated_at', new Date().toISOString()) await supa .from('ml_user_points') .update(updateData) .eq('user_id', userId) .execute() const record = new UTSJSONObject() record.set('user_id', userId) record.set('points', -points) record.set('type', type) record.set('description', description) await supa .from('ml_point_records') .insert(record) .execute() return true } catch (e) { console.error('扣减积分失败:', e) return false } } // 获取历史累计积分 private async getTotalEarned(): Promise { try { const userId = this.getCurrentUserId() if (userId == null) return 0 const res = await supa .from('ml_user_points') .select('total_earned') .eq('user_id', userId!) .single() .execute() if (res.error == null && res.data != null) { if (res.data instanceof UTSJSONObject) { return res.data.getNumber('total_earned') ?? 0 } else { const obj = JSON.parse(JSON.stringify(res.data)) as UTSJSONObject return obj.getNumber('total_earned') ?? 0 } } return 0 } catch (e) { return 0 } } // ==================== 积分过期相关API ==================== // 获取即将过期积分 async getExpiringPoints(): Promise { const result = new UTSJSONObject() result.set('expiring_points', 0) result.set('expiring_date', null) result.set('details', [] as any[]) try { const userId = this.getCurrentUserId() if (userId == null) return result // 查询30天内即将过期的积分记录 const now = new Date() const thirtyDaysLater = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000) const nowStr = now.toISOString() const laterStr = thirtyDaysLater.toISOString() const res = await supa .from('ml_point_records') .select('points, description, expires_at, created_at') .eq('user_id', userId!) .gt('points', 0) .eq('is_expired', false) .not('expires_at', 'is', null) .gte('expires_at', nowStr) .lte('expires_at', laterStr) .order('expires_at', { ascending: true }) .execute() if (res.error != null) { console.error('获取即将过期积分失败:', res.error) return result } if (res.data != null && Array.isArray(res.data)) { const records = res.data as any[] let totalExpiring = 0 let earliestDate: string | null = null const details: any[] = [] for (let i = 0; i < records.length; i++) { const record = records[i] let recordObj: UTSJSONObject if (record instanceof UTSJSONObject) { recordObj = record } else { recordObj = JSON.parse(JSON.stringify(record)) as UTSJSONObject } const points = recordObj.getNumber('points') ?? 0 const expiresAt = recordObj.getString('expires_at') ?? '' totalExpiring += points if (earliestDate == null || expiresAt < earliestDate) { earliestDate = expiresAt } details.push({ points: points, description: recordObj.getString('description'), expires_at: expiresAt, created_at: recordObj.getString('created_at') ?? '' }) } result.set('expiring_points', totalExpiring) result.set('expiring_date', earliestDate != null ? earliestDate.split('T')[0] : null) result.set('details', details) } return result } catch (e) { console.error('获取即将过期积分异常:', e) return result } } // 获取积分概览(包含即将过期积分) async getPointsOverview(): Promise { const result = new UTSJSONObject() result.set('current_points', 0) result.set('total_earned', 0) result.set('expiring_points', 0) result.set('expiring_date', null) try { const userId = this.getCurrentUserId() if (userId == null) return result const res = await supa .from('ml_user_points') .select('points, total_earned, expiring_points, expiring_date') .eq('user_id', userId!) .single() .execute() if (res.error == null && res.data != null) { let data: UTSJSONObject if (res.data instanceof UTSJSONObject) { data = res.data as UTSJSONObject } else { data = JSON.parse(JSON.stringify(res.data)) as UTSJSONObject } result.set('current_points', data.getNumber('points') ?? 0) result.set('total_earned', data.getNumber('total_earned') ?? 0) result.set('expiring_points', data.getNumber('expiring_points') ?? 0) result.set('expiring_date', data.getString('expiring_date')) } return result } catch (e) { console.error('获取积分概览异常:', e) return result } } // 获取过期提醒通知列表 async getExpiryNotifications(): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { const empty: any[] = [] return empty } const res = await supa .from('ml_point_expiry_notifications') .select('*') .eq('user_id', userId!) .eq('is_sent', false) .order('expiry_date', { ascending: true }) .execute() if (res.error != null || res.data == null) { const empty: any[] = [] return empty } return res.data as any[] } catch (e) { console.error('获取过期提醒失败:', e) const empty: any[] = [] return empty } } // 标记过期提醒为已读 async markNotificationRead(notificationId: string): Promise { try { const res = await supa .from('ml_point_expiry_notifications') .update({ is_sent: true, sent_at: new Date().toISOString() }) .eq('id', notificationId) .execute() return res.error == null } catch (e) { console.error('标记通知失败:', e) return false } } // 手动触发积分维护任务(管理员功能) // 注意:UTS不支持rpc,此功能需要在Supabase后台手动执行或通过其他方式触发 async triggerPointsMaintenance(): Promise { console.warn('triggerPointsMaintenance: UTS不支持rpc调用,请在Supabase后台手动执行 daily_points_maintenance()') return false } // ==================== 推销模式 - 商家配置API ==================== // 获取商家推销配置 async getMerchantPromotionConfig(merchantId: string): Promise { const result = new UTSJSONObject() result.set('promotion_enabled', false) result.set('share_free_enabled', false) result.set('distribution_enabled', false) result.set('required_count', 4) result.set('reward_type', 'product_price') result.set('fixed_reward_amount', 0) try { const res = await supa .from('ml_merchant_promotion_config') .select('*') .eq('merchant_id', merchantId) .limit(1) .execute() if (res.error == null && res.data != null && Array.isArray(res.data)) { const arr = res.data as any[] if (arr.length > 0) { const item = arr[0] const itemAny = item as any if (itemAny instanceof UTSJSONObject) { result.set('promotion_enabled', itemAny.getBoolean('promotion_enabled') ?? false) result.set('share_free_enabled', itemAny.getBoolean('share_free_enabled') ?? false) result.set('distribution_enabled', itemAny.getBoolean('distribution_enabled') ?? false) result.set('required_count', itemAny.getNumber('required_count') ?? 4) result.set('reward_type', itemAny.getString('reward_type') ?? 'product_price') result.set('fixed_reward_amount', itemAny.getNumber('fixed_reward_amount') ?? 0) } } } } catch (e) { console.error('获取商家推销配置失败:', e) } return result } // 检查商家是否开启分享免单 async isShareFreeEnabled(merchantId: string): Promise { try { const config = await this.getMerchantPromotionConfig(merchantId) const promotionEnabled = config.get('promotion_enabled') const shareFreeEnabled = config.get('share_free_enabled') return (promotionEnabled === true || promotionEnabled === 'true') && (shareFreeEnabled === true || shareFreeEnabled === 'true') } catch (e) { console.error('检查分享免单状态失败:', e) return false } } // ==================== 推销模式 - 余额相关API ==================== // 获取用户余额 async getUserBalance(): Promise { const result = new UTSJSONObject() result.set('balance', 0) result.set('frozen_balance', 0) result.set('total_earned', 0) result.set('total_withdrawn', 0) try { const userId = this.getCurrentUserId() if (userId == null) return result const res = await supa .from('ml_user_balance') .select('*') .eq('user_id', userId!) .limit(1) .execute() if (res.error == null && res.data != null && Array.isArray(res.data)) { const arr = res.data as any[] if (arr.length > 0) { const item = arr[0] const itemAny = item as any if (itemAny instanceof UTSJSONObject) { result.set('balance', itemAny.getNumber('balance') ?? 0) result.set('frozen_balance', itemAny.getNumber('frozen_balance') ?? 0) result.set('total_earned', itemAny.getNumber('total_earned') ?? 0) result.set('total_withdrawn', itemAny.getNumber('total_withdrawn') ?? 0) } } } return result } catch (e) { console.error('获取用户余额失败:', e) return result } } // 获取余额变动记录 async getBalanceRecords(page: number = 1, limit: number = 20): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { const empty: any[] = [] return empty } const offset = (page - 1) * limit const res = await supa .from('ml_balance_records') .select('*') .eq('user_id', userId!) .order('created_at', { ascending: false }) .range(offset, offset + limit - 1) .execute() if (res.error != null || res.data == null) { const empty: any[] = [] return empty } return res.data as any[] } catch (e) { console.error('获取余额记录失败:', e) const empty: any[] = [] return empty } } // ==================== 推销模式 - 分享免单相关API ==================== // 创建分享记录 async createShareRecord(productId: string, orderId: string, orderItemId: string | null, productName: string, productImage: string | null, productPrice: number): Promise { const result = new UTSJSONObject() result.set('success', false) result.set('share_code', '') result.set('message', '') try { const userId = this.getCurrentUserId() if (userId == null) { result.set('message', '请先登录') return result } // 生成分享码 const shareCode = this.generateShareCode() const insertData = new UTSJSONObject() insertData.set('user_id', userId) insertData.set('product_id', productId) insertData.set('order_id', orderId) insertData.set('order_item_id', orderItemId) insertData.set('share_code', shareCode) insertData.set('product_name', productName) insertData.set('product_image', productImage) insertData.set('product_price', productPrice) insertData.set('required_count', 4) insertData.set('current_count', 0) insertData.set('status', 0) const res = await supa .from('ml_share_records') .insert(insertData) .execute() if (res.error != null) { console.error('[createShareRecord] 插入失败:', res.error) console.error('[createShareRecord] 插入数据:', JSON.stringify(insertData)) result.set('message', '创建分享记录失败: ' + (res.error.message ?? '未知错误')) return result } // 获取插入记录的id let insertedId = '' if (res.data != null && Array.isArray(res.data) && res.data.length > 0) { const inserted = res.data[0] let insertedObj: UTSJSONObject | null = null if (inserted instanceof UTSJSONObject) { insertedObj = inserted } else { insertedObj = JSON.parse(JSON.stringify(inserted)) as UTSJSONObject } insertedId = insertedObj.getString('id') ?? '' } result.set('success', true) result.set('id', insertedId) result.set('share_code', shareCode) result.set('message', '分享创建成功') return result } catch (e) { console.error('创建分享记录失败:', e) result.set('message', '创建分享记录异常') return result } } // 生成分享码 private generateShareCode(): string { const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' let result = '' for (let i = 0; i < 8; i++) { const randomIndex = Math.floor(Math.random() * chars.length) result += chars.charAt(randomIndex) } return result } // 验证分享码 async validateShareCode(shareCode: string): Promise { const result = new UTSJSONObject() result.set('valid', false) result.set('share_record', null) try { const res = await supa .from('ml_share_records') .select('*') .eq('share_code', shareCode) .eq('status', 0) .limit(1) .execute() if (res.error == null && res.data != null && Array.isArray(res.data)) { const arr = res.data as any[] if (arr.length > 0) { result.set('valid', true) result.set('share_record', arr[0]) } } return result } catch (e) { console.error('验证分享码失败:', e) return result } } // 获取我的分享记录 async getMyShareRecords(): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { const empty: any[] = [] return empty } const res = await supa .from('ml_share_records') .select('*') .eq('user_id', userId!) .order('created_at', { ascending: false }) .execute() if (res.error != null || res.data == null) { const empty: any[] = [] return empty } return res.data as any[] } catch (e) { console.error('获取分享记录失败:', e) const empty: any[] = [] return empty } } // 获取分享详情 async getShareDetail(shareId: string): Promise { const result = new UTSJSONObject() result.set('share_record', null) result.set('secondary_purchases', [] as any[]) try { const res = await supa .from('ml_share_records') .select('*') .eq('id', shareId) .limit(1) .execute() if (res.error == null && res.data != null && Array.isArray(res.data)) { const arr = res.data as any[] if (arr.length > 0) { result.set('share_record', arr[0]) } } // 获取二级购买记录 const purchasesRes = await supa .from('ml_secondary_purchases') .select('*') .eq('share_record_id', shareId) .order('created_at', { ascending: false }) .execute() if (purchasesRes.error == null && purchasesRes.data != null) { result.set('secondary_purchases', purchasesRes.data) } return result } catch (e) { console.error('获取分享详情失败:', e) return result } } // 获取免单奖励记录 async getFreeOrderRewards(): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { const empty: any[] = [] return empty } const res = await supa .from('ml_free_order_rewards') .select('*') .eq('user_id', userId!) .order('created_at', { ascending: false }) .execute() if (res.error != null || res.data == null) { const empty: any[] = [] return empty } return res.data as any[] } catch (e) { console.error('获取免单奖励记录失败:', e) const empty: any[] = [] return empty } } // ==================== 推销模式 - 会员等级相关API ==================== // 获取会员等级列表 async getMemberLevels(): Promise { try { const res = await supa .from('ml_member_levels') .select('*') .eq('is_active', true) .order('level_rank', { ascending: true } as OrderOptions) .execute() if (res.error != null || res.data == null) { const empty: any[] = [] return empty } return res.data as any[] } catch (e) { console.error('获取会员等级列表失败:', e) const empty: any[] = [] return empty } } // 获取用户会员信息 async getUserMemberInfo(): Promise { const result = new UTSJSONObject() result.set('member_level', 0) result.set('level_name', '普通会员') result.set('discount', 1.0) result.set('total_spent', 0) result.set('next_level', null) result.set('progress_percent', 0) result.set('manual_level', false) try { const userId = this.getCurrentUserId() if (userId == null) return result // 获取用户信息(包括 tier_id) const userRes = await supa .from('ml_user_profiles') .select('tier_id, total_spent, manual_level') .eq('user_id', userId!) .limit(1) .execute() let tierId: string = '' let totalSpent = 0 let manualLevel = false if (userRes.error == null && userRes.data != null && Array.isArray(userRes.data)) { const arr = userRes.data as any[] if (arr.length > 0) { const item = arr[0] const itemAny = item as any if (itemAny instanceof UTSJSONObject) { tierId = itemAny.getString('tier_id') ?? '' totalSpent = itemAny.getNumber('total_spent') ?? 0 manualLevel = itemAny.getBoolean('manual_level') ?? false } } } // 获取等级信息 const levels = await this.getMemberLevels() let levelName = '普通会员' let discount = 1.0 let nextLevel: UTSJSONObject | null = null let progressPercent = 0 let currentLevelRank = 0 // 通过 tier_id 匹配等级 for (let i = 0; i < levels.length; i++) { const level = levels[i] const levelAny = level as any let levelId = '' let levelNameStr = '' let levelRank = 0 let levelDiscount = 1.0 if (levelAny instanceof UTSJSONObject) { levelId = levelAny.getString('id') ?? '' levelNameStr = levelAny.getString('name') ?? '' levelRank = levelAny.getNumber('level_rank') ?? 0 levelDiscount = levelAny.getNumber('discount_rate') ?? 1.0 } // 通过 tier_id 匹配当前等级 if (levelId == tierId) { levelName = levelNameStr discount = levelDiscount currentLevelRank = levelRank } } // 找下一等级(level_rank 更大的第一个等级) for (let i = 0; i < levels.length; i++) { const level = levels[i] const levelAny = level as any let levelRank = 0 let levelNameStr = '' let levelMinAmount = 0 if (levelAny instanceof UTSJSONObject) { levelRank = levelAny.getNumber('level_rank') ?? 0 levelNameStr = levelAny.getString('name') ?? '' levelMinAmount = levelAny.getNumber('min_amount') ?? 0 } if (levelRank > currentLevelRank && nextLevel == null) { const nextLevelObj = new UTSJSONObject() const levelObj = level as UTSJSONObject nextLevelObj.set('id', levelObj.getString('id') ?? '') nextLevelObj.set('name', levelNameStr) nextLevelObj.set('min_amount', levelMinAmount) nextLevel = nextLevelObj } } result.set('member_level', currentLevelRank) result.set('level_name', levelName) result.set('discount', discount) result.set('total_spent', totalSpent) result.set('next_level', nextLevel) result.set('progress_percent', progressPercent) result.set('manual_level', manualLevel) return result } catch (e) { console.error('获取用户会员信息失败:', e) return result } } // 获取当前等级的最低消费金额 private getCurrentLevelMinAmount(levels: any[], currentLevel: number): number { for (let i = 0; i < levels.length; i++) { const level = levels[i] const levelAny = level as any if (levelAny instanceof UTSJSONObject) { const levelId = levelAny.getNumber('id') ?? 0 if (levelId === currentLevel) { return levelAny.getNumber('min_amount') ?? 0 } } } return 0 } // 获取会员等级变更记录 async getMemberLevelLogs(): Promise { try { const userId = this.getCurrentUserId() if (userId == null) { const empty: any[] = [] return empty } const res = await supa .from('ml_member_level_logs') .select('*') .eq('user_id', userId!) .order('created_at', { ascending: false }) .execute() if (res.error != null || res.data == null) { const empty: any[] = [] return empty } return res.data as any[] } catch (e) { console.error('获取会员等级变更记录失败:', e) const empty: any[] = [] return empty } } } // 导出单例实例 export const supabaseService = new SupabaseService() // 默认导出 export default supabaseService