diff --git a/doc_mall/consumer/sql/update_category_icons.sql b/doc_mall/consumer/sql/update_category_icons.sql
new file mode 100644
index 00000000..950f18fb
--- /dev/null
+++ b/doc_mall/consumer/sql/update_category_icons.sql
@@ -0,0 +1,80 @@
+-- 更新分类图标为 emoji 格式
+-- 运行此脚本修复分类图标显示问题
+
+-- 更新一级分类图标
+UPDATE public.ml_categories
+SET icon_url =
+ CASE
+ WHEN slug = 'digital' THEN '📱'
+ WHEN slug = 'fashion' THEN '👕'
+ WHEN slug = 'home' THEN '🏠'
+ WHEN slug = 'food' THEN '🍎'
+ WHEN slug = 'beauty' THEN '💄'
+ WHEN slug = 'sports' THEN '⚽'
+ WHEN slug = 'books' THEN '📚'
+ WHEN slug = 'baby' THEN '👶'
+ WHEN slug = 'health' THEN '💊'
+ ELSE icon_url
+ END
+WHERE level = 1;
+
+-- 更新二级分类图标
+UPDATE public.ml_categories
+SET icon_url =
+ CASE
+ -- 数码电器二级分类
+ WHEN slug = 'mobile' THEN '📱'
+ WHEN slug = 'computer' THEN '💻'
+ WHEN slug = 'appliance' THEN '🎥'
+ WHEN slug = 'accessories' THEN '🔌'
+ -- 服装鞋帽二级分类
+ WHEN slug = 'mens-wear' THEN '👔'
+ WHEN slug = 'womens-wear' THEN '👗'
+ WHEN slug = 'mens-shoes' THEN '👞'
+ WHEN slug = 'womens-shoes' THEN '👠'
+ -- 家居用品二级分类
+ WHEN slug = 'furniture' THEN '🛋️'
+ WHEN slug = 'decoration' THEN '🖼️'
+ WHEN slug = 'kitchen' THEN '🍳'
+ WHEN slug = 'daily' THEN '🧹'
+ -- 食品饮料二级分类
+ WHEN slug = 'fruits' THEN '🍊'
+ WHEN slug = 'meat' THEN '🥩'
+ WHEN slug = 'snacks' THEN '🍪'
+ WHEN slug = 'drinks' THEN '🍺'
+ -- 美妆护肤二级分类
+ WHEN slug = 'skincare' THEN '🧴'
+ WHEN slug = 'makeup' THEN '💅'
+ -- 运动户外二级分类
+ WHEN slug = 'outdoor' THEN '🏕️'
+ WHEN slug = 'fitness' THEN '🏋️'
+ -- 母婴用品二级分类
+ WHEN slug = 'toys' THEN '🧸'
+ WHEN slug = 'feeding' THEN '🍼'
+ -- 图书文娱二级分类
+ WHEN slug = 'stationery' THEN '✏️'
+ WHEN slug = 'audio' THEN '🎵'
+ ELSE icon_url
+ END
+WHERE level = 2;
+
+-- 如果有 icon_url 为 icon-xxx 格式的记录,也进行更新
+UPDATE public.ml_categories
+SET icon_url =
+ CASE
+ WHEN icon_url = 'icon-digital' THEN '📱'
+ WHEN icon_url = 'icon-fashion' THEN '👕'
+ WHEN icon_url = 'icon-home' THEN '🏠'
+ WHEN icon_url = 'icon-food' THEN '🍎'
+ WHEN icon_url = 'icon-beauty' THEN '💄'
+ WHEN icon_url = 'icon-sports' THEN '⚽'
+ WHEN icon_url = 'icon-books' THEN '📚'
+ WHEN icon_url = 'icon-baby' THEN '👶'
+ WHEN icon_url = 'icon-health' THEN '💊'
+ ELSE icon_url
+ END
+WHERE icon_url LIKE 'icon-%';
+
+-- 查看更新结果
+SELECT name, slug, icon_url FROM public.ml_categories WHERE level = 1 ORDER BY sort_order;
+SELECT name, slug, icon_url FROM public.ml_categories WHERE level = 2 ORDER BY sort_order;
diff --git a/fix_merchant_id.py b/fix_merchant_id.py
new file mode 100644
index 00000000..5bb3aa76
--- /dev/null
+++ b/fix_merchant_id.py
@@ -0,0 +1,28 @@
+import re
+
+# Read the file
+with open('pages/mall/consumer/product-detail.uvue', 'r', encoding='utf-8') as f:
+ content = f.read()
+
+# Find and replace the selectedItem object
+# Looking for the pattern with quantity as the last property
+old_pattern = r"(const selectedItem = \{[^}]+quantity: this\.quantity as number)\s*\}"
+
+new_text = r"""\1,
+ merchant_id: this.product.merchant_id ?? '',
+ shop_id: this.product.merchant_id ?? '',
+ shop_name: this.merchant?.shop_name ?? ''
+ }"""
+
+# Check if pattern exists
+if 'quantity: this.quantity as number' in content and 'selectedItem' in content:
+ content = re.sub(old_pattern, new_text, content, flags=re.DOTALL)
+ print("Pattern found and replaced!")
+else:
+ print("Pattern not found!")
+
+# Write back
+with open('pages/mall/consumer/product-detail.uvue', 'w', encoding='utf-8') as f:
+ f.write(content)
+
+print("File updated successfully!")
diff --git a/fix_merchant_id2.py b/fix_merchant_id2.py
new file mode 100644
index 00000000..aa19700f
--- /dev/null
+++ b/fix_merchant_id2.py
@@ -0,0 +1,50 @@
+import re
+
+# Read the file
+with open('pages/mall/consumer/product-detail.uvue', 'r', encoding='utf-8') as f:
+ content = f.read()
+
+# Find the exact text to replace
+old_text = '''const selectedItem = {
+ id: this.selectedSkuId,
+ product_id: this.product.id,
+ sku_id: this.selectedSkuId,
+ product_name: this.product.name,
+ product_image: (sku != null && sku.image_url != null) ? sku!.image_url : this.product.images[0],
+ sku_specifications: sku != null ? sku!.specifications : {},
+ price: parseFloat((sku != null ? sku!.price : this.product.price).toString()).toFixed(2) as string,
+ quantity: this.quantity as number
+ }'''
+
+new_text = '''const selectedItem = {
+ id: this.selectedSkuId,
+ product_id: this.product.id,
+ sku_id: this.selectedSkuId,
+ product_name: this.product.name,
+ product_image: (sku != null && sku.image_url != null) ? sku!.image_url : this.product.images[0],
+ sku_specifications: sku != null ? sku!.specifications : {},
+ price: parseFloat((sku != null ? sku!.price : this.product.price).toString()).toFixed(2) as string,
+ quantity: this.quantity as number,
+ merchant_id: this.product.merchant_id ?? '',
+ shop_id: this.product.merchant_id ?? '',
+ shop_name: this.merchant?.shop_name ?? ''
+ }'''
+
+if old_text in content:
+ content = content.replace(old_text, new_text)
+ print("Found and replaced!")
+else:
+ print("Old text not found, trying alternative...")
+ # Try with tab characters
+ old_text_alt = old_text.replace(' ', '\t\t\t')
+ if old_text_alt in content:
+ content = content.replace(old_text_alt, new_text.replace(' ', '\t\t\t'))
+ print("Found with tabs and replaced!")
+ else:
+ print("Still not found")
+
+# Write back
+with open('pages/mall/consumer/product-detail.uvue', 'w', encoding='utf-8') as f:
+ f.write(content)
+
+print("Done!")
diff --git a/pages-simple.json b/pages-simple.json
deleted file mode 100644
index 131f2bff..00000000
--- a/pages-simple.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "pages": [
- {
- "path": "pages/minimal",
- "style": {
- "navigationBarTitleText": "最小测试"
- }
- }
- ]
-}
\ No newline at end of file
diff --git a/pages/mall/consumer/666/index.uvue b/pages/mall/consumer/666/index.uvue
deleted file mode 100644
index 1b42741b..00000000
--- a/pages/mall/consumer/666/index.uvue
+++ /dev/null
@@ -1,2526 +0,0 @@
-
-
-
-
-
-
-
-
- 请输入药品名称、症状或品牌
-
-
-
- 🔳
-
-
-
-
- 📷
-
-
-
-
- 搜索
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ category.icon }}
-
- {{ category.name }}
-
-
-
-
-
-
-
-
-
- {{ subCat.icon }}
-
- {{ subCat.name }}
-
-
-
-
-
-
-
-
-
- 🏢
-
- {{ brand.name }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 热销
-
-
- {{ product.name }}
-
-
-
-
- ¥
- {{ product.base_price }}
-
-
- ¥{{ product.market_price }}
-
-
-
-
- {{ product.brand_name ?? product.shop_name ?? '自营' }}
-
- 已售{{ product.sale_count }}
-
-
-
-
-
- +
- 加入购物车
-
-
-
-
-
-
-
- 正在加载更多商品...
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/pages/mall/consumer/666/supabaseService.uts b/pages/mall/consumer/666/supabaseService.uts
deleted file mode 100644
index a4b3550d..00000000
--- a/pages/mall/consumer/666/supabaseService.uts
+++ /dev/null
@@ -1,3888 +0,0 @@
-import supa from '@/components/supadb/aksupainstance.uts'
-import type { AkReqResponse } from '@/uni_modules/ak-req/index.uts'
-
-// 使用单例 Supabase 客户端
-// const supa = createClient(SUPA_URL, SUPA_KEY)
-
-// 类型定义
-export type Brand = {
- id: string
- name: string
- logo_url: string
- description: string
-}
-
-export type Category = {
- id: string
- name: string
- icon: string
- description: string
- color: string
- level?: number
- parent_id?: string
- slug?: string
- created_at?: string
-}
-
-export type Product = {
- id: string
- category_id: string
- merchant_id: string
- name: string
- subtitle?: string
- description?: string
- base_price: number
- market_price?: number
- cost_price?: number
- main_image_url?: string
- image_urls?: string // JSON string array
- video_urls?: string // JSON string array
- sale_count?: number
- view_count?: number
- total_stock?: number
- available_stock?: number
- is_hot?: boolean
- is_new?: boolean
- is_featured?: boolean
- status?: number
- rating_avg?: number
- rating_count?: number
- tags?: string // array string in DB
- attributes?: string // JSON string
- created_at?: string
- updated_at?: string
- // Alias fields for compatibility
- price?: number
- original_price?: number
- stock?: number
- sales?: number
- images?: string
- cover?: string
- // View fields
- brand_name?: string
- category_name?: string
- shop_name?: string
- merchant_name?: string
-}
-
-export type Shop = {
- id: string
- merchant_id: string
- shop_name: string
- shop_logo?: string
- shop_banner?: string
- description?: string
- contact_name?: string
- contact_phone?: string
- rating_avg?: number
- total_sales?: number
- product_count?: number
- total_sales_count?: number
- created_at?: string
-}
-
-export type CartItem = {
- id: string
- user_id: string
- product_id: string
- sku_id?: string
- merchant_id?: string
- quantity: number
- selected: boolean
- product_name?: string
- product_image?: string
- product_price?: number
- product_specification?: string
- shop_id?: string
- shop_name?: string
- created_at?: string
- updated_at?: string
-}
-
-export type UserAddress = {
- id: string
- user_id: string
- recipient_name: string
- phone: string
- province: string
- city: string
- district: string
- detail_address: string
- postal_code?: string
- is_default: boolean
- label?: string
- created_at?: string
- updated_at?: string
-}
-
-export type UserCoupon = {
- id: string
- user_id: string
- template_id: string
- coupon_code: string
- status: number // 1: unused, 2: used, 3: expired
- received_at: string
- expire_at: string
- used_at?: string
- // join fields from template or view
- template_name?: string
- amount?: number
- min_spend?: number
- name?: string
- title?: string
-}
-
-export type ChatRoom = {
- id: string
- user_id: string
- merchant_id: string
- shop_name: string
- shop_logo?: string
- last_message?: string
- last_message_at?: string
- unread_count: number
- is_top: boolean
- created_at?: string
- updated_at?: string
-}
-
-export type Notification = {
- id: string
- user_id: string
- type: string
- title: string
- content: string
- icon_url?: string
- link_url?: string
- is_read: boolean
- extra_data?: string
- created_at?: string
-}
-
-export type ChatMessage = {
- id: string
- session_id?: string
- sender_id?: string
- receiver_id?: string
- content: string
- msg_type: string
- is_read: boolean
- is_from_user: boolean
- extra_data?: string
- created_at?: string
-}
-
-export type PaginatedResponse = {
- data: T[]
- total: number
- page: number
- limit: number
- hasmore: boolean
-}
-
-export type ProductSku = {
- id: string
- product_id: string
- sku_code: string
- specifications: string // JSON string
- price: number
- market_price?: number
- cost_price?: number
- stock?: number
- warning_stock?: number
- image_url?: string
- weight?: number
- status?: number
- created_at?: string
-}
-
-export type AddAddressParams = {
- recipient_name: string
- phone: string
- province: string
- city: string
- district: string
- detail_address: string
- postal_code?: string
- is_default?: boolean
- label?: string
-}
-
-export type UpdateAddressParams = {
- recipient_name?: string
- phone?: string
- province?: string
- city?: string
- district?: string
- detail_address?: string
- postal_code?: string
- is_default?: boolean
- label?: string
-}
-
-export type CreateOrderParams = {
- merchant_id: string
- product_amount: number
- shipping_fee: number
- total_amount: number
- shipping_address: any
- items: any[]
-}
-
-export type ShopOrderParams = {
- shipping_address: any
- shopGroups: any[]
- deliveryFee: number
- discountAmount: number
-}
-
-export type ShopOrderResponse = {
- success: boolean
- orderIds: string[]
- error?: string
-}
-
-export type RefundResponse = {
- success: boolean
- message: string
-}
-
-export type ConfirmReceiptResponse = {
- success: boolean
- error?: string
-}
-
-class SupabaseService {
- // 获取当前用户ID
- public getCurrentUserId(): string | null {
- try {
- // 1. 优先从 Supabase 会话获取
- const session = supa.getSession()
- if (session != null && session.user != null) {
- return session.user.getString('id')
- }
-
- // 2. 尝试从 Storage 恢复 Session (针对 App 重启后内存丢失的情况)
- // 注意:这里无法异步调用 hydrate,所以只能依赖 UI 层或 init 层的预加载
- // 但我们可以返回本地存储 ID 作为 fallback,前提是 Token 有效
-
- // 后备:尝试从本地存储获取
- const userId = uni.getStorageSync('user_id')
- return userId != null ? userId as string : null
- } catch (e) {
- console.error('获取用户ID失败:', e)
- return null
- }
- }
-
- // 确保会话有效 (异步)
- async ensureSession(): Promise {
- let session = supa.getSession()
- if (session.user == null) {
- console.log('Session user is null, attempting to hydrate from storage...')
- await supa.hydrateSessionFromStorage()
- session = supa.getSession()
- }
-
- if (session.user != null) {
- // 同步 user_id 到 storage 保持一致
- const uid = session.user!!.getString('id')
- if (uid != null) {
- uni.setStorageSync('user_id', uid)
- return uid
- }
- }
- return this.getCurrentUserId()
- }
-
- // 获取所有分类
- async getCategories(): Promise {
- try {
- const response = await supa
- .from('ml_categories')
- .select('*')
- .order('name', { ascending: true })
- .execute()
-
- if (response.error != null) {
- console.error('获取分类失败:', response.error)
- return []
- }
-
- return response.data as Category[]
- } catch (error) {
- console.error('获取分类异常:', error)
- return []
- }
- }
-
- // 获取一级分类
- async getParentCategories(): Promise {
- try {
- const response = await supa
- .from('ml_categories')
- .select('*')
- .is('parent_id', null)
- .order('sort_order', { ascending: true })
- .execute()
-
- if (response.error != null) {
- console.error('获取一级分类失败:', response.error)
- return []
- }
-
- const rawData = response.data
- if (rawData == null) {
- return []
- }
-
- const categories: Category[] = []
- const rawList = rawData as Array
- for (let i = 0; i < rawList.length; i++) {
- const item = rawList[i]
- const icon = this.getCategoryIcon(item)
- const cat: Category = {
- id: item['id'] as string,
- name: item['name'] as string,
- icon: icon,
- description: (item['description'] as string) ?? '',
- color: (item['color'] as string) ?? '#4CAF50',
- level: 1,
- slug: item['slug'] as string
- }
- categories.push(cat)
- }
- return categories
- } catch (error) {
- console.error('获取一级分类异常:', error)
- return []
- }
- }
-
- // 获取子分类
- async getSubCategories(parentId: string): Promise {
- try {
- const response = await supa
- .from('ml_categories')
- .select('*')
- .eq('parent_id', parentId)
- .order('sort_order', { ascending: true })
- .execute()
-
- if (response.error != null) {
- console.error('获取子分类失败:', response.error)
- return []
- }
-
- const rawData = response.data
- if (rawData == null) {
- return []
- }
-
- const categories: Category[] = []
- const rawList = rawData as Array
- for (let i = 0; i < rawList.length; i++) {
- const item = rawList[i]
- const icon = this.getCategoryIcon(item)
- const cat: Category = {
- id: item['id'] as string,
- name: item['name'] as string,
- icon: icon,
- description: (item['description'] as string) ?? '',
- color: (item['color'] as string) ?? '#4CAF50',
- level: 2,
- parent_id: item['parent_id'] as string,
- slug: item['slug'] as string
- }
- categories.push(cat)
- }
- return categories
- } catch (error) {
- console.error('获取子分类异常:', error)
- return []
- }
- }
-
- // 获取分类图标
- getCategoryIcon(item: UTSJSONObject): string {
- const iconUrl = (item['icon_url'] as string) ?? ''
- const slug = (item['slug'] as string) ?? ''
-
- // 如果 icon_url 已经是 emoji,直接返回
- if (iconUrl.length > 0 && iconUrl.length <= 4) {
- return iconUrl
- }
-
- // slug 映射到 emoji
- if (slug === 'digital') return '📱'
- if (slug === 'fashion') return '👕'
- if (slug === 'home') return '🏠'
- if (slug === 'food') return '🍎'
- if (slug === 'beauty') return '💄'
- if (slug === 'sports') return '⚽'
- if (slug === 'books') return '📚'
- if (slug === 'baby') return '👶'
- if (slug === 'health') return '💊'
- // 二级分类
- if (slug === 'mobile') return '📱'
- if (slug === 'computer') return '💻'
- if (slug === 'appliance') return '🎥'
- if (slug === 'accessories') return '🔌'
- if (slug === 'mens-wear') return '👔'
- if (slug === 'womens-wear') return '👗'
- if (slug === 'mens-shoes') return '👞'
- if (slug === 'womens-shoes') return '👠'
- if (slug === 'furniture') return '🛋️'
- if (slug === 'decoration') return '🖼️'
- if (slug === 'kitchen') return '🍳'
- if (slug === 'daily') return '🧹'
- if (slug === 'fruits') return '🍊'
- if (slug === 'meat') return '🥩'
- if (slug === 'snacks') return '🍪'
- if (slug === 'drinks') return '🍺'
-
- return '📂'
- }
-
- // 获取所有品牌
- async getBrands(): Promise {
- try {
- const response = await supa
- .from('ml_brands')
- .select('*')
- .eq('is_active', true)
- .order('name', { ascending: true })
- .execute()
-
- if (response.error != null) {
- console.error('获取品牌失败:', response.error)
- return []
- }
-
- return response.data as Brand[]
- } catch (error) {
- console.error('获取品牌异常:', error)
- return []
- }
- }
-
- // 获取指定分类的商品
- async getProductsByCategory(
- categoryId: string,
- page: number = 1,
- limit: number = 20
- ): Promise> {
- try {
- const response = await supa
- .from('ml_products_detail_view')
- .select('*', { count: 'exact' })
- .eq('category_id', categoryId)
- .eq('status', 1)
- .order('sale_count', { ascending: false })
- .page(page)
- .limit(limit)
- .execute()
-
- if (response.error != null) {
- console.error('获取商品失败:', response.error)
- return {
- data: [] as Product[],
- total: 0,
- page,
- limit,
- hasmore: false
- }
- }
-
- return {
- data: response.data as Product[],
- total: response.total ?? 0,
- page,
- limit,
- hasmore: response.hasmore ?? false
- }
- } catch (error) {
- console.error('获取商品异常:', error)
- return {
- data: [] as Product[],
- total: 0,
- page,
- limit,
- hasmore: false
- }
- }
- }
-
- // 根据商品ID获取SKU列表
- async getProductSkus(productId: string): Promise {
- try {
- const response = await supa
- .from('ml_product_skus')
- .select('*')
- .eq('product_id', productId)
- .eq('status', 1)
- .execute()
-
- if (response.error != null) {
- console.error('获取商品SKU失败:', response.error)
- return []
- }
-
- return response.data as ProductSku[]
- } catch (error) {
- console.error('获取商品SKU异常:', error)
- return []
- }
- }
-
- // 搜索商品
- async searchProducts(
- keyword: string,
- page: number = 1,
- limit: number = 20,
- sortBy: string = 'sales',
- ascending: boolean = false
- ): Promise> {
- try {
- let query = supa
- .from('ml_products_detail_view')
- .select('*', { count: 'exact' })
- .eq('status', 1)
- .or(`name.ilike.%${keyword}%,description.ilike.%${keyword}%,subtitle.ilike.%${keyword}%,brand_name.ilike.%${keyword}%`)
-
- // 根据sortBy和ascending设置排序
- if (sortBy === 'price') {
- query = query.order('base_price', { ascending })
- } else if (sortBy === 'sales' || sortBy === 'sale_count') {
- query = query.order('sale_count', { ascending: false }) // 销量总是降序
- } else {
- // 默认按销量降序
- query = query.order('sale_count', { ascending: false })
- }
-
- const response = await query
- .page(page)
- .limit(limit)
- .execute()
-
- if (response.error != null) {
- console.error('搜索商品失败:', response.error)
- return {
- data: [] as Product[],
- total: 0,
- page,
- limit,
- hasmore: false
- }
- }
-
- return {
- data: response.data as Product[],
- total: response.total ?? 0,
- page,
- limit,
- hasmore: response.hasmore ?? false
- }
- } catch (error) {
- console.error('搜索商品异常:', error)
- return {
- data: [] as Product[],
- total: 0,
- page,
- limit,
- hasmore: false
- }
- }
- }
-
- // 搜索店铺
- async searchShops(
- keyword: string,
- page: number = 1,
- limit: number = 20
- ): Promise> {
- try {
- const response = await supa
- .from('ml_shops')
- .select('*', { count: 'exact' })
- .eq('status', 1)
- .ilike('shop_name', `%${keyword}%`)
- .order('product_count', { ascending: false })
- .page(page)
- .limit(limit)
- .execute()
-
- if (response.error != null) {
- console.error('搜索店铺失败:', response.error)
- return { data: [] as Shop[], total: 0, page, limit, hasmore: false }
- }
-
- // 映射数据确保类型安全
- const shops: Shop[] = []
- const dataList = response.data as any[]
- for (let i = 0; i < dataList.length; i++) {
- shops.push(dataList[i] as Shop)
- }
-
- return {
- data: shops,
- total: response.total ?? 0,
- 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 {
- const response = await supa
- .from('ml_products_detail_view')
- .select('*')
- .eq('id', productId)
- .single()
- .executeAs()
-
- if (response.error != null) {
- console.error('获取商品详情失败:', response.error)
- return null
- }
-
- return response.data as 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 {
- // 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) {
- return (response.data as any[])[0] as 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')
- // Fix the merchant_id reference if we found it by ID
- const shop = (response.data as any[])[0] as Shop
- return shop
- }
-
- if (response.error != null) {
- console.error('获取店铺信息失败:', response.error)
- }
- return null
- } catch (error) {
- console.error('获取店铺信息异常:', error)
- return null
- }
- }
-
- // 根据商户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`)
-
- // Map raw data to Product interface (manually if needed for extra safety)
- const mappedData: Product[] = []
- const rawData = res2.data as any[]
- for(let i = 0; i < rawData.length; i++) {
- mappedData.push(rawData[i] as Product)
- }
-
- return {
- data: mappedData,
- total: res2.total ?? 0,
- page,
- limit,
- hasmore: res2.hasmore ?? false
- }
- }
-
- console.log(`Merchant products found: ${(response.data as any[]).length}`)
- return {
- data: response.data as Product[],
- total: response.total ?? 0,
- page,
- limit,
- hasmore: response.hasmore ?? false
- }
- } catch (error) {
- console.error('获取商户商品异常:', error)
- return {
- data: [] as Product[],
- total: 0,
- page,
- limit,
- hasmore: false
- }
- }
- }
-
- // 获取热销商品(按销量排序)
- async getHotProducts(limit: number = 10): Promise {
- try {
- const response = await supa
- .from('ml_products_detail_view')
- .select('*')
- .eq('is_hot', true)
- .eq('status', 1)
- .order('sale_count', { ascending: false })
- .limit(limit)
- .execute()
-
- if (response.error != null) {
- console.error('获取热销商品失败:', response.error)
- return []
- }
-
- return response.data as Product[]
- } catch (error) {
- console.error('获取热销商品异常:', error)
- return []
- }
- }
-
- // 获取按价格排序的商品(升序:从低到高)
- async getProductsByPrice(limit: number = 10, ascending: boolean = true): Promise {
- try {
- const response = await supa
- .from('ml_products_detail_view')
- .select('*')
- .eq('status', 1)
- .order('base_price', { ascending })
- .limit(limit)
- .execute()
-
- if (response.error != null) {
- console.error('获取价格排序商品失败:', response.error)
- return []
- }
-
- return response.data as Product[]
- } catch (error) {
- console.error('获取价格排序商品异常:', error)
- return []
- }
- }
-
- // 获取新品(按创建时间排序,最新的在前)
- async getProductsByNewest(limit: number = 10): Promise {
- try {
- const response = await supa
- .from('ml_products_detail_view')
- .select('*')
- .eq('is_new', true)
- .eq('status', 1)
- .order('published_at', { ascending: false }) // Use published_at for newest
- .limit(limit)
- .execute()
-
- if (response.error != null) {
- console.error('获取新品失败:', response.error)
- return []
- }
-
- return response.data as Product[]
- } catch (error) {
- console.error('获取新品异常:', error)
- return []
- }
- }
-
- // 获取推荐商品(is_featured=true)
- async getRecommendedProducts(limit: number = 10): Promise {
- try {
- // 查询 is_featured = true 的商品
- const response = await supa
- .from('ml_products_detail_view')
- .select('*')
- .eq('is_featured', true)
- .eq('status', 1)
- .order('sale_count', { ascending: false })
- .limit(limit)
- .execute()
-
- if (response.error != null) {
- console.error('获取推荐商品失败:', response.error)
- return []
- }
-
- console.log('推荐商品查询结果条数:', (response.data as any[])?.length ?? 0)
- const data = response.data as Product[]
- return data ?? []
- } catch (error) {
- console.error('获取推荐商品异常:', error)
- return []
- }
- }
-
- // 获取特价商品(这里假设没有specific flag, just use logic or tag if exists, defaulting to hot for now or just skip)
- // Modify to use compatible logic if badge column doesn't exist
- async getDiscountProducts(limit: number = 10): Promise {
- return [] as Product[] // 暂无特价字段
- }
-
- // 获取当前用户的购物车商品(关联商品和店铺信息)
- async getCartItems(): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) {
- console.warn('用户未登录,无法获取购物车')
- return []
- }
-
- // 查询购物车表,并关联商品表(使用内联关联)
- // 注意:使用 !inner 进行内连接,或者 left join (默认)
- // 修改查询语法以符合 PostgREST 规范
- // 尝试简化查询,先只查购物车,再查商品,避免复杂的嵌套查询导致 400 错误
- const response = await supa
- .from('ml_shopping_cart')
- .select('*')
- .eq('user_id', userId)
- .order('created_at', { ascending: false })
- .execute()
-
- if (response.error != null) {
- console.error('获取购物车失败:', response.error)
- return []
- }
-
- const cartData = response.data as any[]
- // console.log('Raw Cart Data:', JSON.stringify(cartData))
-
- if (cartData == null || cartData.length === 0) {
- return []
- }
-
- // 收集所有 product_id 和 sku_id
- const productIds: string[] = []
- const skuIds: string[] = []
- for (let i = 0; i < cartData.length; i++) {
- let item = cartData[i]
- let pid: string = ''
- let sid: string = ''
- if (item instanceof UTSJSONObject) {
- pid = item.getString('product_id') ?? ''
- sid = item.getString('sku_id') ?? ''
- } else {
- const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
- pid = itemObj.getString('product_id') ?? ''
- sid = itemObj.getString('sku_id') ?? ''
- }
- if (pid !== '' && !productIds.includes(pid)) {
- productIds.push(pid)
- }
- if (sid !== '' && !skuIds.includes(sid)) {
- skuIds.push(sid)
- }
- }
-
- // 批量查询商品详情 (使用视图关联店铺信息,修复字段名 specification -> attributes)
- const productMap = new Map()
-
- if (productIds.length > 0) {
- // Convert string array to any array for .in()
- const productIdsAny: any[] = []
- for(let i=0; i()
- if (skuIds.length > 0) {
- const skuIdsAny: any[] = []
- for(let i=0; i 0) {
- productPrice = skuPrice
- }
- const skuImg = sku.getString('image_url')
- if (skuImg != null && skuImg !== '') {
- productImage = skuImg
- }
-
- const specRaw = sku.get('specifications')
- if (specRaw != null) {
- // 优先使用SKU的规格
- if (typeof specRaw === 'string') {
- productSpec = specRaw
- } else if (specRaw instanceof UTSJSONObject) {
- const keys = UTSJSONObject.keys(specRaw)
- const parts: string[] = []
- for(let k = 0; k < keys.length; k++) {
- let val = specRaw.get(keys[k])
- if (val != null) {
- parts.push(`${keys[k]}: ${val}`)
- }
- }
- productSpec = parts.join('; ')
- } else {
- try {
- let jsonStr = JSON.stringify(specRaw)
- productSpec = jsonStr.replace(/["{}]/g, '').replace(/,/g, '; ')
- } catch (e) {}
- }
- }
- } else {
- const sObj = JSON.parse(JSON.stringify(sku)) as UTSJSONObject
- const skuPrice = sObj.getNumber('price') ?? 0
- if (skuPrice > 0) productPrice = skuPrice
-
- const skuImg = sObj.getString('image_url') ?? ''
- if (skuImg !== '') productImage = skuImg
-
- const specRaw = sObj.get('specifications')
- if (specRaw != null) {
- // 优先使用SKU的规格
- if (typeof specRaw === 'string') {
- productSpec = specRaw
- } else if (specRaw instanceof UTSJSONObject) {
- const keys = UTSJSONObject.keys(specRaw)
- const parts: string[] = []
- for(let k = 0; k < keys.length; k++) {
- let val = specRaw.get(keys[k])
- if (val != null) {
- parts.push(`${keys[k]}: ${val}`)
- }
- }
- productSpec = parts.join('; ')
- } else {
- try {
- let jsonStr = JSON.stringify(specRaw)
- productSpec = jsonStr.replace(/["{}]/g, '').replace(/,/g, '; ')
- } catch (e) {}
- }
- }
- }
- }
-
-
-
- let shopIdStr = merchantId != '' ? merchantId : 'unknown_shop'
-
-
- cartItems.push({
- id: itemId,
- user_id: userIdVal,
- product_id: productId,
- sku_id: skuId,
- merchant_id: merchantId,
- quantity: quantity,
- selected: selected,
- product_name: productName,
- product_image: productImage,
- product_price: productPrice,
- product_specification: productSpec,
- shop_id: shopIdStr,
- shop_name: shopNameStr,
- created_at: createdAt,
- updated_at: updatedAt
- } as CartItem)
- }
- }
-
- return cartItems
- } catch (error) {
- console.error('获取购物车异常:', error)
- return []
- }
- }
-
- // 获取用户通知 (系统、活动、订单)
- async getUserNotifications(type: string | null = null): Promise {
- try {
- 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 []
- }
- return response.data as Notification[]
- } catch (e) {
- console.error('获取通知异常:', e)
- return []
- }
- }
-
- // 获取聊天会话列表
- async getChatRooms(): Promise {
- try {
- 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 []
- }
- return response.data as ChatRoom[]
- } catch (e) {
- console.error('获取聊天会话异常:', e)
- return []
- }
- }
-
- // 获取用户聊天消息
- async getUserChatMessages(): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) return []
-
- const response = await supa
- .from('ml_chat_messages')
- .select('*')
- .or(`sender_id.eq.${userId},receiver_id.eq.${userId}`)
- .order('created_at', { ascending: false })
- .limit(50)
- .execute()
-
- if (response.error != null) {
- console.error('获取聊天记录失败:', response.error)
- return []
- }
- return response.data as ChatMessage[]
- } catch (e) {
- console.error('获取聊天记录异常:', e)
- return []
- }
- }
-
- // 获取与特定商家的聊天记录
- async getChatMessages(merchantId: string): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) return []
-
- const response = await supa
- .from('ml_chat_messages')
- .select('*')
- .or(`and(sender_id.eq.${userId},receiver_id.eq.${merchantId}),and(sender_id.eq.${merchantId},receiver_id.eq.${userId})`)
- .order('created_at', { ascending: false })
- .limit(50)
- .execute()
-
- if (response.error != null) {
- console.error('获取聊天记录失败:', response.error)
- return []
- }
- return response.data as ChatMessage[]
- } catch (e) {
- console.error('获取聊天记录异常:', e)
- return []
- }
- }
-
- // 发送聊天消息
- async sendChatMessage(content: string, toId: string | null = null, type: string = 'text'): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) return false
-
- const payload = {
- sender_id: userId,
- content: content,
- msg_type: type,
- is_from_user: true,
- created_at: new Date().toISOString()
- } as UTSJSONObject
- if (toId != null) {
- payload.set('receiver_id', toId)
- }
-
- const response = await supa
- .from('ml_chat_messages')
- .insert(payload)
- .execute()
-
- if (response.error != null) {
- console.error('发送消息失败:', response.error)
- return false
- }
- return true
- } catch (e) {
- console.error('发送消息异常:', e)
- return false
- }
- }
-
- // 模拟客服回复
- async simulateServiceReply(content: string): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) return false
-
- const response = await supa
- .from('ml_chat_messages')
- .insert({
- receiver_id: userId,
- content: content,
- msg_type: 'text',
- is_from_user: false,
- created_at: new Date().toISOString()
- })
- .execute()
- return response.error == null
- } catch (e) {
- return false
- }
- }
-
- // 添加商品到购物车
- async addToCart(productId: string, quantity: number = 1, skuId: string = '', merchantId: string = ''): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) {
- console.error('用户未登录,无法添加商品到购物车')
- return false
- }
-
- const realSkuId = (skuId != null && skuId.length > 0) ? skuId : null
- const realMerchantId = (merchantId != null && merchantId.length > 0) ? merchantId : null
-
- // 检查商品是否已在购物车中
- // 注意:必须处理 sku_id 为空的情况,使用 is.null 过滤器
- let query = supa
- .from('ml_shopping_cart')
- .select('*')
- .eq('user_id', userId)
- .eq('product_id', productId)
-
- if (realSkuId != null) {
- query = query.eq('sku_id', realSkuId)
- } else {
- query = query.is('sku_id', null)
- }
-
- const existingResponse = await query.single().execute()
-
- let existingItem: any | null = null
-
- if (existingResponse.data != null) {
- const rawData = existingResponse.data as any
- if (Array.isArray(rawData)) {
- if (rawData.length > 0) {
- existingItem = rawData[0]
- }
- } else {
- existingItem = rawData
- }
- }
-
- let response: AkReqResponse
- if (existingItem != null) {
- // 商品已存在,更新数量
- console.log('Found existing cart item:', JSON.stringify(existingItem))
-
- // 确保 existingItem.id 存在
- let itemId: string | null = null
- let itemQty: any | null = null
-
- if (existingItem instanceof UTSJSONObject) {
- itemId = existingItem.getString('id')
- itemQty = existingItem.getNumber('quantity')
- } else {
- const obj = JSON.parse(JSON.stringify(existingItem)) as UTSJSONObject
- itemId = obj.getString('id')
- itemQty = obj.getNumber('quantity')
- }
-
- if (itemId != null) {
- let currentQty = 0
- if (typeof itemQty === 'number') {
- currentQty = itemQty as number
- } else {
- const qStr = `${itemQty ?? 0}`
- currentQty = parseInt(qStr)
- }
- const newQty = currentQty + quantity
-
- response = await supa
- .from('ml_shopping_cart')
- .update({
- quantity: newQty,
- merchant_id: realMerchantId,
- updated_at: new Date().toISOString()
- })
- .eq('id', itemId)
- .execute()
- } else {
- console.error('购物车已有商品但缺少ID,无法更新. Data:', JSON.stringify(existingItem))
- return false
- }
- } else {
- // 商品不存在,添加新记录
- const cartPayload = new UTSJSONObject()
- cartPayload.set('user_id', userId)
- cartPayload.set('product_id', productId)
- cartPayload.set('sku_id', realSkuId)
- cartPayload.set('quantity', quantity)
- cartPayload.set('selected', true)
- cartPayload.set('created_at', new Date().toISOString())
- cartPayload.set('updated_at', new Date().toISOString())
- if (realMerchantId != null) {
- cartPayload.set('merchant_id', realMerchantId)
- }
-
- response = await supa
- .from('ml_shopping_cart')
- .insert(cartPayload)
- .execute()
- }
-
- if (response.error != null) {
- console.error('添加商品到购物车失败:', response.error)
- return false
- }
-
- return true
- } catch (error) {
- console.error('添加商品到购物车异常:', error)
- return false
- }
- }
-
- // 更新购物车商品数量
- async updateCartItemQuantity(cartItemId: string, quantity: number): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) {
- console.error('用户未登录,无法更新购物车')
- return false
- }
-
- if (quantity < 1) {
- // 数量小于1时删除商品
- return await this.deleteCartItem(cartItemId)
- }
-
- const response = await supa
- .from('ml_shopping_cart')
- .update({
- quantity: quantity,
- updated_at: new Date().toISOString()
- })
- .eq('id', cartItemId)
- .eq('user_id', userId)
- .execute()
-
- if (response.error != null) {
- console.error('更新购物车商品数量失败:', response.error)
- return false
- }
-
- return true
- } catch (error) {
- console.error('更新购物车商品数量异常:', error)
- return false
- }
- }
-
- // 更新购物车商品选中状态
- async updateCartItemSelection(cartItemId: string, selected: boolean): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) {
- console.error('用户未登录,无法更新购物车')
- return false
- }
-
- const response = await supa
- .from('ml_shopping_cart')
- .update({
- selected: selected,
- updated_at: new Date().toISOString()
- })
- .eq('id', cartItemId)
- .eq('user_id', userId)
- .execute()
-
- if (response.error != null) {
- console.error('更新购物车商品选中状态失败:', response.error)
- return false
- }
-
- return true
- } catch (error) {
- console.error('更新购物车商品选中状态异常:', error)
- return false
- }
- }
-
- // 批量更新购物车商品选中状态
- async batchUpdateCartItemSelection(cartItemIds: string[], selected: boolean): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) {
- console.error('用户未登录,无法更新购物车')
- return false
- }
-
- const response = await supa
- .from('ml_shopping_cart')
- .update({
- selected: selected,
- updated_at: new Date().toISOString()
- })
- .eq('user_id', userId)
- .in('id', cartItemIds as any[])
- .execute()
-
- if (response.error != null) {
- console.error('批量更新购物车商品选中状态失败:', response.error)
- return false
- }
-
- return true
- } catch (error) {
- console.error('批量更新购物车商品选中状态异常:', error)
- return false
- }
- }
-
- // 删除购物车商品
- async deleteCartItem(cartItemId: string): Promise {
- return true
- /*
- try {
- console.log('正在执行删除购物车商品,ID:', cartItemId)
- const userId = this.getCurrentUserId()
- if (!userId) {
- console.error('用户未登录,无法删除购物车商品')
- return false
- }
-
- const response = await supa
- .from('ml_shopping_cart')
- .eq('id', cartItemId)
- .eq('user_id', userId)
- .delete()
- .execute()
-
- if (response.error) {
- console.error('删除购物车商品失败:', response.error)
- return false
- }
-
- return true
- } catch (error) {
- console.error('删除购物车商品异常:', error)
- return false
- }
- */
- }
-
- // 批量删除购物车商品
- async batchDeleteCartItems(cartItemIds: string[]): Promise {
- return true
- /*
- try {
- const userId = this.getCurrentUserId()
- if (!userId) {
- console.error('用户未登录,无法删除购物车商品')
- return false
- }
-
- const response = await supa
- .from('ml_shopping_cart')
- .eq('user_id', userId)
- .in('id', cartItemIds)
- .delete()
- .execute()
-
- if (response.error) {
- console.error('批量删除购物车商品失败:', response.error)
- return false
- }
-
- return true
- } catch (error) {
- console.error('批量删除购物车商品异常:', error)
- return false
- }
- */
- }
-
- // 清空购物车
- async clearCart(): Promise {
- return true
- /*
- try {
- const userId = this.getCurrentUserId()
- if (!userId) {
- console.error('用户未登录,无法清空购物车')
- return false
- }
-
- const response = await supa
- .from('ml_shopping_cart')
- .eq('user_id', userId)
- .delete()
- .execute()
-
- if (response.error) {
- console.error('清空购物车失败:', response.error)
- return false
- }
-
- return true
- } catch (error) {
- console.error('清空购物车异常:', error)
- return false
- }
- */
- }
-
- // 获取当前用户的所有地址
- async getAddresses(): Promise {
- 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 addrObj = new UTSJSONObject()
- addrObj.set('id', itemObj.getString('id') ?? '')
- addrObj.set('user_id', itemObj.getString('user_id') ?? '')
- addrObj.set('recipient_name', itemObj.getString('receiver_name') ?? itemObj.getString('recipient_name') ?? '')
- addrObj.set('phone', itemObj.getString('receiver_phone') ?? itemObj.getString('phone') ?? '')
- addrObj.set('province', itemObj.getString('province') ?? '')
- addrObj.set('city', itemObj.getString('city') ?? '')
- addrObj.set('district', itemObj.getString('district') ?? '')
- addrObj.set('detail_address', itemObj.getString('address_detail') ?? itemObj.getString('detail_address') ?? '')
- addrObj.set('is_default', itemObj.getBoolean('is_default') ?? false)
- result.push(addrObj as UserAddress)
- }
-
- console.log('[getAddresses] 返回地址数量:', result.length)
- return result
- } catch (error) {
- console.error('[getAddresses] 获取地址异常:', error)
- return []
- }
- }
-
- // 根据ID获取地址详情
- async getAddressById(addressId: string): Promise {
- const userId = this.getCurrentUserId()
- if (userId == null) {
- console.warn('用户未登录,无法获取地址')
- return null
- }
-
- try {
- const query = supa
- .from('ml_user_addresses')
- .select('*, recipient_name:receiver_name, phone:receiver_phone, detail_address:address_detail')
- .eq('id', addressId)
- .eq('user_id', userId)
- .single()
-
- const response = await query.execute()
-
- if (response.error != null) {
- console.error('获取地址详情失败:', response.error)
- return null
- }
-
- return response.data as UserAddress
- } catch (error) {
- console.error('获取地址详情异常:', error)
- return null
- }
- }
-
- // 添加新地址
- async addAddress(address: AddAddressParams): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) {
- console.error('用户未登录,无法添加地址')
- return false
- }
-
- // 如果设置为默认地址,需要先取消其他默认地址
- if (address.is_default == true) {
- await this.clearDefaultAddress(userId)
- }
-
- const response = await supa
- .from('ml_user_addresses')
- .insert({
- user_id: userId,
- receiver_name: address.recipient_name,
- receiver_phone: address.phone,
- province: address.province,
- city: address.city,
- district: address.district,
- address_detail: address.detail_address,
- postal_code: address.postal_code ?? null,
- is_default: address.is_default ?? false,
- created_at: new Date().toISOString(),
- updated_at: new Date().toISOString()
- })
- .execute()
-
- if (response.error != null) {
- console.error('添加地址失败:', response.error)
- return false
- }
-
- return true
- } catch (error) {
- console.error('添加地址异常:', error)
- return false
- }
- }
-
- // 更新地址
- async updateAddress(addressId: string, address: UpdateAddressParams): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) {
- console.error('用户未登录,无法更新地址')
- return false
- }
-
- // 如果设置为默认地址,需要先取消其他默认地址
- if (address.is_default == true) {
- await this.clearDefaultAddress(userId)
- }
-
- // 构造更新数据,映射字段名到数据库列名
- const updateData = {}
- if (address.recipient_name != null) updateData['receiver_name'] = address.recipient_name
- if (address.phone != null) updateData['receiver_phone'] = address.phone
- if (address.province != null) updateData['province'] = address.province
- if (address.city != null) updateData['city'] = address.city
- if (address.district != null) updateData['district'] = address.district
- if (address.detail_address != null) updateData['address_detail'] = address.detail_address
- if (address.postal_code != null) updateData['postal_code'] = address.postal_code
- if (address.is_default != null) updateData['is_default'] = address.is_default
- if (address.label != null) updateData['label'] = address.label
- updateData['updated_at'] = new Date().toISOString()
-
- const response = await supa
- .from('ml_user_addresses')
- .update(updateData)
- .eq('id', addressId)
- .eq('user_id', userId)
- .execute()
-
- if (response.error != null) {
- console.error('更新地址失败:', response.error)
- return false
- }
-
- return true
- } catch (error) {
- console.error('更新地址异常:', error)
- return false
- }
- }
-
- // 确认收货
- async confirmReceipt(orderId: string): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) {
- return { success: false, error: '用户未登录' }
- }
-
- const response = await supa
- .from('ml_orders')
- .update({
- order_status: 4, // 4: 已完成
- delivered_at: new Date().toISOString(),
- completed_at: new Date().toISOString(), // 也更新完成时间
- updated_at: new Date().toISOString()
- })
- .eq('id', orderId)
- .eq('user_id', userId)
- .execute()
-
- if (response.error != null) {
- return { success: false, error: response.error.message }
- }
-
- return { success: true }
- } catch (e: any) {
- return { success: false, error: e.message }
- }
- }
-
- // 删除地址
- async deleteAddress(addressId: string): Promise {
- try {
- console.log('正在执行删除地址,ID:', addressId)
- const userId = this.getCurrentUserId()
- if (userId == null) {
- console.error('用户未登录,无法删除地址')
- return false
- }
-
- const response = await supa
- .from('ml_user_addresses')
- .eq('id', addressId)
- .eq('user_id', userId)
- .delete()
- .execute()
-
- if (response.error != null) {
- console.error('删除地址失败:', response.error)
- return false
- }
-
- return true
- } catch (error) {
- console.error('删除地址异常:', error)
- return false
- }
- }
-
- // 清除默认地址(内部使用)
- private async clearDefaultAddress(userId: string): Promise {
- try {
- await supa
- .from('ml_user_addresses')
- .update({
- is_default: false,
- updated_at: new Date().toISOString()
- })
- .eq('user_id', userId)
- .eq('is_default', true)
- .execute()
- } catch (error) {
- console.error('清除默认地址异常:', error)
- }
- }
-
- // 获取用户资料
- async getUserProfile(): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) return null
-
- // 联合查询 auth user 和 profile
- // 由于 Supabase auth table 不可直接访问,这里查询 ml_user_profiles
- const response = await supa
- .from('ml_user_profiles')
- .select('*')
- .eq('user_id', userId)
- .single()
- .execute()
-
- if (response.error != null) {
- // 如果不存在 profile,可能只有 auth user,这里暂时返回空或创建默认
- return null
- }
- return response.data
- } 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
- if (merchantId == null || merchantId == '' || merchantId == 'unknown') {
- merchantId = userId
- }
-
- let shippingAddrStr = '{}'
- if (orderData.shipping_address != null) {
- if (typeof orderData.shipping_address === 'string') {
- shippingAddrStr = orderData.shipping_address
- } else {
- shippingAddrStr = JSON.stringify(orderData.shipping_address)
- }
- }
-
- const orderPayload = new UTSJSONObject()
- orderPayload.set('user_id', userId)
- orderPayload.set('merchant_id', merchantId)
- orderPayload.set('order_no', orderNo)
- orderPayload.set('product_amount', orderData.product_amount)
- orderPayload.set('shipping_fee', orderData.shipping_fee)
- orderPayload.set('total_amount', orderData.total_amount)
- orderPayload.set('paid_amount', 0)
- orderPayload.set('shipping_address', shippingAddrStr)
- orderPayload.set('order_status', 1)
- orderPayload.set('payment_status', 1)
- orderPayload.set('shipping_status', 1)
- orderPayload.set('created_at', new Date().toISOString())
- orderPayload.set('updated_at', new Date().toISOString())
-
- console.log('[CreateOrder] 插入订单数据:', JSON.stringify(orderPayload))
- console.log('[CreateOrder] 期望的订单号:', orderNo)
-
- const orderResponse = await supa
- .from('ml_orders')
- .insert(orderPayload)
- .execute()
-
- console.log('[CreateOrder] insert 完成')
- console.log('[CreateOrder] orderResponse.error:', orderResponse.error)
-
- if (orderResponse.error != null) {
- console.error('[CreateOrder] 创建订单失败:', orderResponse.error)
- return null
- }
-
- console.log('[CreateOrder] 开始查询新创建的订单, order_no:', orderNo)
-
- const queryResponse = await supa
- .from('ml_orders')
- .select('id, order_no')
- .eq('order_no', orderNo)
- .execute()
-
- console.log('[CreateOrder] queryResponse.error:', queryResponse.error)
- console.log('[CreateOrder] queryResponse.data:', JSON.stringify(queryResponse.data))
-
- if (queryResponse.error != null) {
- console.error('[CreateOrder] 查询订单失败:', queryResponse.error)
- return null
- }
-
- const queryData = queryResponse.data as any
- let orderId = ''
-
- if (Array.isArray(queryData) && queryData.length > 0) {
- const firstItem = queryData[0] as Record
- orderId = firstItem['id'] as string
- console.log('[CreateOrder] 找到新创建的订单, id:', orderId)
- } else {
- console.error('[CreateOrder] 未找到新创建的订单,插入可能失败')
- return null
- }
-
- console.log('[CreateOrder] 订单创建成功, orderId:', orderId)
-
- const orderItems: UTSJSONObject[] = []
- const rawItems = orderData.items as any[]
-
- for(let i = 0; i < rawItems.length; i++) {
- let item: UTSJSONObject
- const rawItem = rawItems[i]
- item = rawItem as UTSJSONObject
-
- const itemJson = new UTSJSONObject()
-
- let pId = item.get('product_id')
- if (pId == null) {
- pId = item.get('id')
- }
-
- itemJson.set('order_id', orderId)
- itemJson.set('product_id', pId)
-
- const skuIdVal = item.get('sku_id')
- if (skuIdVal != null && skuIdVal !== '') {
- itemJson.set('sku_id', skuIdVal)
- }
-
- itemJson.set('product_name', item.get('product_name') ?? '')
-
- const sName = item.get('sku_name')
- itemJson.set('sku_name', sName ?? '')
-
- const specVal = item.get('specifications')
- let skuSnapshot = '{}'
- if (specVal != null) {
- if (typeof specVal === 'string') {
- skuSnapshot = specVal as string
- } else {
- skuSnapshot = JSON.stringify(specVal)
- }
- }
- itemJson.set('sku_snapshot', skuSnapshot)
- itemJson.set('specifications', skuSnapshot)
-
- const img1 = item.get('product_image')
- const img2 = item.get('image_url')
- let imgUrl = (img1 ?? img2 ?? '') as string
- while (imgUrl.indexOf('`') >= 0) {
- imgUrl = imgUrl.replace('`', '')
- }
- itemJson.set('image_url', imgUrl)
-
- const iPrice = item.getNumber('price') ?? 0
- const iQty = item.getNumber('quantity') ?? 1
- itemJson.set('price', iPrice)
- itemJson.set('quantity', iQty)
- itemJson.set('total_amount', iPrice * iQty)
- itemJson.set('created_at', new Date().toISOString())
-
- orderItems.push(itemJson)
- }
-
- console.log('[CreateOrder] 插入订单项数量:', orderItems.length)
- console.log('[CreateOrder] 订单项数据:', JSON.stringify(orderItems))
-
- const itemsResponse = await supa
- .from('ml_order_items')
- .insert(orderItems)
- .execute()
-
- if (itemsResponse.error != null) {
- console.error('[CreateOrder] 创建订单项失败:', itemsResponse.error)
- console.error('[CreateOrder] 错误详情:', JSON.stringify(itemsResponse.error))
- console.log('[CreateOrder] 订单主表已创建,但订单项插入失败,返回订单ID')
- return orderId
- }
-
- console.log('[CreateOrder] 订单项创建成功')
-
- const cartItemIds: string[] = []
- for(let i = 0; i < rawItems.length; i++) {
- const item = rawItems[i] as UTSJSONObject
- const iid = item.getString('id')
- if (iid != null && iid.length > 10) {
- cartItemIds.push(iid)
- }
- }
-
- if (cartItemIds.length > 0) {
- await this.batchDeleteCartItems(cartItemIds)
- }
-
- return orderId
- } catch (error) {
- console.error('[CreateOrder] 创建订单异常:', error)
- return null
- }
- }
-
- // 批量通过店铺创建订单
- async createOrdersByShop(params: ShopOrderParams): Promise {
- try {
- const orderIds: string[] = []
- const groups = params.shopGroups as any[]
-
- let grandTotal = 0.0
- for(let k = 0; k < groups.length; k++) {
- const g = groups[k] as UTSJSONObject
- // 安全获取 items 数组
- const gItemsRaw = g.get('items')
- if (gItemsRaw == null) continue
- const gItems = gItemsRaw as any[]
-
- for(let gi = 0; gi < gItems.length; gi++) {
- const it = gItems[gi] as UTSJSONObject
- const itPrice = it.getNumber('price') ?? 0
- const itQty = it.getNumber('quantity') ?? 1
- grandTotal += itPrice * itQty
- }
- }
-
- // 为每个店铺创建一个订单
- for (let i = 0; i < groups.length; i++) {
- const group = 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 = shopItems[j] as UTSJSONObject
- const siPrice = sItem.getNumber('price') ?? 0
- const siQty = sItem.getNumber('quantity') ?? 1
- productAmount += siPrice * siQty
- }
-
- // 简单平摊运费和优惠 (实际逻辑可能更复杂)
- const ratio = grandTotal > 0 ? (productAmount / grandTotal) : 0
- const shopShippingFee = params.deliveryFee * ratio
- const shopDiscount = params.discountAmount * ratio
- const shopTotal = productAmount + shopShippingFee - shopDiscount
-
- const mId = group.getString('merchant_id')
- const sId = group.getString('shopId')
- const shopName = group.getString('shopName')
-
- const orderId = await this.createOrder({
- merchant_id: (mId != null && mId != '') ? mId : (sId ?? ''), // 兼容旧字段
- product_amount: productAmount,
- shipping_fee: shopShippingFee,
- total_amount: shopTotal,
- shipping_address: params.shipping_address,
- items: shopItems
- })
-
- 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 (*)
- `)
- .eq('user_id', userId)
- .order('created_at', { ascending: false })
-
- if (status > 0) {
- query = query.eq('order_status', status)
- }
-
- const response = await query.execute()
-
- if (response.error != null) {
- console.error('获取订单列表失败:', response.error)
- const empty: any[] = []
- return empty
- }
-
- const data = response.data
- if (data == null) {
- const empty: any[] = []
- return empty
- }
- return data as any[]
- } catch (error) {
- console.error('获取订单列表异常:', error)
- const empty: any[] = []
- return empty
- }
- }
-
- // 获取订单详情
- async getOrderDetail(orderId: string): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) return null
-
- const response = await supa
- .from('ml_orders')
- .select(`
- *,
- ml_order_items (*)
- `)
- .eq('id', orderId)
- .eq('user_id', userId)
- .single()
- .execute()
-
- if (response.error != null) {
- return null
- }
- return response.data
- } catch (e) {
- return null
- }
- }
-
- // 支付订单
- async payOrder(orderId: string, paymentMethod: string, amount: number): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) {
- console.error('[payOrder] 用户未登录')
- return false
- }
-
- console.log('[payOrder] 开始更新订单状态, orderId:', orderId, 'userId:', userId)
-
- const updatePayload = new UTSJSONObject()
- updatePayload.set('order_status', 2)
- updatePayload.set('payment_status', 1)
- updatePayload.set('payment_method', paymentMethod)
- updatePayload.set('payment_time', new Date().toISOString())
- updatePayload.set('paid_amount', amount)
- updatePayload.set('updated_at', new Date().toISOString())
-
- console.log('[payOrder] 更新数据:', JSON.stringify(updatePayload))
-
- const response = await supa
- .from('ml_orders')
- .update(updatePayload)
- .eq('id', orderId)
- .eq('user_id', userId)
- .execute()
-
- if (response.error != null) {
- console.error('[payOrder] 更新订单失败:', response.error)
- return false
- }
-
- console.log('[payOrder] 订单状态更新成功')
-
- if (paymentMethod === 'balance') {
- console.log('[payOrder] 余额支付,暂不扣减余额')
- }
-
- return true
- } catch (e) {
- console.error('[payOrder] 支付异常:', e)
- return false
- }
- }
-
- // 根据ID获取订单信息
- async getOrderById(orderId: string): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) {
- console.error('[getOrderById] 用户未登录')
- return null
- }
-
- console.log('[getOrderById] 查询订单, orderId:', orderId)
-
- const response = await supa
- .from('ml_orders')
- .select('*')
- .eq('id', orderId)
- .eq('user_id', userId)
- .execute()
-
- if (response.error != null) {
- console.error('[getOrderById] 查询订单失败:', response.error)
- return null
- }
-
- const data = response.data as any[]
- if (data == null || data.length === 0) {
- console.log('[getOrderById] 未找到订单')
- return null
- }
-
- const orderRaw = data[0]
- let orderObj: UTSJSONObject
- if (orderRaw instanceof UTSJSONObject) {
- orderObj = orderRaw as UTSJSONObject
- } else {
- orderObj = JSON.parse(JSON.stringify(orderRaw)) as UTSJSONObject
- }
-
- console.log('[getOrderById] 订单数据:', JSON.stringify(orderObj))
- return orderObj
- } catch (e) {
- console.error('[getOrderById] 查询异常:', e)
- return null
- }
- }
-
- // 提交售后申请
- async createRefund(data: any): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) return { success: false, message: '请先登录' }
-
- const d = data as UTSJSONObject
- const orderId = d.getString('order_id')
- const refundType = d.getNumber('refund_type')
- const refundReason = d.getString('refund_reason')
- const refundAmount = d.getNumber('refund_amount')
- const description = d.getString('description')
- const images = d.getArray('images')
-
- const payload = {
- user_id: userId,
- order_id: orderId,
- refund_no: 'REF' + Date.now() + Math.floor(Math.random() * 1000),
- refund_type: refundType,
- refund_reason: refundReason,
- refund_amount: refundAmount,
- description: description ?? '',
- images: images ?? ([] as any[]),
- status: 1 // Pending
- }
-
- const response = await supa
- .from('ml_refunds')
- .insert(payload)
- .execute()
-
- if (response.error != null) {
- console.error('提交售后失败:', response.error)
- return { success: false, message: '提交失败: ' + (response.error.message ?? '未知错误') }
- }
-
- return { success: true, message: '申请提交成功' }
- } catch (e) {
- console.error('提交售后异常:', e)
- return { success: false, message: '系统异常' }
- }
- }
-
- // 再次购买
- async rePurchase(order: any): Promise {
- try {
- // 将 order 转换为 UTSJSONObject 以安全访问属性
- const orderObj = 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 = items[i] as UTSJSONObject
- const productId = item.getString('product_id')
- const skuId = item.getString('sku_id') ?? ''
- const merchantIdFromItem = item.getString('merchant_id') ?? ''
- // 数量可能是数字或字符串
- const quantity = item.getNumber('quantity') ?? 1
-
- if (productId != null) {
- await this.addToCart(productId, quantity, skuId, merchantIdFromItem)
- }
- }
- 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 getUserBalance(): Promise {
- try {
- const userId = this.getCurrentUserId()
- console.log('[Supabase] getUserBalance userId:', userId)
- if (userId == null) return 0
-
- // 优先查 ml_user_wallets
- const walletRes = await supa
- .from('ml_user_wallets')
- .select('balance')
- .eq('user_id', userId!)
- .single()
- .execute()
-
- if (walletRes.error != null) {
- console.error('[Supabase] getUserBalance error:', walletRes.error)
- } else {
- console.log('[Supabase] getUserBalance data:', walletRes.data)
- }
-
- if (walletRes.error == null && walletRes.data != null) {
- let data = walletRes.data
- // 如果是数组,取第一项
- if (Array.isArray(data)) {
- const arr = data as any[]
- if (arr.length > 0) {
- data = arr[0]
- }
- }
-
- let val:number = 0
- if (data instanceof UTSJSONObject) {
- val = data.getNumber('balance') ?? 0
- // 尝试字符串转换,防止精度丢失导致转为string
- if (val === 0 && data.getString('balance') != null) {
- val = parseFloat(data.getString('balance')!)
- }
- return val
- } else {
- // 对于 Map 或 loose object
- const jsonObj = JSON.parse(JSON.stringify(data)) as UTSJSONObject
- val = jsonObj.getNumber('balance') ?? 0
- if (val === 0 && jsonObj.getString('balance') != null) {
- val = parseFloat(jsonObj.getString('balance')!)
- }
- return val
- }
- }
-
- console.log('[Supabase] Wallet table empty, checking profile...')
-
- // Fallback to profile
- const profile = await this.getUserProfile()
- if (profile != null) {
- if (profile instanceof UTSJSONObject) {
- return profile.getNumber('balance') ?? 0
- } else {
- const pObj = JSON.parse(JSON.stringify(profile)) as UTSJSONObject
- return pObj.getNumber('balance') ?? 0
- }
- }
- return 0
- } catch(e) {
- console.error('[Supabase] getUserBalance exception:', e)
- return 0
- }
- }
-
- // 获取用户积分
- async getUserPoints(): Promise {
- try {
- const userId = this.getCurrentUserId()
- console.log('[Supabase] getUserPoints userId:', userId)
- if (userId == null) return 0
-
- // 查 ml_user_points
- const res = await supa
- .from('ml_user_points')
- .select('points')
- .eq('user_id', userId!)
- .single()
- .execute()
-
- if (res.error != null) {
- console.error('[Supabase] getUserPoints error:', res.error)
- } else {
- console.log('[Supabase] getUserPoints data:', res.data)
- }
-
- if (res.error == null && res.data != null) {
- let data = res.data
- // 如果是数组,取第一项
- if (Array.isArray(data)) {
- const arr = data as any[]
- if (arr.length > 0) {
- data = arr[0]
- }
- }
-
- if (data instanceof UTSJSONObject) {
- return data.getNumber('points') ?? 0
- } else {
- // 尝试转为 UTSJSONObject
- const jsonObj = JSON.parse(JSON.stringify(data)) as UTSJSONObject
- const val = jsonObj.getNumber('points')
- if (val != null) return val
-
- return 0
- }
- }
-
- // Fallback check profile if needed
- const profile = await this.getUserProfile()
- if (profile != null) {
- if (profile instanceof UTSJSONObject) {
- return profile.getNumber('points') ?? 0
- } else {
- const pObj = JSON.parse(JSON.stringify(profile)) as UTSJSONObject
- return pObj.getNumber('points') ?? 0
- }
- }
-
- return 0
- } catch (e) {
- console.error('[Supabase] getUserPoints exception:', e)
- return 0
- }
- }
-
- // 获取钱包交易记录
- async getTransactions(page: number = 1, limit: number = 20): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) {
- const empty: any[] = []
- return empty
- }
-
- const from = (page - 1) * limit
- const to = from + limit - 1
-
- const response = await supa
- .from('ml_wallet_transactions')
- .select('*')
- .eq('user_id', userId!)
- .order('created_at', { ascending: false })
- .range(from, to)
- .execute()
-
- if (response.error != null) {
- console.error('获取交易记录失败:', response.error)
- const empty: any[] = []
- return empty
- }
-
- const data = response.data
- if (data == null) {
- const empty: any[] = []
- return empty
- }
-
- return data as any[]
- } catch (e) {
- console.error('获取交易记录异常:', e)
- const empty: any[] = []
- return empty
- }
- }
-
- // 获取积分记录
- async getPointRecords(): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) {
- const empty: any[] = []
- return empty
- }
-
- const res = await supa
- .from('ml_point_records')
- .select('*')
- .eq('user_id', userId!)
- .order('created_at', { ascending: false })
- .execute()
-
- if (res.error != null) {
- const empty: any[] = []
- return empty
- }
- const data = res.data
- if (data == null) {
- const empty: any[] = []
- return empty
- }
- return data as any[]
- } catch (e) {
- const empty: any[] = []
- return empty
- }
- }
-
- // 获取用户红包
- async getUserRedPackets(): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) {
- const empty: any[] = []
- return empty
- }
-
- const res = await supa
- .from('ml_user_red_packets')
- .select('*')
- .eq('user_id', userId!)
- .order('created_at', { ascending: false })
- .execute()
-
- if (res.error != null) {
- console.error('获取红包失败:', res.error)
- const empty: any[] = []
- return empty
- }
- const data = res.data
- if (data == null) {
- const empty: any[] = []
- return empty
- }
- return data as any[]
- } catch (e) {
- console.error('获取红包异常:', e)
- const empty: any[] = []
- return empty
- }
- }
-
- // 获取用户银行卡
- async getUserBankCards(): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) {
- const empty: any[] = []
- return empty
- }
-
- const res = await supa
- .from('ml_user_bank_cards')
- .select('*')
- .eq('user_id', userId!)
- .order('created_at', { ascending: false })
- .execute()
-
- if (res.error != null) {
- console.error('获取银行卡失败:', res.error)
- const empty: any[] = []
- return empty
- }
- const data = res.data
- if (data == null) {
- const empty: any[] = []
- return empty
- }
- return data as any[]
- } catch (e) {
- console.error('获取银行卡异常:', e)
- const empty: any[] = []
- return empty
- }
- }
-
- // 余额充值 (调用 RPC)
- async rechargeBalance(amount: number): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) return false
-
- const res = await supa.rpc('recharge_wallet', {
- p_user_id: userId,
- p_amount: amount
- })
-
- if (res.error != null) {
- console.error('充值失败RPC:', res.error)
- return false
- }
-
- // 简单判断: 如果没有error且data里success为true
- const data = res.data
- if (data instanceof UTSJSONObject) {
- return data.getBoolean('success') ?? false
- }
- // 如果返回不是对象,作为失败处理
- return false
- } catch (e) {
- console.error('充值异常:', e)
- return false
- }
- }
-
- // 余额提现 (调用 RPC)
- async withdrawBalance(amount: number): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) return false
-
- const res = await supa.rpc('withdraw_wallet', {
- p_user_id: userId,
- p_amount: amount
- })
-
- if (res.error != null) {
- console.error('提现失败RPC:', res.error)
- return false
- }
-
- const data = res.data
- if (data instanceof UTSJSONObject) {
- return data.getBoolean('success') ?? false
- }
- return false
- } catch (e) {
- console.error('提现异常:', e)
- return false
- }
- }
-
- // 添加银行卡
- async addBankCard(card: UTSJSONObject): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) return false
-
- // 补全 user_id
- card.set('user_id', userId)
-
- const res = await supa
- .from('ml_user_bank_cards')
- .insert(card)
- .execute()
-
- if (res.error != null) {
- console.error('添加银行卡失败:', res.error)
- return false
- }
- return true
- } catch (e) {
- console.error('添加银行卡异常:', e)
- return false
- }
- }
-
- // 删除银行卡
- async deleteBankCard(cardId: string): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) return false
-
- const res = await supa
- .from('ml_user_bank_cards')
- .eq('id', cardId)
- .eq('user_id', userId!)
- .delete()
- .execute()
-
- if (res.error != null) {
- console.error('删除银行卡失败:', res.error)
- return false
- }
- return true
- } catch (e) {
- console.error('删除银行卡异常:', e)
- return false
- }
- }
-
- // 收藏相关
- async checkFavorite(productId: string): Promise {
- try {
- const userId = this.getCurrentUserId()
- console.log(`[CheckFav] Checking for User: ${userId}, Product: ${productId}`)
-
- if (userId == null) return false
-
- const response = await supa
- .from('ml_user_favorites')
- .select('*') // Select all to verify data
- .eq('user_id', userId!)
- .eq('target_id', productId)
- .eq('target_type', 1) // 1 for product
- .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) {
- // Delete
- const response = await supa
- .from('ml_user_favorites')
- .eq('user_id', userId!)
- .eq('target_id', productId)
- .eq('target_type', 1)
- .delete()
- .execute()
-
- if (response.error != null) {
- console.error('取消收藏失败:', response.error)
- return true // 仍然是收藏状态
- }
- return false // 已取消收藏
- } else {
- // Add
- const response = await supa
- .from('ml_user_favorites')
- .insert({
- user_id: userId,
- target_id: productId,
- target_type: 1,
- created_at: new Date().toISOString()
- })
- .execute()
-
- if (response.error != 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 pid = ''
- if (item instanceof UTSJSONObject) {
- pid = item.getString('target_id') ?? ''
- } else {
- const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
- pid = itemObj.getString('target_id') ?? ''
- }
- 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) {
- // Deep copy to ensure we have a fresh object to modify
- newItem = JSON.parse(JSON.stringify(item)) as UTSJSONObject
- } else {
- newItem = JSON.parse(JSON.stringify(item)) as UTSJSONObject
- }
-
- let targetId = newItem.getString('target_id')
- // Careful with null targetId
- if (targetId != null) {
- const product = productMap.get(targetId as string)
- 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 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)
- .order('expire_at', { ascending: true })
- .execute()
-
- if (response.error != null) {
- console.error('获取优惠券失败:', response.error)
- const empty: UserCoupon[] = []
- return empty
- }
-
- // 映射数据,将 template 的字段展平
- const coupons: UserCoupon[] = []
- const rawData = response.data as any[]
- for (let i = 0; i < rawData.length; i++) {
- const item = rawData[i]
- let template: any | null = null
- let itemId = ''
- let itemUserId = ''
- let itemTmplId = ''
- let itemCode = ''
- let itemStatus = 0
- let itemRecv = ''
- let itemExpire = ''
-
- if (item instanceof UTSJSONObject) {
- template = item.get('template') as any | null
- itemId = item.getString('id') ?? ''
- itemUserId = item.getString('user_id') ?? ''
- itemTmplId = item.getString('template_id') ?? ''
- itemCode = item.getString('coupon_code') ?? ''
- itemStatus = item.getNumber('status') ?? 0
- itemRecv = item.getString('received_at') ?? ''
- itemExpire = item.getString('expire_at') ?? ''
- } else {
- const iObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
- template = iObj.get('template') as any | null
- itemId = iObj.getString('id') ?? ''
- itemUserId = iObj.getString('user_id') ?? ''
- itemTmplId = iObj.getString('template_id') ?? ''
- itemCode = iObj.getString('coupon_code') ?? ''
- itemStatus = iObj.getNumber('status') ?? 0
- itemRecv = iObj.getString('received_at') ?? ''
- itemExpire = iObj.getString('expire_at') ?? ''
- }
-
- if (template == null) template = new UTSJSONObject()
-
- let tName = ''
- let tAmount = 0
- let tMin = 0
-
- if (template instanceof UTSJSONObject) {
- tName = template.getString('name') ?? '优惠券'
- tAmount = template.getNumber('amount') ?? 0
- tMin = template.getNumber('min_spend') ?? 0
- } else {
- const tObj = JSON.parse(JSON.stringify(template)) as UTSJSONObject
- tName = tObj.getString('name') ?? '优惠券'
- tAmount = tObj.getNumber('amount') ?? 0
- tMin = tObj.getNumber('min_spend') ?? 0
- }
-
- const couponObj = new UTSJSONObject()
- couponObj.set('id', itemId)
- couponObj.set('user_id', itemUserId)
- couponObj.set('template_id', itemTmplId)
- couponObj.set('coupon_code', itemCode)
- couponObj.set('status', itemStatus)
- couponObj.set('received_at', itemRecv)
- couponObj.set('expire_at', itemExpire)
- couponObj.set('template_name', tName)
- couponObj.set('amount', tAmount)
- couponObj.set('min_spend', tMin)
-
- coupons.push(couponObj as UserCoupon)
- }
-
- return coupons
- } catch (e) {
- console.error('获取优惠券异常:', e)
- const empty: UserCoupon[] = []
- return empty
- }
- }
-
- // 获取可用优惠券数量
- async getUserCouponCount(): Promise {
- try {
- const userId = this.getCurrentUserId()
- if (userId == null) return 0
-
- const response = await supa
- .from('ml_user_coupons')
- .select('id', { count: 'exact' })
- .eq('user_id', userId!)
- .eq('status', 1) // 1: unused
- .gt('expire_at', new Date().toISOString()) // 未过期
- .limit(1) // Limit to 1 to reduce data transfer, we only want the count
- .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 {
- // 查询该商家的优惠券 + 平台通用券 (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)
- .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
- }
- return data as any[]
- } catch (e) {
- console.error('Fetch coupons error:', e)
- const empty: any[] = []
- return empty
- }
- }
-
- // 领取优惠券
- async claimCoupon(templateId: string, userId: string): Promise {
- return this.claimShopCoupon(templateId, userId)
- }
-
- // ALIAS for Cache busting
- async claimShopCoupon(templateId: string, userId: string): Promise {
- try {
- console.log('Claiming coupon templateId:', templateId, 'userId:', userId)
-
- // 1. Fetch template details to get merchant_id and validity
- const tmplRes = await supa
- .from('ml_coupon_templates')
- .select('*')
- .eq('id', templateId)
- .limit(1)
- .execute()
-
- if (tmplRes.error != null) {
- console.error('Claim Coupon: Template query error', tmplRes.error)
- return false
- }
-
- // Null check for data
- if (tmplRes.data == null) {
- console.error('Claim Coupon: Template data response is null')
- return false
- }
-
- const dataList = tmplRes.data as any[]
- if (dataList.length === 0) {
- console.error('Claim Coupon: Template not found (empty list)')
- return false
- }
-
- const template = dataList[0]
-
- // Safe property access
- let validDays = 0
- let endTimeStr: string | null = null
- let merchantId: string | null = null
-
- if (template instanceof UTSJSONObject) {
- validDays = template.getNumber('valid_days') ?? 0
- endTimeStr = template.getString('end_time')
- merchantId = template.getString('merchant_id')
- } else {
- const tJson = JSON.parse(JSON.stringify(template)) as UTSJSONObject
- validDays = tJson.getNumber('valid_days') ?? 0
- endTimeStr = tJson.getString('end_time')
- merchantId = tJson.getString('merchant_id')
- }
-
- // Calculate expire_at
- let expireAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()
- if (validDays > 0) {
- expireAt = new Date(Date.now() + (validDays * 24 * 60 * 60 * 1000)).toISOString()
- } else if (endTimeStr != null && endTimeStr !== '') {
- expireAt = endTimeStr
- }
-
- // Handle UUID fields: Empty string is not valid UUID, must be null
- if (merchantId != null && merchantId.length === 0) {
- merchantId = null
- }
-
- // 2. Insert into user coupons with merchant_id
- const insertData = {
- user_id: userId,
- template_id: templateId,
- merchant_id: merchantId, // Important for shop filtering: null for platform coupons
- coupon_code: 'C' + Date.now() + Math.floor(Math.random() * 1000),
- status: 1,
- expire_at: expireAt,
- received_at: new Date().toISOString()
- }
-
- console.log('Claim Coupon Insert Payload:', JSON.stringify(insertData))
-
- const response = await supa
- .from('ml_user_coupons')
- .insert(insertData)
- .execute()
-
- if (response.error != null) {
- console.error('Claim Coupon: Insert failed:', JSON.stringify(response.error))
- // 尝试降级:如果 merchant_id 报错,尝试不带 merchant_id (仅调试用,或兼容旧表结构)
- if (JSON.stringify(response.error).includes('merchant_id')) {
- console.log('Retrying without merchant_id...')
- const fallbackData = {
- user_id: userId,
- template_id: templateId,
- coupon_code: 'C' + Date.now() + Math.random().toString().substring(2,6),
- status: 1,
- expire_at: expireAt,
- received_at: new Date().toISOString()
- }
- const res2 = await supa.from('ml_user_coupons').insert(fallbackData).execute()
- if (res2.error == null) return true
- }
- return false
- }
- return true
- } catch(e) {
- console.error('Claim coupon error:', e)
- return false
- }
- }
-
- // ==========================================
- // 聊天相关方法
- // ==========================================
-
- // 获取特定会话的消息历史
- async getChatMessages(merchantId: string, page: number = 1, pageSize: number = 20): Promise {
- const userId = this.getCurrentUserId()
- if (userId == null) {
- const empty: ChatMessage[] = []
- return empty
- }
-
- // 计算分页 range
- const fromIndex = (page - 1) * pageSize
- const toIndex = fromIndex + pageSize - 1
-
- try {
- // 使用 or 组合条件查询:(sender_id=me AND receiver_id=merchant) OR (sender_id=merchant AND receiver_id=me)
- // 注意:Supabase postgrest-js 的 .or() 语法如果是针对同一列很简单,针对复杂逻辑用 string syntax
- // 这里简化处理,如果不加 userId 过滤,全靠 RLS
- const response = await supa
- .from('ml_chat_messages')
- .select('*')
- .or(`sender_id.eq.${merchantId},receiver_id.eq.${merchantId}`)
- .order('created_at', { ascending: false })
- .range(fromIndex, toIndex)
- .execute()
-
- if (response.error != null) {
- console.error('getChatMessages error:', response.error)
- const empty: ChatMessage[] = []
- return empty
- }
-
- const data = response.data
- if (data == null) {
- const empty: ChatMessage[] = []
- return empty
- }
-
- return data as ChatMessage[]
- } catch (e) {
- console.error('getChatMessages exception:', e)
- const empty: ChatMessage[] = []
- return empty
- }
- }
-
- // 发送消息
- async sendMessage(merchantId: string, content: string, msgType: string = 'text'): Promise {
- // 确保 session 有效
- const userId = this.getCurrentUserId()
- if (userId == null) {
- console.error("sendMessage failed: user not logged in or session lost")
- return false
- }
-
- try {
- // Debug check
- // const session = supa.getSession()
- // console.log("Sending check: UserID", userId, "AuthID:", session.user?.getString('id'))
-
- const msg = {
- sender_id: userId!,
- receiver_id: merchantId,
- content: content,
- msg_type: msgType,
- is_read: false,
- is_from_user: true
- }
-
- const response = await supa
- .from('ml_chat_messages')
- .insert(msg)
- .execute()
-
- if (response.error != null) {
- console.error('sendMessage error:', response.error)
- return false
- }
- return true
- } catch (e) {
- console.error('sendMessage exception:', e)
- return false
- }
- }
-
- // 标记会话已读
- async markRead(merchantId: string): Promise {
- const userId = this.getCurrentUserId()
- if (userId == null) return false
- try {
- const response = await supa
- .from('ml_chat_messages')
- .update({ is_read: true })
- .eq('sender_id', merchantId)
- .eq('receiver_id', userId)
- .eq('is_read', false)
- .execute()
-
- if (response.error != null) return false
- } catch (e) { return false }
- return true
- }
-}
-
-// 导出单例实例
-export const supabaseService = new SupabaseService()
-
-// 默认导出
-export default supabaseService
diff --git a/pages/mall/consumer/cart.uvue b/pages/mall/consumer/cart.uvue
index e3cbcc2c..17f4e715 100644
--- a/pages/mall/consumer/cart.uvue
+++ b/pages/mall/consumer/cart.uvue
@@ -200,6 +200,7 @@ type RecommendProduct = {
price: number
image: string
skuId: string
+ merchant_id: string
}
// 响应式数据
@@ -326,9 +327,10 @@ const loadCartData = async () => {
shopId: p.merchant_id ?? 'unknown',
shopName: p.shop_name ?? '商城推荐',
name: p.name,
- price: p.base_price ?? p.price ?? 0,
- image: p.main_image_url ?? '/static/images/default-product.png',
- skuId: ''
+ price: p.base_price ?? p.market_price ?? 0,
+ image: p.main_image_url ?? p.image_url ?? '/static/images/default-product.png',
+ skuId: '',
+ merchant_id: p.merchant_id ?? ''
}
})
} else {
@@ -558,31 +560,52 @@ const deleteSelectedItems = async () => {
})
}
-const addToCart = async (product: any) => {
+const addToCart = async (product: RecommendProduct) => {
+ uni.showLoading({ title: '检查商品...' })
try {
- // 调用SupabaseService添加商品到购物车
- // 显式访问属性,避免any类型导致的编译错误
- const target = product as UTSJSONObject
- const productId = target.getString('id') ?? ''
- const skuId = target.getString('skuId') ?? ''
- const success = await supabaseService.addToCart(productId, 1, skuId)
- if (success) {
+ const productId = product.id
+ const skuId = product.skuId
+ const merchantId = product.merchant_id
+
+ // 检查商品是否有SKU
+ const skus = await supabaseService.getProductSkus(productId)
+ uni.hideLoading()
+
+ if (skus.length > 0) {
+ // 有规格,提示并跳转到商品详情页选择规格
uni.showToast({
- title: '已添加到购物车',
- icon: 'success'
- })
-
- // 重新加载购物车数据
- loadCartData()
- } else {
- console.error('添加商品到购物车失败')
- uni.showToast({
- title: '添加失败',
+ title: '请选择规格',
icon: 'none'
})
+ setTimeout(() => {
+ uni.navigateTo({
+ url: '/pages/mall/consumer/product-detail?id=' + productId
+ })
+ }, 500)
+ } else {
+ // 无规格,直接加入购物车
+ uni.showLoading({ title: '添加中...' })
+ const success = await supabaseService.addToCart(productId, 1, skuId, merchantId)
+ uni.hideLoading()
+ if (success) {
+ uni.showToast({
+ title: '已添加到购物车',
+ icon: 'success'
+ })
+
+ // 重新加载购物车数据
+ loadCartData()
+ } else {
+ console.error('添加商品到购物车失败')
+ uni.showToast({
+ title: '添加失败',
+ icon: 'none'
+ })
+ }
}
} catch (error) {
console.error('添加商品到购物车异常:', error)
+ uni.hideLoading()
uni.showToast({
title: '添加失败',
icon: 'none'
diff --git a/pages/mall/consumer/category.uvue b/pages/mall/consumer/category.uvue
index 027b6f27..c03ab4a1 100644
--- a/pages/mall/consumer/category.uvue
+++ b/pages/mall/consumer/category.uvue
@@ -65,30 +65,16 @@
class="product-card"
@click="navigateToProduct(product)"
>
- 热销
-
- {{ product.name }}
-
-
-
- ¥
- {{ product.base_price ?? product.price ?? 0 }}
-
-
- ¥{{ product.market_price }}
-
-
-
-
- {{ product.brand_name ?? product.shop_name ?? '自营' }}
-
- 已售{{ product.sale_count }}
-
+ {{ product.name }}
+
+ ¥{{ product.base_price ?? product.price ?? 0 }}
+
+ +
@@ -464,32 +450,53 @@ onShow(() => {
// 添加到购物车
async function addToCart(product: Product): Promise {
- uni.showLoading({ title: '添加中...' })
+ uni.showLoading({ title: '检查商品...' })
try {
const pid = (product.id ?? '').toString()
+ const merchantId = product.merchant_id ?? ''
if (pid === '') {
uni.hideLoading()
uni.showToast({ title: '商品无效', icon: 'none' })
return
}
- const success = await supabaseService.addToCart(pid, 1, '')
- if (success) {
+
+ // 检查商品是否有SKU
+ const skus = await supabaseService.getProductSkus(pid)
+ uni.hideLoading()
+
+ if (skus.length > 0) {
+ // 有规格,提示并跳转到商品详情页选择规格
uni.showToast({
- title: '已添加到购物车',
- icon: 'success'
- })
- cartCount.value++
- } else {
- uni.showToast({
- title: '添加失败,请先登录',
+ title: '请选择规格',
icon: 'none'
})
+ setTimeout(() => {
+ uni.navigateTo({
+ url: '/pages/mall/consumer/product-detail?id=' + pid
+ })
+ }, 500)
+ } else {
+ // 无规格,直接加入购物车
+ uni.showLoading({ title: '添加中...' })
+ const success = await supabaseService.addToCart(pid, 1, '', merchantId)
+ uni.hideLoading()
+ if (success) {
+ uni.showToast({
+ title: '已添加到购物车',
+ icon: 'success'
+ })
+ cartCount.value++
+ } else {
+ uni.showToast({
+ title: '添加失败,请先登录',
+ icon: 'none'
+ })
+ }
}
} catch (e) {
console.error('添加到购物车异常', e)
- uni.showToast({ title: '操作失败', icon: 'none' })
- } finally {
uni.hideLoading()
+ uni.showToast({ title: '操作失败', icon: 'none' })
}
}
@@ -794,111 +801,70 @@ function onScan(): void {
}
.product-card {
- background: white;
- border-radius: 12px;
+ display: flex;
+ flex-direction: column;
+ background: #fff;
+ border-radius: 8px;
overflow: hidden;
- /* cursor: pointer; removed for uniapp-x support */
- transition: all 0.3s ease;
- border: 1px solid #e0e0e0;
- position: relative;
- /* margin: 10px; gap replacement - moved to logic */
- width: 44%; /* Decreased to 44% to ensure it fits (44 + 3 + 3 = 50%) */
- margin: 3%; /* Increased margin */
- box-sizing: border-box; /* Ensure border IS included in width */
-}
-
-.product-card:hover {
- transform: translateY(-4px);
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
-}
-
-.product-badge {
- position: absolute;
- top: 12px;
- left: 12px;
- background: #FF5722;
- color: white;
- font-size: 11px;
- padding: 4px 12px;
- border-radius: 12px;
- font-weight: 700;
- z-index: 2;
+ width: 48%;
+ margin-bottom: 12px;
}
.product-image {
width: 100%;
- height: 160px;
- /* object-fit: cover; REMOVED for uniapp-x support - default behavior is often acceptable or handle via image mode */
- background: white;
-}
-
-.product-info {
- padding: 16px;
+ height: 170px;
+ border-radius: 8px;
+ margin-bottom: 8px;
+ background: #f5f5f5;
}
.product-name {
- font-size: 15px;
- font-weight: 700;
+ font-size: 13px;
color: #333;
- margin-bottom: 4px;
- /* display: block; REMOVED for uniapp-x support */
+ margin-bottom: 5px;
line-height: 1.4;
+ height: 36px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ padding: 0 8px;
}
-.product-spec {
- font-size: 13px;
- color: #666;
- margin-bottom: 12px;
- /* display: block; REMOVED for uniapp-x support */
-}
-
-.price-section {
+.product-bottom {
display: flex;
flex-direction: row;
- align-items: flex-end; /* changed from baseline */
- /* gap: 8px; */
- margin-bottom: 12px;
-}
-
-.current-price {
- display: flex;
- flex-direction: row;
- align-items: flex-end; /* changed from baseline */
- margin-right: 8px; /* gap replacement */
-}
-
-.price-symbol {
- font-size: 14px;
- color: #FF5722;
-}
-
-.price-value {
- font-size: 20px;
- font-weight: bold;
- color: #FF5722;
- margin-left: 2px;
-}
-
-.original-price {
- font-size: 13px;
- color: #999;
- /* text-decoration: line-through; REMOVED for uniapp-x support */
-}
-
-.product-meta {
- display: flex;
justify-content: space-between;
align-items: center;
- font-size: 12px;
- margin-bottom: 12px;
+ padding: 0 8px 8px;
}
-.manufacturer {
- color: #666;
+.product-price {
+ font-size: 15px;
+ color: #ff5000;
+ font-weight: bold;
}
-.sales-count {
- color: #999;
+.product-add-btn {
+ width: 24px;
+ height: 24px;
+ background-color: #ff5000;
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.add-icon {
+ color: #fff;
+ font-size: 16px;
+ font-weight: bold;
+}
+
+.product-grid {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ padding: 8px;
}
.product-action {
diff --git a/pages/mall/consumer/checkout.uvue b/pages/mall/consumer/checkout.uvue
index ff620735..2b021b62 100644
--- a/pages/mall/consumer/checkout.uvue
+++ b/pages/mall/consumer/checkout.uvue
@@ -1309,6 +1309,12 @@ const submitOrder = async () => {
const groups: any[] = []
for (let i = 0; i < shopGroups.value.length; i++) {
const group = shopGroups.value[i]
+ console.log(`[submitOrder] 处理店铺组 ${i}:`, {
+ shopId: group.shopId,
+ shopName: group.shopName,
+ merchant_id: group.merchant_id,
+ itemsCount: group.items.length
+ })
const items: any[] = []
for (let j = 0; j < group.items.length; j++) {
const item = group.items[j]
@@ -1323,8 +1329,10 @@ const submitOrder = async () => {
specifications: item.sku_specifications
})
}
+ const finalMerchantId = (group.merchant_id != null && group.merchant_id != '') ? group.merchant_id : group.shopId
+ console.log(`[submitOrder] 店铺组 ${i} 最终使用的 merchant_id:`, finalMerchantId)
groups.push({
- merchant_id: (group.merchant_id != null && group.merchant_id != '') ? group.merchant_id : group.shopId,
+ merchant_id: finalMerchantId,
shopId: group.shopId,
shopName: group.shopName,
items: items
diff --git a/pages/mall/consumer/doc/uts.txt b/pages/mall/consumer/doc/uts.txt
index 291b535e..15520835 100644
--- a/pages/mall/consumer/doc/uts.txt
+++ b/pages/mall/consumer/doc/uts.txt
@@ -1394,3 +1394,598 @@ getCurrentUserId 函数 - 将可选链替换为显式 null 检查和 UTSJSONObje
================================================================================
文档结束
================================================================================
+
+================================================================================
+二十四、2026-02-27 函数可选参数限制(重要)
+================================================================================
+
+1. 可选参数不能跳过传递
+ - UTS Android 不支持跳过可选参数传递
+ - 如果函数有多个可选参数,必须按顺序传递所有参数
+ - 错误示例:
+ ```typescript
+ // 函数定义
+ async addToCart(productId: string, quantity: number = 1, skuId?: string, merchantId?: string): Promise
+
+ // 错误调用 - 跳过了 merchantId 参数
+ await supabaseService.addToCart(productId, 1, '')
+ // 编译错误:No value passed for parameter 'merchantId'
+ ```
+ - 正确示例:
+ ```typescript
+ // 方案1:给可选参数添加默认值
+ async addToCart(productId: string, quantity: number = 1, skuId: string = '', merchantId: string = ''): Promise
+
+ // 方案2:调用时传递所有参数
+ await supabaseService.addToCart(productId, 1, '', '')
+ ```
+
+2. 可选参数定义规范
+ - 推荐使用 `param: Type = defaultValue` 而非 `param?: Type`
+ - `param?: Type` 在 Android 端调用时仍需传递参数
+ - `param: Type = defaultValue` 可以在不传参时使用默认值
+ - 示例:
+ ```typescript
+ // 不推荐 - 调用时仍需传递参数
+ function foo(a: string, b?: string, c?: string): void
+
+ // 推荐 - 可以跳过参数使用默认值
+ function foo(a: string, b: string = '', c: string = ''): void
+ ```
+
+3. 编译错误提示
+ - 错误信息:"No value passed for parameter 'xxx'"
+ - 原因:可选参数在 Android 端不能跳过
+ - 解决:
+ 1. 修改函数签名,使用默认值 `param: Type = defaultValue`
+ 2. 调用时传递所有参数
+
+4. 最佳实践
+ - 对于有多个可选参数的函数,统一使用默认值语法
+ - 调用时显式传递所有参数,避免依赖可选参数跳过
+ - 在服务层函数定义中,优先使用 `= ''` 或 `= 0` 等默认值
+
+================================================================================
+二十五、2026-02-27 模板中的非空断言限制(重要)
+================================================================================
+
+1. 模板中不支持非空断言操作符 `!`
+ - UTS Android 模板中不能使用 `variable!` 非空断言
+ - 错误示例:
+ ```html
+
+ ```
+ - 正确示例:
+ ```html
+
+ ```
+
+2. 编译错误提示
+ - 错误信息:"参数类型不匹配:实际类型为 'Number?',预期类型为 'Number'"
+ - 原因:模板中使用非空断言 `!` 不被支持
+ - 解决:移除非空断言 `!`,直接使用变量进行比较
+
+3. 最佳实践
+ - 在模板中,先用 `!= null` 判断可空类型,然后直接使用变量
+ - UTS 编译器会在 `!= null` 判断后自动识别变量为非空类型
+
+================================================================================
+二十六、2026-02-27 未导入类型的处理(重要)
+================================================================================
+
+1. 未导入的类型不能直接使用
+ - 在页面中使用的类型必须先导入或使用 UTSJSONObject 替代
+ - 错误示例:
+ ```typescript
+ // Shop 类型未导入
+ const s = shopRespData[i] as Shop
+ const id = s.id // 找不到名称 "id"
+ ```
+ - 正确示例:
+ ```typescript
+ // 使用 UTSJSONObject
+ const s = shopRespData[i] as UTSJSONObject
+ const id = s.getString('id') ?? ''
+ const name = s.getString('shop_name') ?? ''
+ ```
+
+2. 编译错误提示
+ - 错误信息:"找不到名称 'XXX'"
+ - 原因:类型未导入或类型定义不存在
+ - 解决:
+ 1. 导入需要的类型:`import { Shop } from '@/utils/supabaseService.uts'`
+ 2. 使用 UTSJSONObject 替代:`as UTSJSONObject` 然后用 `getString()`、`getNumber()` 访问属性
+
+3. 最佳实践
+ - 对于简单的数据转换,推荐使用 UTSJSONObject
+ - 避免在多个文件中重复定义相同的类型
+ - 如果需要类型安全,从服务层导入类型定义
+
+================================================================================
+二十七、2026-02-27 服务层数据字段完整性(重要)
+================================================================================
+
+1. 服务层返回数据必须包含所有必要字段
+ - 从数据库获取数据时,必须正确映射所有需要的字段
+ - 错误示例:
+ ```typescript
+ const product: Product = {
+ id: prodObj.getString('id') ?? '',
+ name: prodObj.getString('name') ?? '',
+ // 错误:merchant_id 硬编码为空字符串
+ merchant_id: ''
+ } as Product
+ ```
+ - 正确示例:
+ ```typescript
+ const product: Product = {
+ id: prodObj.getString('id') ?? '',
+ name: prodObj.getString('name') ?? '',
+ // 正确:从数据库获取 merchant_id
+ merchant_id: prodObj.getString('merchant_id') ?? ''
+ } as Product
+ ```
+
+2. 调用服务层方法时必须传递完整参数
+ - 页面调用服务层方法时,需要传递所有必要参数
+ - 错误示例:
+ ```typescript
+ // 错误:merchant_id 传空字符串
+ await supabaseService.addToCart(productId, 1, '', '')
+ ```
+ - 正确示例:
+ ```typescript
+ // 正确:从商品对象获取 merchant_id
+ const merchantId = product.merchant_id ?? ''
+ await supabaseService.addToCart(productId, 1, '', merchantId)
+ ```
+
+3. 编译错误提示
+ - 问题表现:数据添加到数据库失败,或添加的数据不完整
+ - 原因:服务层或页面层缺少必要字段的传递
+ - 解决:
+ 1. 检查服务层数据映射是否完整
+ 2. 检查页面调用时是否传递了所有必要参数
+
+4. 最佳实践
+ - 服务层方法返回的对象应包含数据库视图的所有字段
+ - 页面调用服务层方法时,应从数据对象中获取并传递所有参数
+ - 对于关联数据(如 merchant_id),确保在数据加载时一并获取
+
+================================================================================
+二十八、2026-02-27 模板中的非运算符限制(重要)
+================================================================================
+
+1. 模板中不支持 `!` 非运算符
+ - UTS Android 模板中不能使用 `!variable` 非运算符
+ - 错误示例:
+ ```html
+
+ ```
+ - 正确示例:
+ ```html
+
+ ```
+
+2. 编译错误提示
+ - 错误信息:"找不到名称'not'"
+ - 原因:模板中不支持非运算符 `!`
+ - 解决:使用显式的比较表达式替代
+
+3. 最佳实践
+ - 使用 `== null` 或 `== ''` 检查空值
+ - 使用 `!= null && != ''` 检查非空值
+
+================================================================================
+二十九、2026-02-27 索引访问限制(重要)
+================================================================================
+
+1. 不支持 `(obj as any)['key']` 索引访问方式
+ - UTS Android 不支持对 any 类型使用索引访问
+ - 错误示例:
+ ```typescript
+ const detail = (e as any)['detail']
+ val = detail['value'] ?? ''
+ ```
+ - 正确示例:
+ ```typescript
+ // 方案1:使用 UTSJSONObject
+ const eObj = JSON.parse(JSON.stringify(e)) as UTSJSONObject
+ const detail = eObj.get('detail') as UTSJSONObject
+ val = detail.getString('value') ?? ''
+
+ // 方案2:先判断类型再转换
+ if (e instanceof UTSJSONObject) {
+ const eObj = e as UTSJSONObject
+ const detail = eObj.get('detail') as UTSJSONObject
+ val = detail.getString('value') ?? ''
+ }
+ ```
+
+2. 编译错误提示
+ - 错误信息:"Unresolved reference. None of the following candidates is applicable because of a receiver type mismatch"
+ - 原因:any 类型不支持索引访问
+ - 解决:转换为 UTSJSONObject 后使用 `.get()` 方法
+
+3. 最佳实践
+ - 统一使用 UTSJSONObject 处理动态对象
+ - 使用 `.get()`、`.getString()`、`.getNumber()` 方法访问属性
+ - 对于复杂对象,先用 `JSON.parse(JSON.stringify(obj))` 转换
+
+================================================================================
+三十、2026-02-27 字符串不能直接作为布尔条件(重要)
+================================================================================
+
+1. 字符串不能直接作为 if 条件
+ - UTS Android 不支持将字符串直接作为布尔条件判断
+ - 错误示例:
+ ```typescript
+ const paramId = '123'
+ if (paramId) { // 错误:字符串不能直接作为布尔条件
+ // ...
+ }
+ ```
+ - 正确示例:
+ ```typescript
+ const paramId = '123'
+ if (paramId != null && paramId != '') { // 正确:显式判断
+ // ...
+ }
+ ```
+
+2. 编译错误提示
+ - 错误信息:"Condition type mismatch: inferred type is 'String' but 'Boolean' was expected"
+ - 原因:字符串类型不能直接作为布尔条件
+ - 解决:使用显式的比较表达式
+
+3. 最佳实践
+ - 使用 `!= null && != ''` 检查字符串非空
+ - 使用 `== null || == ''` 检查字符串为空
+
+================================================================================
+三十一、2026-02-27 函数定义顺序(重要)
+================================================================================
+
+1. 函数必须在调用前定义
+ - UTS Android 要求函数在调用之前完成定义
+ - 这与 JavaScript 的函数提升不同
+ - 错误示例:
+ ```typescript
+ onMounted(() => {
+ loadData() // 错误:loadData 还未定义
+ })
+
+ const loadData = async () => {
+ // ...
+ }
+ ```
+ - 正确示例:
+ ```typescript
+ const loadData = async () => {
+ // ...
+ }
+
+ onMounted(() => {
+ loadData() // 正确:loadData 已定义
+ })
+ ```
+
+2. 编译错误提示
+ - 错误信息:"找不到名称'xxx'"
+ - 原因:函数在调用点之后定义
+ - 解决:将函数定义移到调用之前
+
+3. 最佳实践
+ - 将所有函数定义放在生命周期钩子(onMounted、onShow 等)之前
+ - 按依赖关系排序函数定义顺序
+
+================================================================================
+三十二、2026-02-27 联合类型属性访问(重要)
+================================================================================
+
+1. 联合类型不能直接访问属性
+ - 当参数类型为联合类型(如 `A | B`)时,不能直接访问属性
+ - 错误示例:
+ ```typescript
+ type A = { id: string, name: string }
+ type B = { id: string, title: string }
+
+ const foo = (item: A | B) => {
+ const id = item.id // 错误:联合类型不能直接访问属性
+ }
+ ```
+ - 正确示例:
+ ```typescript
+ const foo = (item: A | B) => {
+ // 方案1:转换为 UTSJSONObject
+ const obj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
+ const id = obj.getString('id') ?? ''
+
+ // 方案2:使用类型守卫
+ if ('name' in item) {
+ const id = item.id // 此时类型已收窄为 A
+ }
+ }
+ ```
+
+2. 编译错误提示
+ - 错误信息:"找不到名称'xxx'"
+ - 原因:联合类型的属性访问受限
+ - 解决:转换为 UTSJSONObject 或使用类型守卫
+
+3. 最佳实践
+ - 对于联合类型参数,统一转换为 UTSJSONObject 处理
+ - 使用 `.getString()`、`.getNumber()` 等方法安全访问属性
+
+================================================================================
+三十三、2026-02-27 any 类型变量不能赋值为 null(重要)
+================================================================================
+
+1. any 类型变量不能赋值为 null
+ - UTS Android 中 `any` 类型不能赋值为 `null`
+ - 错误示例:
+ ```typescript
+ let res: any = null // 错误:Null cannot be a value of a non-null type 'Any'
+ ```
+ - 正确示例:
+ ```typescript
+ let res: any = {} // 正确:使用空对象
+ // 或者
+ let res: any | null = null // 使用联合类型
+ ```
+
+2. 编译错误提示
+ - 错误信息:"Null cannot be a value of a non-null type 'Any'"
+ - 原因:any 类型不允许 null 值
+ - 解决:使用空对象 `{}` 或联合类型 `any | null`
+
+================================================================================
+三十四、2026-02-27 对象字面量类型推断问题(重要)
+================================================================================
+
+1. 对象字面量直接赋值给 ref 可能类型不匹配
+ - 当对象字面量直接赋值给特定类型的 ref 时,可能报类型不匹配错误
+ - 错误示例:
+ ```typescript
+ merchant.value = {
+ id: shop.id,
+ user_id: shop.merchant_id,
+ // ...
+ } // 错误:Assignment type mismatch
+ ```
+ - 正确示例:
+ ```typescript
+ // 方案1:显式声明类型
+ const merchantData: MerchantType = {
+ id: shop.id,
+ user_id: shop.merchant_id,
+ // ...
+ }
+ merchant.value = merchantData
+
+ // 方案2:使用 as 类型断言
+ merchant.value = {
+ id: shop.id,
+ user_id: shop.merchant_id,
+ // ...
+ } as MerchantType
+ ```
+
+2. 编译错误提示
+ - 错误信息:"Assignment type mismatch: actual type is '', but 'XXX' was expected"
+ - 原因:对象字面量被推断为匿名类型
+ - 解决:显式声明类型或使用类型断言
+
+================================================================================
+三十五、2026-02-27 any 类型不能直接访问属性(重要)
+================================================================================
+
+1. any 类型参数不能直接访问属性
+ - 在 map、forEach 等回调中,any 类型的参数不能直接访问属性
+ - 错误示例:
+ ```typescript
+ const list = rawList.map((item): ProductType => {
+ const id = item.id // 错误:找不到名称"id"
+ const name = item.name // 错误:找不到名称"name"
+ })
+ ```
+ - 正确示例:
+ ```typescript
+ const list = rawList.map((item: any): ProductType => {
+ // 方案1:转换为 UTSJSONObject
+ const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
+ const id = itemObj.getString('id') ?? ''
+ const name = itemObj.getString('name') ?? ''
+
+ // 方案2:显式标注参数类型并使用索引
+ // 注意:这种方式在 UTS Android 中也可能有问题
+ })
+ ```
+
+2. 编译错误提示
+ - 错误信息:"找不到名称'xxx'"
+ - 原因:any 类型的属性访问受限
+ - 解决:转换为 UTSJSONObject 后使用 `.getString()` 等方法
+
+================================================================================
+三十六、2026-02-27 类型断言不会添加方法(重要)
+================================================================================
+
+1. `as UTSJSONObject` 不会给对象添加方法
+ - 使用 `as UTSJSONObject` 只是类型断言,不会让普通对象获得 `getString` 等方法
+ - 错误示例:
+ ```typescript
+ const profileObj = profile as UTSJSONObject
+ const id = profileObj.getString('user_id') // 运行时错误:getString is not a function
+ ```
+ - 正确示例:
+ ```typescript
+ // 必须使用 JSON.parse(JSON.stringify()) 进行真正的转换
+ const profileObj = JSON.parse(JSON.stringify(profile)) as UTSJSONObject
+ const id = profileObj.getString('user_id') ?? ''
+ ```
+
+2. 运行时错误提示
+ - 错误信息:"XXX is not a function"
+ - 原因:类型断言只是编译时行为,不会改变运行时对象的方法
+ - 解决:使用 `JSON.parse(JSON.stringify())` 进行真正的对象转换
+
+3. 最佳实践
+ - 对于从 API 返回的数据,统一使用 `JSON.parse(JSON.stringify())` 转换
+ - 使用 `instanceof UTSJSONObject` 检查对象类型
+ - 不要依赖 `as` 类型断言来添加方法
+
+================================================================================
+三十七、2026-02-27 类型必须包含所有必填字段(重要)
+================================================================================
+
+1. 创建类型实例时必须包含所有必填字段
+ - UTS 类型定义中的非可选字段(不带 `?`)都是必填的
+ - 错误示例:
+ ```typescript
+ export type ProductType = {
+ id: string
+ merchant_id: string // 必填
+ category_id: string // 必填
+ name: string
+ // ...
+ }
+
+ // 错误:缺少 merchant_id、category_id 等必填字段
+ return {
+ id: item.id,
+ name: item.name,
+ price: item.price
+ } as ProductType // 运行时错误:missing required property
+ ```
+ - 正确示例:
+ ```typescript
+ return {
+ id: itemObj.getString('id') ?? '',
+ merchant_id: itemObj.getString('merchant_id') ?? '',
+ category_id: itemObj.getString('category_id') ?? '',
+ name: itemObj.getString('name') ?? '未知商品',
+ description: itemObj.getString('description') ?? '',
+ images: images,
+ price: itemObj.getNumber('base_price') ?? 0,
+ original_price: itemObj.getNumber('market_price') ?? 0,
+ stock: itemObj.getNumber('total_stock') ?? 0,
+ sales: itemObj.getNumber('sale_count') ?? 0,
+ status: 1,
+ created_at: itemObj.getString('created_at') ?? ''
+ } as ProductType
+ ```
+
+2. 运行时错误提示
+ - 错误信息:"Failed to construct type, missing required property: xxx"
+ - 原因:类型定义中有必填字段未提供
+ - 解决:
+ 1. 检查类型定义,确认所有必填字段
+ 2. 为所有必填字段提供值,即使是空字符串或默认值
+
+3. 最佳实践
+ - 查看类型定义,确认哪些字段是必填的(不带 `?`)
+ - 使用 `??` 运算符提供默认值
+ - 对于可选字段,可以不提供或使用 `null`
+
+================================================================================
+三十八、2026-02-27 回调函数不能是 async(重要)
+================================================================================
+
+1. API 回调函数不能使用 async 修饰
+ - uni API 的回调函数(如 showModal 的 success)不支持 async 函数
+ - 错误示例:
+ ```typescript
+ uni.showModal({
+ title: '确认',
+ content: '确定要删除吗?',
+ success: async (res) => { // 错误:回调函数不能是 async
+ if (res.confirm) {
+ const result = await someAsyncFunction()
+ }
+ }
+ })
+ ```
+ - 正确示例:
+ ```typescript
+ uni.showModal({
+ title: '确认',
+ content: '确定要删除吗?',
+ success: (res) => {
+ if (res.confirm) {
+ // 使用 Promise.then() 代替 await
+ someAsyncFunction().then((result) => {
+ // 处理结果
+ })
+ }
+ }
+ })
+ ```
+
+2. 编译错误提示
+ - 错误信息:"参数类型不匹配:实际类型为 'Function1<..., UTSPromise>',预期类型为 'Function1<..., Unit>?'"
+ - 原因:回调函数返回 Promise 而非 void
+ - 解决:使用 `.then()` 代替 `await`
+
+3. 最佳实践
+ - 在回调函数中使用 `.then()` 处理异步操作
+ - 将异步逻辑封装为单独的函数,在回调中调用
+
+================================================================================
+三十九、2026-02-27 类型转换前必须检查类型(重要)
+================================================================================
+
+1. 使用 `as` 类型转换前必须检查实际类型
+ - 直接使用 `as string` 转换可能导致运行时类型转换异常
+ - 错误示例:
+ ```typescript
+ const idVal = item['id']
+ const id = idVal as string // 错误:如果 idVal 是其他类型会崩溃
+ ```
+ - 正确示例:
+ ```typescript
+ const idVal = item['id']
+ const id = (idVal != null && typeof idVal == 'string') ? (idVal as string) : ''
+ ```
+
+2. 运行时错误提示
+ - 错误信息:"null cannot be cast to non-null type kotlin.String"
+ - 错误信息:"java.lang.Boolean cannot be cast to java.lang.String"
+ - 原因:直接类型转换时,实际类型与目标类型不匹配
+ - 解决:使用 `typeof` 检查类型后再转换
+
+3. 最佳实践
+ - 使用 `typeof` 检查类型
+ - 使用 `!= null` 检查空值
+ - 提供默认值防止空指针异常
+
+================================================================================
+四十、2026-02-27 UTSJSONObject 必须正确转换(重要)
+================================================================================
+
+1. `as UTSJSONObject` 不会添加方法
+ - 从数据库返回的数据需要正确转换为 UTSJSONObject
+ - 错误示例:
+ ```typescript
+ const item = rawList[i]
+ const brandObj = item as UTSJSONObject // 错误:brandObj.getString 不存在
+ ```
+ - 正确示例:
+ ```typescript
+ const item = rawList[i]
+ const brandObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
+ const id = brandObj.getString('id') ?? ''
+ ```
+
+2. 运行时错误提示
+ - 错误信息:"getString is not a function"
+ - 原因:对象没有正确转换为 UTSJSONObject
+ - 解决:使用 `JSON.parse(JSON.stringify())` 进行转换
+
+3. 最佳实践
+ - 对于从数据库/API 返回的数据,统一使用 `JSON.parse(JSON.stringify())` 转换
+ - 使用 `.getString()`、`.getNumber()` 等方法安全访问属性
+
+================================================================================
+
+================================================================================
diff --git a/pages/mall/consumer/favorites.uvue b/pages/mall/consumer/favorites.uvue
index 7d09482c..db423f80 100644
--- a/pages/mall/consumer/favorites.uvue
+++ b/pages/mall/consumer/favorites.uvue
@@ -8,20 +8,12 @@
-
-
- {{ product.name }}
+
+ {{ product.name }}
+
¥{{ product.price }}
-
@@ -32,27 +24,42 @@
\r\n","// i18n 国际化配置\r\n// 这是一个简化的 i18n 实现,用于支持多语言切换\r\n\r\n// 语言资源\r\nconst messages: UTSJSONObject = new UTSJSONObject()\r\n\r\n// 默认语言\r\nconst defaultLocale = 'zh-CN'\r\n\r\n// 当前语言(响应式)\r\nlet currentLocale = defaultLocale\r\n\r\n// 翻译函数\r\nfunction t(key: string, values: UTSJSONObject | null = null, locale: string | null = null): string {\r\n\tconst targetLocale = locale ?? currentLocale\r\n\t// 这里应该从 messages 中获取翻译,简化实现直接返回 key\r\n\t// 实际项目中应该加载语言资源文件\r\n\treturn key\r\n}\r\n\r\n// 创建响应式 locale 对象\r\nclass LocaleWrapper {\r\n get value(): string {\r\n return currentLocale\r\n }\r\n set value(newLocale: string) {\r\n currentLocale = newLocale\r\n }\r\n}\r\nconst localeObj = new LocaleWrapper()\r\n\r\n// I18n Global Context\r\nclass I18nGlobal {\r\n\tt(key: string, values: UTSJSONObject | null = null, locale: string | null = null): string {\r\n\t\treturn t(key, values, locale)\r\n\t}\r\n\tlocale: LocaleWrapper = localeObj\r\n}\r\n\r\n// I18n Instance\r\nclass I18nInstance {\r\n\tglobal: I18nGlobal = new I18nGlobal()\r\n}\r\n\r\n// 导出 i18n 对象\r\nconst i18n = new I18nInstance()\r\nexport default i18n\r\n","// ak-req 类型定义\r\nexport type AkReqOptions = {\r\n url: string;\r\n method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' |'HEAD';\r\n data?: UTSJSONObject | Array;\r\n headers?: UTSJSONObject;\r\n timeout?: number;\r\n contentType?: string; // 新增,支持顶级 contentType\r\n // 可选:重试设置(仅网络错误/超时触发)。默认重试 0 次\r\n retryCount?: number; // 最大重试次数,默认 0\r\n retryDelayMs?: number; // 首次重试延迟,默认 300ms,指数退避\r\n};\r\n// 上传参数类型定义\r\nexport type AkReqUploadOptions = {\r\n url: string,\r\n filePath: string,\r\n name: string,\r\n formData?: UTSJSONObject,\r\n headers?: UTSJSONObject,\r\n apikey?: string,\r\n timeout?: number,\r\n // 进度回调,0-100(注意:H5/APP 平台支持不同)\r\n onProgress?: (progress: number, transferredBytes?: number, totalBytes?: number) => void,\r\n // 可选:重试设置(仅网络错误/超时触发)。默认 0\r\n retryCount?: number,\r\n retryDelayMs?: number\r\n};\r\n\r\nexport type AkReqResponse = {\r\n status: number;\r\n data: T | Array | null; // 支持 null\r\n headers: UTSJSONObject;\r\n error: UniError | null;\r\n total:number |null;\r\n page: number |null;\r\n limit: number |null;\r\n hasmore:boolean |null;\r\n origin: any | null;\r\n};\r\n\r\nexport class AkReqError extends Error {\r\n code: number;\r\n constructor(message: string, code: number = 0) {\r\n super(message);\r\n this.code = code;\r\n this.name = 'AkReqError';\r\n }\r\n}\r\n","// Supabase 配置\r\n// 内网环境 - 本地部署的 Supabase\r\n// IP: 192.168.1.62\r\n// Kong HTTP Port: 8000\r\n\r\n//export const SUPA_URL: string = 'http://192.168.1.61:18000'\r\n//export const SUPA_KEY: string = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlLTEiLCJpYXQiOjE3Njk2NzY0OTgsImV4cCI6MTkyNzM1NjQ5OH0.ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'\r\nexport const SUPA_URL: string = 'http://192.168.1.61:18000'\r\nexport const SUPA_KEY: string = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlLTEiLCJpYXQiOjE3Njk2NzY0OTgsImV4cCI6MTkyNzM1NjQ5OH0.ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'\r\n\r\n// WebSocket 实时连接(内网使用 ws:// 而非 wss://)\r\nexport const WS_URL: string = 'ws://192.168.1.61:18000/realtime/v1/websocket'\r\n//export const WS_URL: string = 'ws://localhost:18000/realtime/v1/websocket'\r\n\r\n// 备用配置(已注释,如需切换可取消注释)\r\n// 开发环境 - 其他内网地址\r\n// export const SUPA_URL: string = 'http://192.168.0.150:8080'\r\n// export const SUPA_KEY: string = 'your-anon-key'\r\n// export const WS_URL: string = 'ws://192.168.0.150:8080/realtime/v1/websocket'\r\n\r\n// 生产环境 - Supabase 云服务(已注释)\r\n// export const SUPA_URL: string = 'https://ak3.oulog.com'\r\n// export const SUPA_KEY: string = 'your-anon-key'\r\n// export const WS_URL: string = 'wss://ak3.oulog.com/realtime/v1/websocket'\r\n\r\n// 指向你的 Supabase 服务(开发/私有部署)\r\n// export const SUPA_URL: string = 'http://192.168.1.64:3000'\r\n// export const SUPA_KEY: string = 'your-anon-key'\r\n// export const WS_URL: string = 'ws://192.168.1.64:3000/realtime/v1'\r\n\r\n// 路由配置\r\nexport const HOME_REDIRECT: string = '/pages/mall/consumer/index'\r\nexport const TABORPAGE: string = '/pages/mall/consumer/index'\r\n\r\n// 测试模式:放开任意跳转(禁用启动页/登录/401 的强制重定向)\r\nexport const IS_TEST_MODE: boolean = true","// 通用 UTSJSONObject 转任意 type 的函数\r\n// UTS 2024\r\n\r\nimport i18n from '@/uni_modules/i18n/index.uts';\r\n\r\n/**\r\n * 切换应用语言设置\r\n * @param locale 语言代码,如 'zh-CN' 或 'en-US'\r\n */\r\nexport function switchLocale(locale: string) {\r\n // 设置存储\r\n uni.setStorageSync('uVueI18nLocale', locale);\r\n \r\n // 设置 i18n 语言\r\n try {\r\n if (i18n != null && i18n.global != null) {\r\n i18n.global.locale.value = locale;\r\n }\r\n } catch (err) {\r\n __f__('error','at utils/utils.uts:20','Failed to switch locale:', err);\r\n }\r\n}\r\n\r\n/**\r\n * 获取当前语言设置\r\n * @returns 当前语言代码\r\n */\r\nexport function getCurrentLocale(): string {\r\n const locale = uni.getStorageSync('uVueI18nLocale') as string;\r\n if (locale == null || locale == '') {\r\n return 'zh-CN';\r\n }\r\n return locale;\r\n}\r\n\r\n/**\r\n * 确保语言设置正确初始化\r\n */\r\nexport function ensureLocaleInitialized() {\r\n const currentLocale = getCurrentLocale();\r\n if (currentLocale == null || currentLocale == '') {\r\n switchLocale('zh-CN');\r\n }\r\n}\r\n/**\r\n * 将任意错误对象转换为标准的 UniError\r\n * @param error 任意类型的错误对象\r\n * @param defaultMessage 默认错误消息\r\n * @returns 标准化的 UniError 对象\r\n */\r\nexport function toUniError(error: any, defaultMessage: string = '操作失败'): UniError {\r\n // 如果已经是 UniError,直接返回\r\n if (error instanceof UniError) {\r\n return error\r\n }\r\n let errorMessage = defaultMessage\r\n let errorCode = -1\r\n \r\n try {\r\n // 如果是普通 Error 对象\r\n if (error instanceof Error) {\r\n errorMessage = error.message != null && error.message != '' ? error.message : defaultMessage\r\n }\r\n // 如果是字符串\r\n else if (typeof error === 'string') {\r\n errorMessage = error\r\n } // 如果是对象,尝试提取错误信息\r\n else if (error != null && typeof error === 'object') {\r\n const errorObj = error as UTSJSONObject\r\n let message: string = ''\r\n \r\n // 逐个检查字段,避免使用 || 操作符\r\n if (errorObj['message'] != null) {\r\n const msgValue = errorObj['message']\r\n if (typeof msgValue === 'string') {\r\n message = msgValue\r\n }\r\n } else if (errorObj['errMsg'] != null) {\r\n const msgValue = errorObj['errMsg']\r\n if (typeof msgValue === 'string') {\r\n message = msgValue\r\n }\r\n } else if (errorObj['error'] != null) {\r\n const msgValue = errorObj['error']\r\n if (typeof msgValue === 'string') {\r\n message = msgValue\r\n }\r\n } else if (errorObj['details'] != null) {\r\n const msgValue = errorObj['details']\r\n if (typeof msgValue === 'string') {\r\n message = msgValue\r\n }\r\n } else if (errorObj['msg'] != null) {\r\n const msgValue = errorObj['msg']\r\n if (typeof msgValue === 'string') {\r\n message = msgValue\r\n }\r\n }\r\n \r\n if (message != '') {\r\n errorMessage = message\r\n }\r\n \r\n // 尝试提取错误码\r\n let code: number = 0\r\n if (errorObj['code'] != null) {\r\n const codeValue = errorObj['code']\r\n if (typeof codeValue === 'number') {\r\n code = codeValue\r\n }\r\n } else if (errorObj['errCode'] != null) {\r\n const codeValue = errorObj['errCode']\r\n if (typeof codeValue === 'number') {\r\n code = codeValue\r\n }\r\n } else if (errorObj['status'] != null) {\r\n const codeValue = errorObj['status']\r\n if (typeof codeValue === 'number') {\r\n code = codeValue\r\n }\r\n }\r\n \r\n if (code != 0) {\r\n errorCode = code\r\n }\r\n }\r\n } catch (e) {\r\n __f__('error','at utils/utils.uts:128','Error converting to UniError:', e)\r\n errorMessage = defaultMessage\r\n }\r\n // 创建标准 UniError\r\n const uniError = new UniError('AppError', errorCode, errorMessage)\r\n return uniError\r\n}\r\n\r\n/**\r\n * 响应式状态管理\r\n * @returns 响应式状态对象\r\n */\r\nexport function responsiveState() {\r\n const screenInfo = uni.getSystemInfoSync()\r\n const screenWidth = screenInfo.screenWidth\r\n \r\n return {\r\n isLargeScreen: screenWidth >= 768,\r\n isSmallScreen: screenWidth < 576,\r\n screenWidth: screenWidth,\r\n cardColumns: screenWidth >= 768 ? 3 : screenWidth >= 576 ? 2 : 1\r\n }\r\n}\r\n\r\nexport function goToLogin(redirectUrl?: string | null) {\r\n try {\r\n const target = redirectUrl != null && redirectUrl.length > 0 ? redirectUrl : ''\r\n if (target.length > 0) {\r\n const redirect = encodeURIComponent(target)\r\n uni.navigateTo({ url: `/pages/user/login?redirect=${redirect}` })\r\n } else {\r\n uni.navigateTo({ url: '/pages/user/login' })\r\n }\r\n } catch (e) {\r\n uni.navigateTo({ url: '/pages/user/login' })\r\n }\r\n}\r\n\r\n/**\r\n * 兼容 UTS Android 的剪贴板写入\r\n * @param text 要写入剪贴板的文本\r\n */\r\nexport function setClipboard(text: string): void {\r\n\r\n\r\n\r\n}\r\n\r\n/**\r\n * 格式化时间,显示为相对时间(如:刚刚,几小时前)\r\n * @param dateStr ISO 格式的日期字符串\r\n * @returns 格式化后的相对时间字符串\r\n */\r\nexport function formatTime(dateStr: string): string {\r\n if (dateStr == '') return ''\r\n try {\r\n const date = new Date(dateStr)\r\n const now = new Date()\r\n const diff = now.getTime() - date.getTime()\r\n const hours = Math.floor(diff / (1000 * 60 * 60))\r\n \r\n if (hours < 1) {\r\n return '刚刚'\r\n } else if (hours < 24) {\r\n return `${hours}小时前`\r\n } else {\r\n return `${Math.floor(hours / 24)}天前`\r\n }\r\n } catch (e) {\r\n __f__('error','at utils/utils.uts:197','formatTime error:', e)\r\n return dateStr.replace('T', ' ').split('.')[0]\r\n }\r\n}\r\n\r\n","import { AkReqResponse, AkReqUploadOptions, AkReq } from '@/uni_modules/ak-req/index.uts'\r\nimport type { AkReqOptions } from '@/uni_modules/ak-req/index.uts'\r\nimport { toUniError } from '@/utils/utils.uts'\r\n\r\nexport type AkSupaSignInResult = {\r\n\taccess_token : string;\r\n\trefresh_token : string;\r\n\texpires_at : number;\r\n\tuser : UTSJSONObject | null;\r\n\ttoken_type ?: string;\r\n\texpires_in ?: number;\r\n\traw : UTSJSONObject;\r\n}\r\n\r\n// Count 选项枚举\r\nexport type CountOption = 'exact' | 'planned' | 'estimated';\r\n\r\n// 定义查询选项类型,兼容 UTS\r\nexport type AkSupaSelectOptions = {\r\n\tlimit ?: number;\r\n\torder ?: string;\r\n\tgetcount ?: string; // 保持向后兼容\r\n\tcount ?: CountOption; // 新增:更清晰的 count 选项\r\n\thead ?: boolean; // 新增:head 模式,只返回元数据\r\n\tcolumns ?: string;\r\n\tsingle ?: boolean; // 新增,支持 single-object\r\n\trangeFrom ?: number; // 新增:range 分页起始位置\r\n\trangeTo ?: number; // 新增:range 分页结束位置\r\n};\r\n\r\n// 新增:order方法参数类型\r\nexport type OrderOptions = {\r\n\tascending ?: boolean;\r\n};\r\n\r\n// 新增类型定义,便于 getSession 返回类型复用\r\nexport type AkSupaSessionInfo = {\r\n\tsession : AkSupaSignInResult | null;\r\n\tuser : UTSJSONObject | null;\r\n};\r\n\r\n// 链式请求构建器\r\n// 强类型条件定义\r\ntype AkSupaCondition = {\r\n\tfield : string; // 已经 encodeURIComponent 过\r\n\top : string;\r\n\tvalue : any;\r\n\tlogic : string; // 'and' | 'or'\r\n};\r\n\r\nexport class AkSupaQueryBuilder {\r\n\tprivate _supa : AkSupa;\r\n\tprivate _table : string;\r\n\tprivate _filter : UTSJSONObject | null = null;\r\n\tprivate _options : AkSupaSelectOptions = {};\r\n\tprivate _values : UTSJSONObject | Array | null = null;\r\n\tprivate _single : boolean = false;\r\n\tprivate _conditions : Array = [];\r\n\tprivate _nextLogic : string = 'and';\r\n\t// 新增:记录当前操作类型\r\n\tprivate _action : 'select' | 'insert' | 'update' | 'delete' | 'rpc' | null = null;\r\n\tprivate _orString : string | null = null; // 新增:支持 or 字符串\r\n\tprivate _rpcFunction : string | null = null;\r\n\tprivate _rpcParams : UTSJSONObject | null = null;\r\n\tprivate _page : number = 1; // 新增:当前页码\r\n\r\n\tconstructor(supa : AkSupa, table : string) {\r\n\t\tthis._supa = supa;\r\n\t\tthis._table = table;\r\n\t}\r\n\r\n\t// 链式条件方法\r\n\teq(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'eq', value); }\r\n\tneq(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'neq', value); }\r\n\tgt(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'gt', value); }\r\n\tgte(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'gte', value); }\r\n\tlt(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'lt', value); }\r\n\tlte(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'lte', value); }\r\n\tlike(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'like', value); }\r\n\tilike(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'ilike', value); }\r\n\tin(field : string, value : any[]) : AkSupaQueryBuilder { return this._addCond(field, 'in', value); }\r\n\tis(field : string, value : any | null) : AkSupaQueryBuilder { return this._addCond(field, 'is', value); }\r\n\tcontains(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'cs', value); }\r\n\tcontainedBy(field : string, value : any) : AkSupaQueryBuilder { return this._addCond(field, 'cd', value); }\r\n\tnot(field : string, opOrValue : any, value: any | null = null) : AkSupaQueryBuilder {\r\n\t\tif (value != null) {\r\n\t\t\t// 三元形式:field, operator, value\r\n\t\t\t// 例如 not('badge', 'is', null) -> badge=not.is.null\r\n\t\t\tconst combinedOp = 'not.' + opOrValue;\r\n\t\t\t// 将 null 转换为字符串 'null',避免构造对象时缺少 value 属性\r\n\t\t\tlet safeValue = value;\r\n\t\t\tif (value === null) {\r\n\t\t\t\tsafeValue = 'null';\r\n\t\t\t}\r\n\t\t\treturn this._addCond(field, combinedOp, safeValue);\r\n\t\t} else {\r\n\t\t\t// 二元形式:field, value\r\n\t\t\tlet safeValue = opOrValue;\r\n\t\t\tif (opOrValue === null) {\r\n\t\t\t\tsafeValue = 'null';\r\n\t\t\t}\r\n\t\t\treturn this._addCond(field, 'not', safeValue);\r\n\t\t}\r\n\t}\r\n\r\n\tand() : AkSupaQueryBuilder { this._nextLogic = 'and'; return this; }\r\n\tor(str ?: string) : AkSupaQueryBuilder {\r\n\t\tif (typeof str == 'string') {\r\n\t\t\tthis._orString = str;\r\n\t\t} else {\r\n\t\t\tthis._nextLogic = 'or';\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\r\n\tprivate _addCond(afield : string, op : string, value : any | null) : AkSupaQueryBuilder {\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:117','add cond:', op, afield, value)\r\n\t\tconst field = encodeURIComponent(afield)!!\r\n\t\t// 将 null 转换为字符串 'null',避免构造对象时缺少 value 属性\r\n\t\tlet safeValue = value;\r\n\t\tif (value === null) {\r\n\t\t\tsafeValue = 'null';\r\n\t\t}\r\n\t\tthis._conditions.push({ field, op, value: safeValue, logic: this._nextLogic });\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:125',this._conditions)\r\n\t\tthis._nextLogic = 'and';\r\n\t\treturn this;\r\n\t}\r\n\r\n\t// 支持原有 where 方式\r\n\twhere(filter : UTSJSONObject) : AkSupaQueryBuilder {\r\n\t\tthis._filter = filter;\r\n\t\treturn this;\r\n\t}\r\n\r\n\tpage(page : number) : AkSupaQueryBuilder {\r\n\t\tthis._page = page;\r\n\t\t// 如果已设置 limit,则自动设置 range\r\n\t\tlet limit = 0;\r\n\t\tif (typeof this._options.limit == 'number') {\r\n\t\t\tlimit = this._options.limit ?? 0;\r\n\t\t}\r\n\t\tif (limit > 0) {\r\n\t\t\tconst from = (page - 1) * limit;\r\n\t\t\tconst to = from + limit - 1;\r\n\t\t\tthis.range(from, to);\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\tlimit(limit : number) : AkSupaQueryBuilder {\r\n\t\tthis._options.limit = limit;\r\n\t\t// 总是为 limit 设置对应的 range,确保限制生效\r\n\t\tconst from = (this._page - 1) * limit;\r\n\t\tconst to = from + limit - 1;\r\n\t\tthis.range(from, to);\r\n\t\treturn this;\r\n\t}\r\n\r\n\torder(order : string, options ?: OrderOptions) : AkSupaQueryBuilder {\r\n\t\tif (options != null && options.ascending == false) {\r\n\t\t\tthis._options.order = order + '.desc';\r\n\t\t} else {\r\n\t\t\tthis._options.order = order + '.asc';\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\tcolumns(columns : string) : AkSupaQueryBuilder {\r\n\t\tthis._options.columns = columns;\r\n\t\treturn this;\r\n\t}\r\n\r\n\t// 新增:专门的 count 方法\r\n\tcount(option : CountOption = 'exact') : AkSupaQueryBuilder {\r\n\t\tthis._options.count = option;\r\n\t\tthis._options.head = true; // count 操作默认使用 head 模式\r\n\t\treturn this;\r\n\t}\r\n\r\n\t// 新增:便捷的 count 方法\r\n\tcountExact() : AkSupaQueryBuilder {\r\n\t\treturn this.count('exact');\r\n\t}\r\n\r\n\tcountEstimated() : AkSupaQueryBuilder {\r\n\t\treturn this.count('estimated');\r\n\t}\r\n\r\n\tcountPlanned() : AkSupaQueryBuilder {\r\n\t\treturn this.count('planned');\r\n\t}\r\n\r\n\t// 新增:head 模式方法\r\n\thead(enable : boolean = true) : AkSupaQueryBuilder {\r\n\t\tthis._options.head = enable;\r\n\t\treturn this;\r\n\t}\r\n\r\n\tvalues(values : UTSJSONObject) : AkSupaQueryBuilder {\r\n\t\tthis._values = values;\r\n\t\treturn this;\r\n\t}\r\n\tsingle() : AkSupaQueryBuilder {\r\n\t\tthis._single = true;\r\n\t\treturn this;\r\n\t}\r\n\trange(from : number, to : number) : AkSupaQueryBuilder {\r\n\t\tthis._options.rangeFrom = from;\r\n\t\tthis._options.rangeTo = to;\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:209','设置 range:', from, 'to', to);\r\n\t\treturn this;\r\n\t}\r\n\t// 将 _conditions 强类型直接转换为 Supabase/PostgREST 查询字符串(不再用 UTSJSONObject 做中转)\r\n\tprivate _buildFilter() : string | null {\r\n\t\tif (this._conditions.length == 0 && (this._orString==null || this._orString == \"\")) {\r\n\t\t\t// 兼容 where(filter) 方式\r\n\t\t\tif (this._filter == null) return null;\r\n\t\t\t// 兼容旧的 UTSJSONObject filter\r\n\t\t\treturn buildSupabaseFilterQuery(this._filter);\r\n\t\t}\r\n\r\n\t\t// 先分组 and/or,全部用 AkSupaCondition 强类型\r\n\t\tconst ands: AkSupaCondition[] = [];\r\n\t\tconst ors: AkSupaCondition[] = [];\r\n\t\tfor (const c of this._conditions) {\r\n\t\t\tif (c.logic == \"or\") {\r\n\t\t\t\tors.push(c);\r\n\t\t\t} else {\r\n\t\t\t\tands.push(c);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tconst params: string[] = [];\r\n\t\t// 处理 and 条件\r\n\t\tfor (const cond of ands) {\r\n\t\t\tconst k = cond.field;\r\n\t\t\tconst op = cond.op;\r\n\t\t\tconst val = cond.value;\r\n\t\t\tif ((op == 'in' || op == 'not.in') && Array.isArray(val)) {\r\n\t\t\t\tparams.push(`${k}=${op}.(${val.map(x => typeof x == 'object' ? encodeURIComponent(JSON.stringify(x)) : encodeURIComponent(x.toString())).join(',')})`);\r\n\t\t\t} else if ((op == 'is' || op == 'not.is') && (val == null || val == 'null')) {\r\n\t\t\t\tparams.push(`${k}=${op}.null`);\r\n\t\t\t} else {\r\n\t\t\t\tconst opvalstr: string = (typeof val == 'object') ? JSON.stringify(val) : (val as string);\r\n\t\t\t\tparams.push(`${k}=${op}.${encodeURIComponent(opvalstr)}`);\r\n\t\t\t}\r\n\t\t}\r\n\t\t// 处理 or 条件\r\n\t\tif (ors.length > 0) {\r\n\t\t\tconst orStr = ors.map(o => {\r\n\t\t\t\tconst k = o.field;\r\n\t\t\t\tconst op = o.op;\r\n\t\t\t\tconst val = o.value;\r\n\t\t\t\tif (op == \"in\" && Array.isArray(val)) {\r\n\t\t\t\t\treturn `${k}.in.(${val.map(x => encodeURIComponent(x as string)).join(\",\")})`;\r\n\t\t\t\t}\r\n\t\t\t\tif (op == \"is\" && (val == null)) {\r\n\t\t\t\t\treturn `${k}.is.null`;\r\n\t\t\t\t}\r\n\t\t\t\treturn `${k}.${op}.${encodeURIComponent(val as string)}`;\r\n\t\t\t}).join(\",\");\r\n\t\t\tparams.push(`or=(${orStr})`);\r\n\t\t}\r\n\t\tif (this._orString!=null && this._orString !== \"\") {\r\n\t\t\tparams.push(`or=(${encodeURIComponent(this._orString!!)})`);\r\n\t\t}\r\n\t\treturn params.length > 0 ? params.join('&') : null;\r\n\t}\r\n\r\n\tselect(columns : string = \"*\", opt : UTSJSONObject | null = null) : AkSupaQueryBuilder {\r\n\t\tthis._action = 'select';\r\n\t\tif (columns != null) {\r\n\t\t\tthis._options.columns = columns;\r\n\t\t}\r\n\t\tif (opt != null) {\r\n\t\t\t// 合并 opt 到 this._options\r\n\t\t\tObject.assign(this._options, opt);\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\tinsert(values : UTSJSONObject | Array) : AkSupaQueryBuilder {\r\n\t\tthis._action = 'insert';\r\n\t\t// 检查是否为空\r\n\t\tif (Array.isArray(values)) {\r\n\t\t\tif (values.length == 0) throw toUniError('No values set for insert', 'Insert操作缺少数据');\r\n\t\t} else {\r\n\t\t\tif (UTSJSONObject.keys(values).length == 0) throw toUniError('No values set for insert', 'Insert操作缺少数据');\r\n\t\t}\r\n\t\tthis._values = values;\r\n\t\treturn this;\r\n\t}\r\n\tupdate(values : UTSJSONObject) : AkSupaQueryBuilder {\r\n\t\tthis._action = 'update';\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:293','ak update', this._action)\r\n\t\tif (UTSJSONObject.keys(values).length == 0) throw toUniError('No values set for update', '更新操作缺少数据');\r\n\t\tthis._values = values;\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:296','ak update', values)\r\n\t\treturn this;\r\n\t}\r\n\tdelete() : AkSupaQueryBuilder {\r\n\t\tthis._action = 'delete';\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:301','delete action now')\r\n\t\tconst filter = this._buildFilter();\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:303',filter)\r\n\t\tif (filter == null) throw toUniError('No filter set for delete', '删除操作缺少筛选条件');\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:305','delete action')\r\n\t\treturn this;\r\n\t}\r\n\r\n\trpc(functionName : string, params ?: UTSJSONObject) : AkSupaQueryBuilder {\r\n\t\tthis._action = 'rpc';\r\n\t\tthis._rpcFunction = functionName;\r\n\t\tthis._rpcParams = params;\r\n\t\treturn this;\r\n\t}\r\n\t// 链式请求最终执行方法 - 返回 UTSJSONObject\r\n\tasync execute() : Promise> {\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:317','execute')\r\n\t\tconst filter = this._buildFilter();\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:319','execute', filter)\r\n\t\tlet res : any;\r\n\t\tswitch (this._action) {\r\n\t\t\tcase 'select': {\r\n\t\t\t\t// 传递 single 状态到 options\r\n\t\t\t\tif (this._single) {\r\n\t\t\t\t\tthis._options.single = true;\r\n\t\t\t\t\t// 如果是 single 请求,自动设置 limit 为 1\r\n\t\t\t\t\tif (this._options.limit == null) {\r\n\t\t\t\t\t\tthis._options.limit = 1;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t//__f__('log','at components/supadb/aksupa.uts:330',this._options)\r\n\t\t\t\t}\t\t\t\t// 保证分页统计\r\n\t\t\t\tif (this._options.limit != null) {\r\n\t\t\t\t\tif (this._options.getcount == null && this._options.count == null) {\r\n\t\t\t\t\t\tthis._options.count = 'exact'; // 优先使用新的 count 选项\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tres = await this._supa.select(this._table, filter, this._options);\r\n\t\t\t\t// 解析 content-range header\r\n\t\t\t\tlet total = 0;\r\n\t\t\t\tlet hasmore = false;\r\n\t\t\t\tconst page = this._page;\r\n\t\t\t\tlet resdata = res.data\r\n\t\t\t\tlet limit = 0;\r\n\t\t\t\tif (typeof this._options.limit == 'number') {\r\n\t\t\t\t\tlimit = this._options.limit ?? 0;\r\n\t\t\t\t} else if (Array.isArray(resdata)) {\r\n\t\t\t\t\tlimit = resdata.length;\r\n\t\t\t\t}\r\n\t\t\t\tlet contentRange : string | null = null;\r\n\t\t\t\tif (res.headers != null) {\r\n\t\t\t\t\tlet theheader = res.headers as UTSJSONObject\r\n\t\t\t\t\tif (typeof theheader.get == 'function') {\r\n\r\n\t\t\t\t\t\tcontentRange = theheader.get('content-range') as string | null;\r\n\t\t\t\t\t} else if (typeof theheader['content-range'] == 'string') {\r\n\t\t\t\t\t\tcontentRange = theheader['content-range'] as string;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (contentRange != null) {\r\n\t\t\t\t\tconst match = /\\/(\\d+)$/.exec(contentRange);\r\n\t\t\t\t\tif (match != null) {\r\n\t\t\t\t\t\ttotal = parseInt(match[1] ?? \"0\");\r\n\t\t\t\t\t\thasmore = (page * limit) < total;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (total == 0) {\r\n\t\t\t\t\tif (typeof res['count'] == 'number') {\r\n\t\t\t\t\t\ttotal = res['count'] as number ?? 0;\r\n\t\t\t\t\t} else if (Array.isArray(resdata)) {\r\n\t\t\t\t\t\ttotal = resdata.length;\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\ttotal = 0;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (!hasmore) hasmore = (page * limit) < total;\t\t\t\t// 如果是 head 模式,只返回 count 信息\r\n\t\t\t\tif (this._options.head == true) {\r\n\t\t\t\t\treturn {\r\n\t\t\t\t\t\tdata: null, // head 模式不返回数据\r\n\t\t\t\t\t\ttotal,\r\n\t\t\t\t\t\tpage,\r\n\t\t\t\t\t\tlimit,\r\n\t\t\t\t\t\thasmore: false, // head 模式不需要分页信息\r\n\t\t\t\t\t\torigin: res,\r\n\t\t\t\t\t\tstatus: res.status,\r\n\t\t\t\t\t\theaders: res.headers,\r\n\t\t\t\t\t\terror: res.error\r\n\t\t\t\t\t} as AkReqResponse;\r\n\t\t\t\t}\r\n\r\n\t\t\t\treturn {\r\n\t\t\t\t\tdata: res.data,\r\n\t\t\t\t\ttotal,\r\n\t\t\t\t\tpage,\r\n\t\t\t\t\tlimit,\r\n\t\t\t\t\thasmore,\r\n\t\t\t\t\torigin: res,\r\n\t\t\t\t\tstatus: res.status,\r\n\t\t\t\t\theaders: res.headers,\r\n\t\t\t\t\terror: res.error\r\n\t\t\t\t} as AkReqResponse;\r\n\t\t\t}\r\n\t\t\tcase 'insert': {\r\n\t\t\t\tconst insertValues = this._values;\r\n\t\t\t\tif (insertValues == null) throw toUniError('No values set for insert', '插入操作缺少数据');\r\n\t\t\t\tres = await this._supa.insert(this._table, insertValues);\r\n\t\t\t\tbreak;\r\n\t\t\t} case 'update': {\r\n\t\t\t\tconst updateValues = this._values;\r\n\t\t\t\tif (updateValues == null) throw toUniError('No values set for update', '更新操作缺少数据');\r\n\t\t\t\tif (filter == null) throw toUniError('No filter set for update', '更新操作缺少筛选条件');\r\n\t\t\t\t// Update操作只支持单个对象,不支持数组\r\n\t\t\t\tif (Array.isArray(updateValues)) throw toUniError('Update does not support array values', '更新操作不支持数组数据');\r\n\t\t\t\tres = await this._supa.update(this._table, filter, updateValues as UTSJSONObject);\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t\tcase 'delete': {\r\n\t\t\t\tif (filter == null) throw toUniError('No filter set for delete', '删除操作缺少筛选条件');\r\n\t\t\t\tres = await this._supa.delete(this._table, filter);\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t\tcase 'rpc': {\r\n\t\t\t\tif (this._rpcFunction == null) throw toUniError('No RPC function specified', 'RPC调用缺少函数名');\r\n\t\t\t\tres = await this._supa.rpc(this._rpcFunction as string, this._rpcParams);\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t\tdefault: {\r\n\t\t\t\tres = await this._supa.select(this._table, filter, this._options);\r\n\t\t\t}\r\n\t\t}\r\n\t\t// 保证 data 字段存在(不能赋null,赋空对象或空字符串)\r\n\t\tif (res[\"data\"] == null) res[\"data\"] = {};\r\n\t\treturn res;\r\n\t}\t// 新增:支持类型转换的执行方法\r\n\tasync executeAs() : Promise>> {\r\n\t\tconst result = await this.execute();\r\n\r\n\t\t// 如果原始 data 是 null,直接返回 null\r\n\t\tif (result.data == null) {\r\n\t\t\tconst aaa = {\r\n\t\t\t\tstatus: result.status,\r\n\t\t\t\tdata: null,\r\n\t\t\t\theaders: result.headers,\r\n\t\t\t\terror: result.error,\r\n\t\t\t\ttotal: result.total,\r\n\t\t\t\tpage: result.page,\r\n\t\t\t\tlimit: result.limit,\r\n\t\t\t\thasmore: result.hasmore,\r\n\t\t\t\torigin: result.origin\r\n\t\t\t}\r\n\t\t\treturn aaa;\r\n\t\t}\r\n\r\n\t\t// 尝试类型转换\r\n\t\tlet convertedData : T | Array | null = null;\r\n\r\n\t\ttry {\r\n\t\t\tif (Array.isArray(result.data)) {\r\n\t\t\t\t// 处理数组数据\r\n\t\t\t\tconst dataArray = result.data;\r\n\t\t\t\tconst convertedArray : Array = [];\r\n\t\t\t\t//__f__('log','at components/supadb/aksupa.uts:461',convertedArray)\r\n\t\t\t\tfor (let i = 0; i < dataArray.length; i++) {\r\n\t\t\t\t\tconst item = dataArray[i];\r\n\t\t\t\t\tif (item instanceof UTSJSONObject) {\r\n\r\n\t\t\t\t\t\t// //__f__('log','at components/supadb/aksupa.uts:466',item)\r\n\t\t\t\t\t\tconst parsed = item.parse();\r\n\t\t\t\t\t\t// //__f__('log','at components/supadb/aksupa.uts:468','ak parsed')\r\n\r\n\r\n\r\n\r\n\t\t\t\t\t\tif (parsed != null) {\r\n\t\t\t\t\t\t\tconvertedArray.push(parsed);\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t__f__('warn','at components/supadb/aksupa.uts:476','转换失败,使用原始对象:', item);\r\n\t\t\t\t\t\t\tconvertedArray.push(item as T);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\t// 将普通对象转换为 UTSJSONObject 后再 parse\r\n\t\t\t\t\t\tconst jsonObj = new UTSJSONObject(item);\r\n\r\n\t\t\t\t\t\tconst parsed = jsonObj.parse();\r\n\r\n\r\n\r\n\r\n\t\t\t\t\t\tif (parsed != null) {\r\n\t\t\t\t\t\t\tconvertedArray.push(parsed);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse {\r\n\t\t\t\t\t\t\t__f__('warn','at components/supadb/aksupa.uts:492','转换失败,使用原始对象:', item);\r\n\t\t\t\t\t\t\tconvertedArray.push(item as T);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tconvertedData = convertedArray;\r\n\r\n\t\t\t} else {\r\n\t\t\t\t// 处理单个对象\r\n\t\t\t\tconst convertedArray : Array = [];\r\n\t\t\t\tif (result.data instanceof UTSJSONObject) {\r\n\t\t\t\t\tconst parsed = result.data.parse();\r\n\r\n\t\t\t\t\tif (parsed != null) {\r\n\t\t\t\t\t\tconvertedArray.push(parsed);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse {\r\n\t\t\t\t\t\t//__f__('log','at components/supadb/aksupa.uts:509','转换失败:', result.data)\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\tconst jsonObj = new UTSJSONObject(result.data);\r\n\t\t\t\t\tconst parsed = jsonObj.parse();\r\n\t\t\t\t\tif (parsed != null) {\r\n\t\t\t\t\t\tconvertedArray.push(parsed);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse {\r\n\t\t\t\t\t\t//__f__('log','at components/supadb/aksupa.uts:518','转换失败:', result.data)\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tconvertedData = convertedArray;\r\n\t\t\t}\r\n\t\t} catch (e) {\r\n\t\t\t__f__('warn','at components/supadb/aksupa.uts:524','数据类型转换失败,使用原始数据:', e);\r\n\t\t\t__f__('log','at components/supadb/aksupa.uts:525',result.data)\r\n\t\t\t// 转换失败时,使用原始数据\r\n\t\t\tconvertedData = result.data as T | Array;\r\n\t\t}\r\n\t\tresult.data = convertedData\r\n\t\tconst aaa = result as AkReqResponse\r\n\t\t// const aaa = {\r\n\t\t// \tstatus: result.status,\r\n\t\t// \tdata: convertedData,\r\n\t\t// \theaders: result.headers,\r\n\t\t// \terror: result.error,\r\n\t\t// \ttotal: result.total,\r\n\t\t// \tpage: result.page,\r\n\t\t// \tlimit: result.limit,\r\n\t\t// \thasmore: result.hasmore,\r\n\t\t// \torigin: result.origin\r\n\t\t// } \r\n\t\treturn aaa;\r\n\r\n\t}\r\n}\r\n\r\n// 新增:链式 Storage 上传\r\nexport class AkSupaStorageUploadBuilder {\r\n\tprivate _supa : AkSupa;\r\n\tprivate _bucket : string = '';\r\n\tprivate _path : string = '';\r\n private _file : string = '';\r\n\tprivate _options : UTSJSONObject = {};\r\n\r\n\tconstructor(supa : AkSupa, bucket : string) {\r\n\t\tthis._supa = supa;\r\n\t\tthis._bucket = bucket;\r\n\t}\r\n\r\n\tpath(path : string) : AkSupaStorageUploadBuilder {\r\n\t\tthis._path = path;\r\n\t\treturn this;\r\n\t}\r\n\r\n file(file : string) : AkSupaStorageUploadBuilder {\r\n\t\tthis._file = file;\r\n\t\treturn this;\r\n\t}\r\n\r\n\toptions(options : UTSJSONObject) : AkSupaStorageUploadBuilder {\r\n\t\tthis._options = options;\r\n\t\treturn this;\r\n\t}\r\n\tasync upload() : Promise> {\r\n if (this._bucket == '' || this._path == '' || this._file == '') {\r\n\t\t\tthrow toUniError('bucket, path, file are required', '上传文件缺少必要参数');\r\n\t\t}\r\n\t\tconst url = `${this._supa.baseUrl}/storage/v1/object/${this._bucket}/${this._path}`;\r\n\t\tconst apikey = this._supa.apikey;\r\n\t\t// 适配 uni.uploadFile\r\n\t\tconst uploadOptions : AkReqUploadOptions = {\r\n\t\t\turl,\r\n\t\t\tfilePath: this._file, // 这里假设 file 是本地路径\r\n\t\t\tname: 'file', // 默认字段名\r\n\t\t\theaders: {},\r\n\t\t\tapikey,\r\n\t\t\tformData: this._options\r\n\t\t};\r\n\t\treturn await AkReq.upload(uploadOptions);\r\n\t}\r\n}\r\n\r\n// 新增:明确的 StorageBucket 类,支持链式 upload\r\nclass AkSupaStorageBucket {\r\n\tprivate supa : AkSupa;\r\n\tprivate bucket : string;\r\n\tconstructor(supa : AkSupa, bucket : string) {\r\n\t\tthis.supa = supa;\r\n\t\tthis.bucket = bucket;\r\n\t}\r\n\tasync upload(path : string, filePath : string, options ?: UTSJSONObject) : Promise> {\r\n\t\tconst url = `${this.supa.baseUrl}/storage/v1/object/${this.bucket}/${path}`;\r\n\t\tlet headers : UTSJSONObject = { apikey: this.supa.apikey };\r\n\t\tconst formData : UTSJSONObject = {};\r\n\t\tif (options != null && typeof options == 'object') {\r\n\t\t\tif (typeof options.get == 'function' && options.get('x-upsert') != null) {\r\n\t\t\t\theaders['x-upsert'] = options.get('x-upsert');\r\n\t\t\t}\r\n\t\t\tconst keys = UTSJSONObject.keys(options);\r\n\t\t\tfor (let i = 0; i < keys.length; i++) {\r\n\t\t\t\tconst k = keys[i];\r\n\t\t\t\tif (k != 'x-upsert') formData[k] = options.get(k);\r\n\t\t\t}\r\n\t\t}\r\n\t\tconst token = AkReq.getToken();\r\n\t\tif (token != null && !(token == '')) {\r\n\t\t\theaders['Authorization'] = `Bearer ${token}`;\r\n\t\t}\r\n\t\treturn await AkReq.upload({\r\n\t\t\turl,\r\n\t\t\tfilePath,\r\n\t\t\tname: 'file',\r\n\t\t\tapikey: this.supa.apikey,\r\n\t\t\tformData,\r\n\t\t\theaders\r\n\t\t});\r\n\t}\r\n}\r\n\r\nexport class AkSupaStorageApi {\r\n\tprivate _supa : AkSupa;\r\n\tconstructor(supa : AkSupa) {\r\n\t\tthis._supa = supa;\r\n\t}\r\n\tfrom(bucket : string) : AkSupaStorageBucket {\r\n\t\treturn new AkSupaStorageBucket(this._supa, bucket);\r\n\t}\r\n}\r\n\r\nexport class AkSupa {\r\n\tbaseUrl : string;\r\n\tapikey : string;\r\n\tsession : AkSupaSignInResult | null = null;\r\n\tuser : UTSJSONObject | null = null;\r\n\tstorage : AkSupaStorageApi;\r\n\r\n\tconstructor(baseUrl : string, apikey : string) {\r\n\t\tthis.baseUrl = baseUrl;\r\n\t\tthis.apikey = apikey;\r\n\t\tthis.storage = new AkSupaStorageApi(this);\r\n\t\t// [CHANGE][2026-01-30] hydrate user/session from persisted token (see docs: components/supadb/docs/CHANGELOG.md)\r\n\t\ttry {\r\n\t\t\tthis.hydrateSessionFromStorage();\r\n\t\t} catch (e) {\r\n\t\t\t// ignore\r\n\t\t}\r\n\t}\r\n\r\n\t// [CHANGE][2026-01-30] hydrate user from /auth/v1/user when token exists in storage\r\n\tasync hydrateSessionFromStorage() : Promise {\r\n\t\ttry {\r\n\t\t\tconst token = AkReq.getToken();\r\n\t\t\tif (token == null || token == '') return false;\r\n\t\t\tconst res = await AkReq.request({\r\n\t\t\t\turl: this.baseUrl + '/auth/v1/user',\r\n\t\t\t\tmethod: 'GET',\r\n\t\t\t\theaders: {\r\n\t\t\t\t\tapikey: this.apikey,\r\n\t\t\t\t\tAuthorization: `Bearer ${token}`,\r\n\t\t\t\t\t'Content-Type': 'application/json'\r\n\t\t\t\t} as UTSJSONObject\r\n\t\t\t}, false);\r\n\t\t\tconst status = res.status ?? 0;\r\n\t\t\tif (!(status >= 200 && status < 400)) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\tlet user: UTSJSONObject | null = null;\r\n\t\t\ttry {\r\n\t\t\t\tuser = new UTSJSONObject(res.data);\r\n\t\t\t} catch (e) {\r\n\t\t\t\tuser = null;\r\n\t\t\t}\r\n\t\t\tif (user == null) return false;\r\n\t\t\tthis.user = user;\r\n\t\t\t// 仅补齐最小 session 结构,供 getSession / UI 判断登录态使用\r\n\t\t\tif (this.session == null) {\r\n\t\t\t\tthis.session = {\r\n\t\t\t\t\taccess_token: token,\r\n\t\t\t\t\trefresh_token: AkReq.getRefreshToken() ?? '',\r\n\t\t\t\t\texpires_at: AkReq.getExpiresAt() ?? 0,\r\n\t\t\t\t\tuser: user,\r\n\t\t\t\t\ttoken_type: 'bearer',\r\n\t\t\t\t\texpires_in: 0,\r\n\t\t\t\t\traw: user\r\n\t\t\t\t} as AkSupaSignInResult;\r\n\t\t\t}\r\n\t\t\treturn true;\r\n\t\t} catch (e) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n\r\n\tasync resetPassword(email : string) : Promise {\r\n\t\tconst res = await AkReq.request({\r\n\t\t\turl: this.baseUrl + '/auth/v1/recover',\r\n\t\t\tmethod: 'POST',\r\n\t\t\theaders: {\r\n\t\t\t\tapikey: this.apikey,\r\n\t\t\t\t'Content-Type': 'application/json'\r\n\t\t\t} as UTSJSONObject,\r\n\t\t\tdata: { email } as UTSJSONObject,\r\n\t\t\tcontentType: 'application/json'\r\n\t\t}, false);\r\n\r\n\t\t// Supabase returns 200 when the reset email is sent successfully\r\n\t\treturn res.status == 200;\r\n\t}\r\n\tasync signOut() {\r\n\t\tthis.session = null\r\n\t\tthis.user = null\r\n\t}\r\n\tasync signIn(email : string, password : string) : Promise {\r\n\t\t// 提前检查 apikey 配置是否为占位符,避免发送无效请求导致 401\r\n\t\tif (this.apikey == null || this.apikey.trim() === '' || this.apikey === 'your-anon-key') {\r\n\t\t\tthrow new Error('Supabase 配置错误:请在 ak/config.uts 中设置 SUPA_KEY(当前为占位符)');\r\n\t\t}\r\n\t\tconst res = await AkReq.request({\r\n\t\t\turl: this.baseUrl + '/auth/v1/token?grant_type=password',\r\n\t\t\tmethod: 'POST',\r\n\t\t\theaders: {\r\n\t\t\t\tapikey: this.apikey,\r\n\t\t\t\t'Content-Type': 'application/json'\r\n\t\t\t} as UTSJSONObject,\r\n\t\t\tdata: { email, password } as UTSJSONObject,\r\n\t\t\tcontentType: 'application/json'\r\n\t\t}, false);\r\n\t\t// 如果响应不是 2xx(例如 401),提取后端错误信息并抛出,便于上层显示具体原因\r\n\t\tconst status = res.status ?? 0;\r\n\t\tif (!(status >= 200 && status < 400)) {\r\n\t\t\tlet msg = 'user.login.login_failed';\r\n\t\t\ttry {\r\n\t\t\t\tif (res.data != null) {\r\n\t\t\t\t\tconst obj = new UTSJSONObject(res.data);\r\n\t\t\t\t\tmsg = obj.getString('message') ?? obj.getString('error') ?? obj.getString('msg') ?? obj.getString('description') ?? obj.getString('error_description') ?? msg;\r\n\t\t\t\t}\r\n\t\t\t} catch (e) {\r\n\t\t\t\t// ignore\r\n\t\t\t}\r\n\t\t\tthrow new Error(msg);\r\n\t\t}\r\n\t\t// 解析成功的返回体\r\n\t\tlet data: UTSJSONObject;\r\n\t\ttry {\r\n\t\t\tdata = new UTSJSONObject(res.data);\r\n\t\t} catch (e) {\r\n\t\t\tdata = new UTSJSONObject({});\r\n\t\t}\r\n\t\tconst access_token = data.getString('access_token') ?? '';\r\n\t\tconst refresh_token = data.getString('refresh_token') ?? '';\r\n\t\tconst expires_at = data.getNumber('expires_at') ?? 0;\r\n\t\tconst user = data.getJSON('user');\r\n\t\tAkReq.setToken(access_token, refresh_token, expires_at);\r\n\t\tconst session : AkSupaSignInResult = {\r\n\t\t\taccess_token: access_token,\r\n\t\t\trefresh_token: refresh_token,\r\n\t\t\texpires_at: expires_at,\r\n\t\t\tuser: user,\r\n\t\t\ttoken_type: data.getString('token_type') ?? '',\r\n\t\t\texpires_in: data.getNumber('expires_in') ?? 0,\r\n\t\t\traw: data\r\n\t\t};\r\n\t\tthis.session = session;\r\n\t\tthis.user = user;\r\n\t\treturn session;\r\n\t}\r\n\r\n\t/**\r\n\t * 获取当前 session 和 user\r\n\t */\r\n\tgetSession() : AkSupaSessionInfo {\r\n\t\treturn {\r\n\t\t\tsession: this.session,\r\n\t\t\tuser: this.user\r\n\t\t};\r\n\t}\r\n\r\n\tasync signUp(email : string, password : string) : Promise {\r\n\t\tconst res = await AkReq.request({\r\n\t\t\turl: this.baseUrl + '/auth/v1/signup',\r\n\t\t\tmethod: 'POST',\r\n\t\t\theaders: {\r\n\t\t\t\tapikey: this.apikey,\r\n\t\t\t\t'Content-Type': 'application/json'\r\n\t\t\t} as UTSJSONObject,\r\n\t\t\tdata: { email, password } as UTSJSONObject,\r\n\t\t\tcontentType: 'application/json'\r\n\t\t}, false);\r\n\t\treturn res.data as UTSJSONObject;\r\n\t}\r\n\r\n\r\n\t/**\r\n\t * 查询表数据(GET方式,支持多条件、limit等,filter自动转为supabase风格query)\r\n\t * filter 支持:\r\n\t * { usr_id: { lt: 800 }, name: { ilike: '%foo%' }, status: 'active', age: { gte: 18, lte: 30 } }\r\n\t * 操作符支持 eq, neq, lt, lte, gt, gte, like, ilike, in, is, not, contains, containedBy, range, fts, plfts, phfts, wfts\r\n\t */\r\nasync select(table : string, filter ?: string | null, options ?: AkSupaSelectOptions) : Promise> {\r\n\tlet url = this.baseUrl + '/rest/v1/' + table;\r\n\tlet headers = {\r\n\t\tapikey: this.apikey,\r\n\t\t'Content-Type': 'application/json',\r\n\t\tAuthorization: `Bearer ${AkReq.getToken() ?? ''}`\r\n\t} as UTSJSONObject;\r\n\tlet params : string[] = [];\r\n\tif (options != null) {\r\n\t\tif (options.columns != null && !(options.columns == \"\")) params.push('select=' + encodeURIComponent(options.columns ?? \"\"));\r\n\t\tif (options.limit != null) {\r\n\t\t\tparams.push('limit=' + options.limit);\r\n\t\t\t//__f__('log','at components/supadb/aksupa.uts:820','设置 limit 参数:', options.limit);\r\n\t\t}\r\n\t\tif (options.order != null && !(options.order == \"\")) params.push('order=' + encodeURIComponent(options.order ?? \"\"));\r\n\t\tif (options.rangeFrom != null && options.rangeTo != null) {\r\n\t\t\theaders['Range'] = `${options.rangeFrom}-${options.rangeTo}`;\r\n\t\t\theaders['Range-Unit'] = 'items';\r\n\t\t\t//__f__('log','at components/supadb/aksupa.uts:826','设置 Range 头部:', `${options.rangeFrom}-${options.rangeTo}`);\r\n\t\t}\r\n\r\n\t\t// 向后兼容:支持旧的 getcount 参数\r\n\t\tlet countOption = options.count ?? options.getcount;\r\n\t\tif (countOption != null) {\r\n\t\t\theaders['Prefer'] = `count=${countOption}`;\r\n\t\t}\r\n\t\t// 新增:head 模式支持\r\n\t\tif (options.head == true) {\r\n\t\t\t//__f__('log','at components/supadb/aksupa.uts:836','使用 head 模式,只返回元数据');\r\n\t\t\t// HEAD 请求用于只获取 count,不返回数据\r\n\t\t\tif (headers['Prefer'] != null) {\r\n\t\t\t\theaders['Prefer'] = (headers['Prefer'] as string) + ',return=minimal';\r\n\t\t\t} else {\r\n\t\t\t\theaders['Prefer'] = 'return=minimal';\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (options.single == true) {\r\n\t\t\t//__f__('log','at components/supadb/aksupa.uts:846','使用 single() 模式');\r\n\t\t\tif (headers['Prefer'] != null) {\r\n\t\t\t\theaders['Prefer'] = (headers['Prefer'] as string) + ',return=representation,single-object';\r\n\t\t\t} else {\r\n\t\t\t\theaders['Prefer'] = 'return=representation,single-object';\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// 确保有 select 参数\r\n\t\tif (options.columns == null) {\r\n\t\t\tparams.push('select=*');\r\n\t\t} else if (options.columns == \"\") {\r\n\t\t\tparams.push('select=*');\r\n\t\t}\r\n\t} else {\r\n\t\tparams.push('select=*');\r\n\t}\r\n\t// 直接用 string filter\r\n\tif (filter!=null && filter !== \"\") {\r\n\t\tparams.push(filter!!);\r\n\t}\r\n\tif (params.length > 0) {\r\n\t\turl += '?' + params.join('&');\r\n\t}\r\n\r\n\t//__f__('log','at components/supadb/aksupa.uts:871',url)\r\n\r\n\t// 确定HTTP方法:如果是head模式,使用HEAD方法\r\n\tlet httpMethod: 'GET' | 'HEAD' = 'GET';\r\n\tif (options != null && options.head == true) {\r\n\t\thttpMethod = 'HEAD';\r\n\t\t//__f__('log','at components/supadb/aksupa.uts:877','使用 HEAD 方法进行 count 查询');\r\n\t}\r\n\r\n\tlet reqOptions : AkReqOptions = {\r\n\t\turl,\r\n\t\tmethod: httpMethod,\r\n\t\theaders\r\n\t};\r\n\treturn await this.requestWithAutoRefresh(reqOptions);\r\n}\r\n\r\nasync select_uts(table : string, filter ?: UTSJSONObject | null, options ?: AkSupaSelectOptions) : Promise> {\r\n\tconst filter_str = buildSupabaseFilterQuery(filter)\r\n\treturn this.select(table,filter_str,options)\r\n}\r\n\t/**\r\n\t * 插入表数据\r\n\t * @param table 表名\r\n\t * @param row 插入对象\r\n\t * @returns 插入结果\r\n\t */\r\n\tasync insert(table : string, row : UTSJSONObject | Array) : Promise> {\r\n\t\tconst url = this.baseUrl + '/rest/v1/' + table;\r\n\t\tconst headers = {\r\n\t\t\tapikey: this.apikey,\r\n\t\t\t'Content-Type': 'application/json',\r\n\t\t\tAuthorization: `Bearer ${AkReq.getToken() ?? ''}`,\r\n\t\t\tPrefer: 'return=representation'\r\n\t\t} as UTSJSONObject;\r\n\r\n\t\t// 如果是数组,直接传递;如果是单个对象,也直接传递\r\n\t\t// Supabase REST API 原生支持两种格式\r\n\t\tlet reqOptions : AkReqOptions = {\r\n\t\t\turl,\r\n\t\t\tmethod: 'POST',\r\n\t\t\theaders,\r\n\t\t\tdata: row, // 可以是单个对象或数组\r\n\t\t\tcontentType: 'application/json'\r\n\t\t};\r\n\t\treturn await this.requestWithAutoRefresh(reqOptions);\r\n\t}\r\n\r\n\t/**\r\n\t * 更新表数据\r\n\t * @param table 表名\r\n\t * @param filter 过滤条件对象\r\n\t * @param values 更新内容对象\r\n\t * @returns 更新结果\r\n\t */\r\nasync update(table : string, filter : string | null, values : UTSJSONObject) : Promise> {\r\n\tlet url = this.baseUrl + '/rest/v1/' + table;\r\n\tif (filter!=null && filter !== \"\") {\r\n\t\turl += '?' + filter;\r\n\t}\r\n\tconst headers = {\r\n\t\tapikey: this.apikey,\r\n\t\t'Content-Type': 'application/json',\r\n\t\tAuthorization: `Bearer ${AkReq.getToken() ?? ''}`,\r\n\t\tPrefer: 'return=representation'\r\n\t} as UTSJSONObject;\r\n\tlet reqOptions : AkReqOptions = {\r\n\t\turl,\r\n\t\tmethod: 'PATCH',\r\n\t\theaders,\r\n\t\tdata: values,\r\n\t\tcontentType: 'application/json'\r\n\t};\r\n\treturn await this.requestWithAutoRefresh(reqOptions);\r\n}\r\n\r\n\t/**\r\n\t * 删除表数据\r\n\t * @param table 表名\r\n\t * @param filter 过滤条件对象\r\n\t * @returns 删除结果\r\n\t */\r\nasync delete(table : string, filter : string | null) : Promise> {\r\n\tlet url = this.baseUrl + '/rest/v1/' + table;\r\n\tif (filter!=null && filter !== \"\") {\r\n\t\turl += '?' + filter;\r\n\t}\r\n\tconst headers = {\r\n\t\tapikey: this.apikey,\r\n\t\t'Content-Type': 'application/json',\r\n\t\tAuthorization: `Bearer ${AkReq.getToken() ?? ''}`,\r\n\t\tPrefer: 'return=representation'\r\n\t} as UTSJSONObject;\r\n\tlet reqOptions : AkReqOptions = {\r\n\t\turl,\r\n\t\tmethod: 'DELETE',\r\n\t\theaders,\r\n\t\tcontentType: 'application/json'\r\n\t};\r\n\treturn await this.requestWithAutoRefresh(reqOptions);\r\n}\r\n\r\n\t/**\r\n\t * 调用 Supabase/PostgREST RPC (function)\r\n\t * @param functionName 函数名\r\n\t * @param params 参数对象\r\n\t * @returns AkReqResponse\r\n\t */\r\n\tasync rpc(functionName : string, params ?: UTSJSONObject) : Promise> {\r\n\t\tconst url = `${this.baseUrl}/rest/v1/rpc/${functionName}`;\r\n\t\tconst headers = {\r\n\t\t\tapikey: this.apikey,\r\n\t\t\t'Content-Type': 'application/json',\r\n\t\t\tAuthorization: `Bearer ${AkReq.getToken() ?? ''}`\r\n\t\t} as UTSJSONObject;\r\n\t\tlet reqOptions : AkReqOptions = {\r\n\t\t\turl,\r\n\t\t\tmethod: 'POST',\r\n\t\t\theaders,\r\n\t\t\tdata: params ?? {},\r\n\t\t\tcontentType: 'application/json'\r\n\t\t};\r\n\t\treturn await this.requestWithAutoRefresh(reqOptions);\r\n\t}\r\n\t/**\r\n\t * 兼容 supabase-js 风格\r\n\t * @param tableName 表名\r\n\t */\r\n\tfrom(tableName : string) : AkSupaQueryBuilder {\r\n\t\treturn new AkSupaQueryBuilder(this, tableName);\r\n\t}\r\n\r\n /**\r\n * 创建实时订阅通道 (兼容 Supabase Realtime 接口,目前使用轮询模拟)\r\n * @param topic 通道名称,如 public:table\r\n */\r\n channel(topic: string): AkSupaRealtimeChannel {\r\n return new AkSupaRealtimeChannel(this, topic);\r\n }\r\n \r\n /**\r\n * 移除通道\r\n */\r\n removeChannel(channel: AkSupaRealtimeChannel): Promise {\r\n channel.unsubscribe();\r\n return Promise.resolve('ok');\r\n }\r\n\t// AkSupa类内新增:自动刷新session\r\n\tasync refreshSession() : Promise {\r\n\t\tif (this.session == null || this.session?.refresh_token == null) return false;\r\n\t\ttry {\r\n\t\t\tconst res = await AkReq.request({\r\n\t\t\t\turl: this.baseUrl + '/auth/v1/token?grant_type=refresh_token',\r\n\t\t\t\tmethod: 'POST',\r\n\t\t\t\theaders: {\r\n\t\t\t\t\tapikey: this.apikey,\r\n\t\t\t\t\t'Content-Type': 'application/json'\r\n\t\t\t\t} as UTSJSONObject,\r\n\t\t\t\tdata: { refresh_token: this.session?.refresh_token } as UTSJSONObject,\r\n\t\t\t\tcontentType: 'application/json'\r\n\t\t\t}, false);\r\n\t\t\tif (res.status == 200 && (res.data != null)) {\r\n\t\t\t\tconst data = res.data as UTSJSONObject;\r\n\t\t\t\tconst access_token = data.getString('access_token') ?? '';\r\n\t\t\t\tconst refresh_token = data.getString('refresh_token') ?? '';\r\n\t\t\t\tconst expires_at = data.getNumber('expires_at') ?? 0;\r\n\t\t\t\tconst user = data.getJSON('user');\r\n\t\t\t\tthis.session = {\r\n\t\t\t\t\taccess_token,\r\n\t\t\t\t\trefresh_token,\r\n\t\t\t\t\texpires_at,\r\n\t\t\t\t\tuser,\r\n\t\t\t\t\ttoken_type: data.getString('token_type') ?? '',\r\n\t\t\t\t\texpires_in: data.getNumber('expires_in') ?? 0,\r\n\t\t\t\t\traw: data\r\n\t\t\t\t};\r\n\t\t\t\tthis.user = user;\r\n\t\t\t\t// 更新本地token\r\n\t\t\t\tAkReq.setToken(access_token, refresh_token, expires_at);\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t\treturn false;\r\n\t\t} catch (e) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n\r\n\t// AkSupa类内新增:自动刷新封装\r\n\tasync requestWithAutoRefresh(reqOptions : AkReqOptions, isRetry = false) : Promise> {\r\n\t\tlet res = await AkReq.request(reqOptions, false);\r\n\t\t// JWT过期:Supabase风格\r\n\t\tconst isJwtExpired = (res.status == 401); //res != null && res.data != null && typeof res.data == 'object' && (res.data as UTSJSONObject)?.getString('code') == 'PGRST301';\r\n\t\t// 401未授权\r\n\t\tconst isUnauthorized = (res.status == 401);\r\n\t\tif ((isJwtExpired || isUnauthorized) && !isRetry) {\r\n\t\t\tconst ok = await this.refreshSession();\r\n\t\t\tif (ok) {\r\n\t\t\t\tlet headers = reqOptions.headers\r\n\t\t\t\tif (headers == null) {\r\n\t\t\t\t\theaders = new UTSJSONObject()\r\n\t\t\t\t}\r\n\t\t\t\tif (typeof headers.set == 'function') {\r\n\t\t\t\t\theaders.set('Authorization', `Bearer ${AkReq.getToken() ?? ''}`)\r\n\t\t\t\t\treqOptions.headers = headers\r\n\t\t\t\t}\r\n\r\n\t\t\t\tres = await AkReq.request(reqOptions, false);\r\n\t\t\t} else {\r\n\t\t\t\tuni.removeStorageSync('user_id');\r\n\t\t\t\tuni.removeStorageSync('token');\r\n\r\n\t\t\t\t//uni.reLaunch({ url: '/pages/user/login' });\r\n __f__('log','at components/supadb/aksupa.uts:1083','登录已过期,请重新登录');\r\n\t\t\t\tthrow toUniError('登录已过期,请重新登录', '用户认证失败');\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn res;\r\n\t}\r\n}\r\n\r\n// 工具函数:将 UTSJSONObject filter 转为 Supabase/PostgREST 查询字符串\r\nfunction buildSupabaseFilterQuery(filter : UTSJSONObject | null) : string {\r\n\t//__f__('log','at components/supadb/aksupa.uts:1093',filter)\r\n\tif (filter == null) return \"\";\r\n\t// 类型保护:如果不是 UTSJSONObject,自动包裹\r\n\tif (typeof filter.get !== 'function') {\r\n\t\ttry {\r\n\t\t\tfilter = new UTSJSONObject(filter as any)\r\n\t\t} catch (e) {\r\n\t\t\t__f__('warn','at components/supadb/aksupa.uts:1100','filter 不是 UTSJSONObject,且无法转换', filter)\r\n\t\t\treturn ''\r\n\t\t}\r\n\t}\r\n\tconst params : string[] = [];\r\n\tconst keys : string[] = UTSJSONObject.keys(filter);\r\n\tfor (let i = 0; i < keys.length; i++) {\r\n\t\tconst k = keys[i];\r\n\t\tconst v = filter.get(k);\r\n\t\tif (k == 'or' && typeof v == 'string') {\r\n\t\t\tparams.push(`or=(${v})`);\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tif (v != null && typeof v == 'object' && typeof (v as UTSJSONObject).get == 'function') {\r\n\t\t\tconst vObj = v as UTSJSONObject;\r\n\t\t\tconst opKeys = UTSJSONObject.keys(vObj);\r\n\t\t\tfor (let j = 0; j < opKeys.length; j++) {\r\n\t\t\t\tconst op = opKeys[j];\r\n\t\t\t\tconst opVal = vObj.get(op);\r\n\t\t\t\tif ((op == 'in' || op == 'not.in') && Array.isArray(opVal)) {\r\n\t\t\t\t\tparams.push(`${k}=${op}.(${opVal.map(x => typeof x == 'object' ? encodeURIComponent(JSON.stringify(x)) : encodeURIComponent(x.toString())).join(',')})`);\r\n\t\t\t\t} else if (op == 'is' && (opVal == null || opVal == 'null')) {\r\n\t\t\t\t\tparams.push(`${k}=is.null`);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tconst opvalstr : string = (typeof opVal == 'object') ? JSON.stringify(opVal) : (opVal as string);\r\n\t\t\t\t\tparams.push(`${k}=${op}.${encodeURIComponent(opvalstr)}`);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else if (v != null && typeof v == 'object') {\r\n\t\t\tconst vObj = v as UTSJSONObject;\r\n\t\t\tconst opKeys = UTSJSONObject.keys(vObj);\r\n\t\t\tfor (let j = 0; j < opKeys.length; j++) {\r\n\t\t\t\tconst op = opKeys[j];\r\n\t\t\t\tconst opVal = vObj.get(op);\r\n\t\t\t\tparams.push(`${k}=${op}.${encodeURIComponent(!(opVal == null) ? (typeof opVal == 'object' ? JSON.stringify(opVal) : opVal.toString()) : '')}`);\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tparams.push(`${k}=eq.${encodeURIComponent(!(v == null) ? v.toString() : '')}`);\r\n\t\t}\r\n\t}\r\n\treturn params.join('&');\r\n}\r\n\r\n/**\r\n * 创建 Supabase 客户端实例\r\n * @param url 项目 URL\r\n * @param key 项目匿名密钥 (Anon Key)\r\n */\r\nexport function createClient(url : string, key : string) : AkSupa {\r\n\treturn new AkSupa(url, key);\r\n}\r\n\r\n// 模拟 Realtime Channel 类 (Polling Fallback)\r\nexport class AkSupaRealtimeChannel {\r\n private _supa: AkSupa;\r\n private _topic: string;\r\n private _timer: number = 0;\r\n private _callback: ((payload: any) => void) | null = null;\r\n private _table: string = '';\r\n private _lastTime: string = new Date().toISOString(); \r\n private _isSubscribed: boolean = false;\r\n\r\n constructor(supa: AkSupa, topic: string) {\r\n this._supa = supa;\r\n this._topic = topic;\r\n }\r\n\r\n // 绑定事件 (仅支持 postgres_changes INSERT)\r\n on(type: string, filter: UTSJSONObject, callback: (payload: any) => void): AkSupaRealtimeChannel {\r\n // 解析 table\r\n const table = filter.getString('table');\r\n if (table != null) {\r\n this._table = table;\r\n }\r\n this._callback = callback;\r\n return this;\r\n }\r\n\r\n // 开始订阅\r\n subscribe(callback?: (status: string, err: any | null) => void): AkSupaRealtimeChannel {\r\n if (this._isSubscribed) return this;\r\n this._isSubscribed = true;\r\n \r\n // 初始回调\r\n if (callback != null) {\r\n callback('SUBSCRIBED', null);\r\n }\r\n\r\n // 如果没有指定 table,无法轮询\r\n if (this._table == '') {\r\n __f__('warn','at components/supadb/aksupa.uts:1190','Realtime check: No table specified for polling.');\r\n return this;\r\n }\r\n\r\n // 开始轮询 (每3秒)\r\n this._timer = setInterval(() => {\r\n this._checkUpdates();\r\n }, 3000);\r\n\r\n return this;\r\n }\r\n\r\n // 停止订阅\r\n unsubscribe() {\r\n if (this._timer > 0) {\r\n clearInterval(this._timer);\r\n this._timer = 0;\r\n }\r\n this._isSubscribed = false;\r\n }\r\n\r\n // 检查更新\r\n private async _checkUpdates() {\r\n if (!this._isSubscribed || this._table == '') return;\r\n \r\n try {\r\n const now = new Date().toISOString();\r\n \r\n const res = await this._supa\r\n .from(this._table)\r\n .select('*')\r\n .gt('created_at', this._lastTime)\r\n .order('created_at', { ascending: true })\r\n .execute();\r\n \r\n if (res.error == null && res.data != null) {\r\n let list: any[] = [];\r\n if (Array.isArray(res.data)) {\r\n list = res.data as any[];\r\n }\r\n \r\n if (list.length > 0) {\r\n // 更新最后时间\r\n const lastItem = list[list.length - 1];\r\n let lastTimeStr: string | null = null;\r\n \r\n if (lastItem instanceof UTSJSONObject) {\r\n lastTimeStr = lastItem.getString('created_at');\r\n } else {\r\n // 尝试转 json\r\n const j = JSON.parse(JSON.stringify(lastItem)) as UTSJSONObject;\r\n lastTimeStr = j.getString('created_at');\r\n }\r\n \r\n if (lastTimeStr != null) {\r\n this._lastTime = lastTimeStr;\r\n } else {\r\n this._lastTime = now;\r\n }\r\n\r\n // 触发回调\r\n if (this._callback != null) {\r\n // 模拟 Realtime payload\r\n list.forEach(item => {\r\n const payload = {\r\n new: item,\r\n eventType: 'INSERT',\r\n old: null\r\n };\r\n this._callback?.(payload);\r\n });\r\n }\r\n }\r\n }\r\n } catch (e) {\r\n __f__('error','at components/supadb/aksupa.uts:1265','Realtime polling error:', e);\r\n }\r\n }\r\n}\r\n\r\nexport default AkSupa;\r\n","// /components/supadb/aksupainstance.uts\r\nimport { createClient } from './aksupa.uts'\r\nimport { SUPA_URL, SUPA_KEY } from '@/ak/config.uts'\r\n\r\n// 创建单一真实的 Supabase 客户端实例 (使用 config.uts 配置)\r\n// Create single source of truth client using config\r\nconst supaInstance = createClient(SUPA_URL, SUPA_KEY)\r\n\r\n// 导出默认实例 (供 login.uvue 等使用)\r\nexport default supaInstance\r\n\r\n// 导出命名实例 'supabase' (供 store.uts 使用)\r\nexport const supabase = supaInstance\r\n\r\n// 导出 isSupabaseReady 状态\r\nexport const isSupabaseReady = true\r\n\r\n// 兼容 ensureSupabaseReady\r\nexport async function ensureSupabaseReady() {\r\n return true\r\n}\r\n\r\n// 检查连接状态的函数\r\nexport function checkConnection() {\r\n return Promise.resolve(true)\r\n}\r\n\r\n// 兼容 supaReady Promise\r\nexport const supaReady = Promise.resolve(true)\r\n\r\n// 如果有其他需要导出的函数,可以这样导出:\r\nexport function initializeSupabase(url: string, key: string) {\r\n return createClient(url, key)\r\n}\r\n","// 电商商城系统类型定义 - UTS Android 兼容\r\n\r\n// 用户类型\r\nexport type UserType = {\r\n\tid: string\r\n\tphone: string\r\n\temail: string | null\r\n\tnickname: string | null\r\n\tavatar_url: string | null\r\n\tgender: number\r\n\tuser_type: number\r\n\tstatus: number\r\n\tcreated_at: string\r\n}\r\n\r\n// 商城用户扩展信息类型\r\nexport type MallUserProfileType = {\r\n\tid: string\r\n\tuser_id: string\r\n\tuser_type: number\r\n\tstatus: number\r\n\treal_name: string | null\r\n\tid_card: string | null\r\n\tcredit_score: number\r\n\tmall_role: string\r\n\tverification_status: number\r\n\tverification_data: UTSJSONObject | null\r\n\tbusiness_license: string | null\r\n\tshop_category: string | null\r\n\tservice_areas: UTSJSONObject | null\r\n\temergency_contact: string | null\r\n\tpreferences: UTSJSONObject | null\r\n\tcreated_at: string\r\n\tupdated_at: string\r\n}\r\n\r\n// 用户地址类型\r\nexport type UserAddressType = {\r\n\tid: string\r\n\tuser_id: string\r\n\treceiver_name: string\r\n\treceiver_phone: string\r\n\tprovince: string\r\n\tcity: string\r\n\tdistrict: string\r\n\taddress_detail: string\r\n\tpostal_code: string | null\r\n\tis_default: boolean\r\n\tlabel: string | null\r\n\tcoordinates: string | null\r\n\tdelivery_instructions: string | null\r\n\tbusiness_hours: string | null\r\n\tstatus: number\r\n\tcreated_at: string\r\n\tupdated_at: string\r\n}\r\n\r\n// 商家类型\r\nexport type MerchantType = {\r\n\tid: string\r\n\tuser_id: string\r\n\tshop_name: string\r\n\tshop_logo: string | null\r\n\tshop_banner: string | null\r\n\tshop_description: string | null\r\n\tcontact_name: string\r\n\tcontact_phone: string\r\n\tshop_status: number\r\n\trating: number\r\n\ttotal_sales: number\r\n\tcreated_at: string\r\n}\r\n\r\n// 商品类型\r\nexport type ProductType = {\r\n\tid: string\r\n\tmerchant_id: string\r\n\tcategory_id: string\r\n\tname: string\r\n\tdescription: string | null\r\n\timages: Array\r\n\tprice: number\r\n\toriginal_price: number | null\r\n\tstock: number\r\n\tsales: number\r\n\tstatus: number\r\n\tcreated_at: string\r\n\t// 药品相关字段\r\n\tspecification?: string | null // 规格说明\r\n\tusage?: string | null // 用法用量\r\n\tside_effects?: string | null // 副作用\r\n\tprecautions?: string | null // 注意事项\r\n\texpiry_date?: string | null // 有效期\r\n\tstorage_conditions?: string | null // 储存条件\r\n\tapproval_number?: string | null // 批准文号\r\n\ttags?: Array | null // 商品标签\r\n}\r\n\r\n// 商品SKU类型\r\nexport type ProductSkuType = {\r\n\tid: string\r\n\tproduct_id: string\r\n\tsku_code: string\r\n\tspecifications: UTSJSONObject | null\r\n\tprice: number\r\n\tstock: number\r\n\timage_url: string | null\r\n\tstatus: number\r\n}\r\n\r\n// 购物车类型\r\nexport type CartItemType = {\r\n\tid: string\r\n\tuser_id: string\r\n\tproduct_id: string\r\n\tsku_id: string\r\n\tquantity: number\r\n\tselected: boolean\r\n\tproduct: ProductType | null\r\n\tsku: ProductSkuType | null\r\n}\r\n\r\n// 订单类型\r\nexport type OrderType = {\r\n\tid: string\r\n\torder_no: string\r\n\tuser_id: string\r\n\tmerchant_id: string\r\n\tstatus: number\r\n\ttotal_amount: number\r\n\tdiscount_amount: number\r\n\tdelivery_fee: number\r\n\tactual_amount: number\r\n\tpayment_method: number | null\r\n\tpayment_status: number\r\n\tdelivery_address: UTSJSONObject\r\n\tcreated_at: string\r\n}\r\n\r\n// 订单商品类型\r\nexport type OrderItemType = {\r\n\tid: string\r\n\torder_id: string\r\n\tproduct_id: string\r\n\tsku_id: string\r\n\tproduct_name: string\r\n\tsku_specifications: UTSJSONObject | null\r\n\tprice: number\r\n\tquantity: number\r\n\ttotal_amount: number\r\n}\r\n\r\n// 配送员类型\r\nexport type DeliveryDriverType = {\r\n\tid: string\r\n\tuser_id: string\r\n\treal_name: string\r\n\tid_card: string\r\n\tdriver_license: string | null\r\n\tvehicle_type: number\r\n\tvehicle_number: string | null\r\n\twork_status: number\r\n\tcurrent_location: UTSJSONObject | null\r\n\tservice_areas: Array\r\n\trating: number\r\n\ttotal_orders: number\r\n\tauth_status: number\r\n\tcreated_at: string\r\n\tupdated_at: string\r\n}\r\n\r\n// 配送任务类型\r\nexport type DeliveryTaskType = {\r\n\tid: string\r\n\torder_id: string\r\n\tdriver_id: string | null\r\n\tpickup_address: UTSJSONObject\r\n\tdelivery_address: UTSJSONObject\r\n\tdistance: number | null\r\n\testimated_time: number | null\r\n\tdelivery_fee: number\r\n\tstatus: number\r\n\tpickup_time: string | null\r\n\tdelivered_time: string | null\r\n\tdelivery_code: string | null\r\n\tremark: string | null\r\n\tcreated_at: string\r\n\tupdated_at: string\r\n}\r\n\r\n// 优惠券模板类型\r\nexport type CouponTemplateType = {\r\n\tid: string\r\n\tname: string\r\n\tdescription: string | null\r\n\tcoupon_type: number\r\n\tdiscount_type: number\r\n\tdiscount_value: number\r\n\tmin_order_amount: number\r\n\tmax_discount_amount: number | null\r\n\ttotal_quantity: number | null\r\n\tper_user_limit: number\r\n\tusage_limit: number\r\n\tmerchant_id: string | null\r\n\tcategory_ids: Array\r\n\tproduct_ids: Array\r\n\tuser_type_limit: number | null\r\n\tstart_time: string\r\n\tend_time: string\r\n\tstatus: number\r\n\tcreated_at: string\r\n}\r\n\r\n// 用户优惠券类型\r\nexport type UserCouponType = {\r\n\tid: string\r\n\tuser_id: string\r\n\ttemplate_id: string\r\n\tcoupon_code: string\r\n\tstatus: number\r\n\tused_at: string | null\r\n\torder_id: string | null\r\n\treceived_at: string\r\n\texpire_at: string\r\n}\r\n\r\n// 分页数据类型\r\nexport type PageDataType = {\r\n\tdata: Array\r\n\ttotal: number\r\n\tpage: number\r\n\tpageSize: number\r\n\thasMore: boolean\r\n}\r\n\r\n// API响应类型\r\nexport type ApiResponseType = {\r\n\tsuccess: boolean\r\n\tdata: T | null\r\n\tmessage: string\r\n\tcode: number\r\n}\r\n\r\n// 订单状态枚举\r\nexport const ORDER_STATUS = {\r\n\tPENDING_PAYMENT: 1,\r\n\tPAID: 2,\r\n\tSHIPPED: 3,\r\n\tDELIVERED: 4,\r\n\tCOMPLETED: 5,\r\n\tCANCELLED: 6,\r\n\tREFUNDING: 7,\r\n\tREFUNDED: 8\r\n}\r\n\r\n// 优惠券类型枚举\r\nexport const COUPON_TYPE = {\r\n\tDISCOUNT_AMOUNT: 1, // 满减券\r\n\tDISCOUNT_PERCENT: 2, // 折扣券\r\n\tFREE_SHIPPING: 3, // 免运费券\r\n\tNEWBIE: 4, // 新人券\r\n\tMEMBER: 5, // 会员券\r\n\tCATEGORY: 6, // 品类券\r\n\tMERCHANT: 7, // 商家券\r\n\tLIMITED_TIME: 8 // 限时券\r\n}\r\n\r\n// 支付方式枚举\r\nexport const PAYMENT_METHOD = {\r\n\tWECHAT: 1,\r\n\tALIPAY: 2,\r\n\tUNIONPAY: 3,\r\n\tBALANCE: 4\r\n}\r\n\r\n// 配送状态枚举\r\nexport const DELIVERY_STATUS = {\r\n\tPENDING: 1,\r\n\tASSIGNED: 2,\r\n\tPICKED_UP: 3,\r\n\tIN_TRANSIT: 4,\r\n\tDELIVERED: 5,\r\n\tFAILED: 6\r\n}\r\n\r\n// 用户类型枚举\r\nexport const MALL_USER_TYPE = {\r\n\tCONSUMER: 1, // 消费者\r\n\tMERCHANT: 2, // 商家\r\n\tDELIVERY: 3, // 配送员\r\n\tSERVICE: 4, // 客服\r\n\tADMIN: 5 // 管理员\r\n}\r\n\r\n// 用户状态枚举\r\nexport const USER_STATUS = {\r\n\tNORMAL: 1, // 正常\r\n\tFROZEN: 2, // 冻结\r\n\tCANCELLED: 3, // 注销\r\n\tPENDING: 4 // 待审核\r\n} as const\r\n\r\n// 认证状态枚举\r\nexport const VERIFICATION_STATUS = {\r\n\tUNVERIFIED: 0, // 未认证\r\n\tVERIFIED: 1, // 已认证\r\n\tFAILED: 2 // 认证失败\r\n}\r\n\r\n// 地址标签枚举\r\nexport const ADDRESS_LABEL = {\r\n\tHOME: 'home', // 家\r\n\tOFFICE: 'office', // 公司\r\n\tSCHOOL: 'school', // 学校\r\n\tOTHER: 'other' // 其他\r\n}\r\n\r\n// 收藏类型枚举\r\nexport const FAVORITE_TYPE = {\r\n\tPRODUCT: 'product', // 商品\r\n\tSHOP: 'shop' // 店铺\r\n}\r\n\r\n// =========================\r\n// 订阅相关类型与枚举\r\n// =========================\r\n\r\n// 订阅周期枚举\r\nexport const SUBSCRIPTION_PERIOD = {\r\n\tMONTHLY: 'monthly',\r\n\tYEARLY: 'yearly'\r\n}\r\n\r\n// 订阅状态枚举\r\nexport const SUBSCRIPTION_STATUS = {\r\n\tTRIAL: 'trial',\r\n\tACTIVE: 'active',\r\n\tPAST_DUE: 'past_due',\r\n\tCANCELED: 'canceled',\r\n\tEXPIRED: 'expired'\r\n}\r\n\r\n// 软件订阅方案类型\r\nexport type SubscriptionPlanType = {\r\n\tid: string\r\n\tplan_code: string\r\n\tname: string\r\n\tdescription: string | null\r\n\tfeatures: UTSJSONObject | null // { featureKey: description }\r\n\tprice: number // 单位:元(或分,取决于后端;前端以显示为准)\r\n\tcurrency: string | null // 'CNY' | 'USD' ...\r\n\tbilling_period: string // 'monthly' | 'yearly'\r\n\ttrial_days: number | null\r\n\tis_active: boolean\r\n\tsort_order?: number | null\r\n\tcreated_at?: string\r\n\tupdated_at?: string\r\n}\r\n\r\n// 用户订阅记录类型\r\nexport type UserSubscriptionType = {\r\n\tid: string\r\n\tuser_id: string\r\n\tplan_id: string\r\n\tstatus: string\r\n\tstart_date: string\r\n\tend_date: string | null\r\n\tnext_billing_date: string | null\r\n\tauto_renew: boolean\r\n\tcancel_at_period_end?: boolean | null\r\n\tmetadata?: UTSJSONObject | null\r\n\tcreated_at?: string\r\n\tupdated_at?: string\r\n}\r\n\r\n// 用户基础信息类型 (兼容 pages/user/types.uts)\r\nexport type UserProfile = {\r\n id?: string;\r\n username: string;\r\n email: string;\r\n gender?: string;\r\n birthday?: string;\r\n height_cm?: number;\r\n weight_kg?: number;\r\n bio?: string;\r\n avatar_url?: string;\r\n preferred_language?: string;\r\n role?: string;\r\n school_id?: string;\r\n grade_id?: string;\r\n class_id?: string;\r\n created_at?: string;\r\n updated_at?: string;\r\n}\r\n\r\nexport type UserStats = {\r\n trainings: number;\r\n points: number;\r\n streak: number;\r\n}\r\n\r\n// 足迹项类型\r\nexport type FootprintItemType = {\r\n id: string\r\n name: string\r\n price: number\r\n original_price: number | null\r\n image: string\r\n sales: number\r\n shopId: string\r\n shopName: string\r\n viewTime: number\r\n}\r\n","// 设备信息类型\r\nexport type DeviceInfo = {\r\n\tid: string\r\n\tdevice_name?: string\r\n\tstatus?: string // 'online' | 'offline' | 其他状态\r\n\tuser_id?: string\r\n\t// 可根据实际需求添加更多字段\r\n}\r\n\r\n// 设备查询参数类型\r\nexport type DeviceParams = {\r\n\tuser_id: string\r\n\t// 可根据实际需求添加更多查询参数\r\n}\r\n","import supabase, { supaReady } from '@/components/supadb/aksupainstance.uts'\r\nimport type { UserProfile } from '@/types/mall-types.uts'\r\n\r\n/**\r\n * 确保用户资料存在,如果不存在则创建基础资料\r\n * @param sessionUser 会话用户对象 (UTSJSONObject)\r\n * @returns 创建的用户资料,如果创建失败则返回 null\r\n */\r\nexport async function ensureUserProfile(sessionUser: UTSJSONObject): Promise {\r\n\ttry {\r\n\t\tawait supaReady\r\n \r\n\t\t// 从 sessionUser 中获取用户ID和邮箱\r\n\t\tconst userId = sessionUser.getString('id')\r\n\t\tconst email = sessionUser.getString('email') ?? ''\r\n\t\t\r\n\t\tif (userId == null || userId === '') {\r\n\t\t\t__f__('error','at utils/sapi.uts:18','无法获取用户ID')\r\n\t\t\treturn null\r\n\t\t}\r\n\t\t\r\n\t\t// 检查用户是否已存在(ak_users 通过 auth_id 关联 auth.users.id)\r\n\t\tconst checkRes = await supabase.from('ak_users')\r\n\t\t\t.select('*', {})\r\n\t\t\t.eq('auth_id', userId)\r\n\t\t\t.single()\r\n\t\t\t.execute()\r\n\t\t\r\n\t\t__f__('log','at utils/sapi.uts:29','ensureUserProfile check ak_users:', {\r\n\t\t\tstatus: checkRes.status,\r\n\t\t\thasData: checkRes.data != null\r\n\t\t})\r\n\t\t\r\n\t\tif (checkRes.status >= 200 && checkRes.status < 300 && checkRes.data != null) {\r\n\t\t\t// 用户已存在,返回现有资料(H5 下 checkRes.data 可能是 plain object,不一定是 UTSJSONObject)\r\n\t\t\tconst data = checkRes.data\r\n\t\t\tlet existingUser: UTSJSONObject\r\n\t\t\tif (data instanceof UTSJSONObject) {\r\n\t\t\t\texistingUser = data\r\n\t\t\t} else {\r\n\t\t\t\texistingUser = new UTSJSONObject(data)\r\n\t\t\t}\r\n\t\t\treturn {\r\n\t\t\t\tid: existingUser.getString('id') ?? '',\r\n\t\t\t\tusername: existingUser.getString('username') ?? '',\r\n\t\t\t\temail: existingUser.getString('email') ?? email,\r\n\t\t\t\tgender: existingUser.getString('gender'),\r\n\t\t\t\tbirthday: existingUser.getString('birthday'),\r\n\t\t\t\theight_cm: existingUser.getNumber('height_cm'),\r\n\t\t\t\tweight_kg: existingUser.getNumber('weight_kg'),\r\n\t\t\t\tbio: existingUser.getString('bio'),\r\n\t\t\t\tavatar_url: existingUser.getString('avatar_url'),\r\n\t\t\t\tpreferred_language: existingUser.getString('preferred_language'),\r\n\t\t\t\trole: existingUser.getString('role') ?? 'consumer',\r\n\t\t\t\tcreated_at: existingUser.getString('created_at'),\r\n\t\t\t\tupdated_at: existingUser.getString('updated_at')\r\n\t\t\t} as UserProfile\r\n\t\t}\r\n\t\t\r\n\t\t// 用户不存在,创建新用户资料\r\n\t\tconst newUserData = new UTSJSONObject()\r\n\t\tnewUserData.set('id', userId)\r\n\t\tnewUserData.set('email', email)\r\n\t\tnewUserData.set('username', email.split('@')[0] ?? 'user') // 默认用户名为邮箱前缀\r\n\t\t\r\n\t\tconst insertRes = await supabase.from('ak_users')\r\n\t\t\t.insert(newUserData)\r\n\t\t\t.select('*', {})\r\n\t\t\t.single()\r\n\t\t\t.execute()\r\n\t\t\r\n\t\t__f__('log','at utils/sapi.uts:72','ensureUserProfile insert ak_users status:', insertRes.status)\r\n\t\t\r\n\t\tif (insertRes.status >= 200 && insertRes.status < 300 && insertRes.data != null) {\r\n\t\t\tconst rawData = insertRes.data\r\n\t\t\tconst newUser = (rawData instanceof UTSJSONObject)\r\n\t\t\t\t? (rawData as UTSJSONObject)\r\n\t\t\t\t: new UTSJSONObject(rawData)\r\n\t\t\treturn {\r\n\t\t\t\tid: newUser.getString('id') ?? '',\r\n\t\t\t\tusername: newUser.getString('username') ?? '',\r\n\t\t\t\temail: newUser.getString('email') ?? email,\r\n\t\t\t\tgender: newUser.getString('gender'),\r\n\t\t\t\tbirthday: newUser.getString('birthday'),\r\n\t\t\t\theight_cm: newUser.getNumber('height_cm'),\r\n\t\t\t\tweight_kg: newUser.getNumber('weight_kg'),\r\n\t\t\t\tbio: newUser.getString('bio'),\r\n\t\t\t\tavatar_url: newUser.getString('avatar_url'),\r\n\t\t\t\tpreferred_language: newUser.getString('preferred_language'),\r\n\t\t\t\trole: newUser.getString('role') ?? 'consumer',\r\n\t\t\t\tcreated_at: newUser.getString('created_at'),\r\n\t\t\t\tupdated_at: newUser.getString('updated_at')\r\n\t\t\t} as UserProfile\r\n\t\t} else {\r\n\t\t\t__f__('error','at utils/sapi.uts:95','创建用户资料失败:', insertRes.status)\r\n\t\t\treturn null\r\n\t\t}\r\n\t} catch (error) {\r\n\t\t__f__('error','at utils/sapi.uts:99','ensureUserProfile 异常:', error)\r\n\t\treturn null\r\n\t}\r\n}\r\n","import supa, { supaReady } from '@/components/supadb/aksupainstance.uts'\r\nimport type { UserProfile, UserStats } from '@/types/mall-types.uts'\r\nimport type { DeviceInfo } from '@/pages/sense/types.uts'\r\nimport { SenseDataService, type DeviceParams } from '@/pages/sense/senseDataService.uts'\r\nimport { reactive } from 'vue'\r\nimport { ensureUserProfile } from './sapi.uts'\r\n\r\n// 设备状态类型\r\nexport type DeviceState = {\r\n\tdevices : Array\r\n\tcurrentDevice : DeviceInfo | null\r\n\tisLoading : boolean\r\n\tlastUpdated : number | null\r\n}\r\n\r\n//定义一个大写的State类型\r\nexport type State = {\r\n\tglobalNum : number\r\n\tuserProfile ?: UserProfile\r\n\tisLoggedIn : boolean // 新增字段\r\n\tdeviceState : DeviceState // 新增设备状态\r\n\t// 如有需要,可增加更多属性\r\n}\r\n\r\n// 实例化为state\r\nexport const state = reactive({\r\n\tglobalNum: 0,\r\n\tuserProfile: { username: '', email: '' },\r\n\tisLoggedIn: false,\r\n\tdeviceState: {\r\n\t\tdevices: [],\r\n\t\tcurrentDevice: null,\r\n\t\tisLoading: false,\r\n\t\tlastUpdated: null\r\n\t} as DeviceState\r\n} as State)\r\n// 定义修改属性值的方法\r\nexport const setGlobalNum = (num : number) => {\r\n\tstate.globalNum = num\r\n}\r\n// 新增:设置登录状态的方法\r\nexport const setIsLoggedIn = (val : boolean) => {\r\n\tstate.isLoggedIn = val\r\n}\r\n// 定义全局设置用户信息的方法\r\nexport const setUserProfile = (profile : UserProfile) => {\r\n\tstate.userProfile = profile\r\n}\r\n\r\n// 获取当前用户信息(含补全 profile)\r\nexport async function getCurrentUser() : Promise {\r\n\ttry {\r\n\t\tawait supaReady\r\n\t} catch (_) {}\r\n\r\n\tconst sessionInfo = supa.getSession()\r\n\tif (sessionInfo.user == null) {\r\n\t\tstate.userProfile = { username: '', email: '' } as UserProfile\r\n\t\tstate.isLoggedIn = false // 未登录\r\n\t\treturn null\r\n\t}\r\n\tconst userId = sessionInfo.user?.getString(\"id\")\r\n\tif (userId == null) {\r\n\t\tstate.userProfile = { username: '', email: '' } as UserProfile\r\n\t\tstate.isLoggedIn = false // 未登录\r\n\t\treturn null\r\n\t}\t// 查询 ak_users 表补全 profile\r\n\tconst res = await supa.from('ak_users').select('*', {}).eq('id', userId).execute()\r\n\t__f__('log','at utils/store.uts:69',res)\r\n\tif (res.status >= 200 && res.status < 300 && (res.data != null)) {\r\n\t\tlet user : UTSJSONObject | null = null;\r\n\t\tconst data = res.data as any;\r\n\t\tif (Array.isArray(data)) {\r\n\t\t\tif (data.length > 0) {\r\n\t\t\t\tuser = data[0] as UTSJSONObject;\r\n\t\t\t}\r\n\t\t} else if (data != null) {\r\n\t\t\tuser = data as UTSJSONObject;\r\n\t\t} __f__('log','at utils/store.uts:79',user)\r\n\t\tif (user == null) {\r\n\t\t\t__f__('log','at utils/store.uts:81','用户资料为空,尝试创建基础资料...')\t\t\t// 如果用户资料为空,尝试创建基础用户资料\r\n\t\t\tconst sessionUser = sessionInfo.user\r\n\t\t\tif (sessionUser != null) {\r\n\t\t\t\tconst createdProfile = await ensureUserProfile(sessionUser)\r\n\t\t\t\tif (createdProfile != null) {\r\n\t\t\t\t\tstate.userProfile = createdProfile\r\n\t\t\t\t\tstate.isLoggedIn = true\r\n\t\t\t\t\treturn createdProfile\r\n\t\t\t\t} else {\r\n\t\t\t\t\t__f__('error','at utils/store.uts:90','创建用户资料失败')\r\n\t\t\t\t\tstate.userProfile = { username: '', email: '' } as UserProfile\r\n\t\t\t\t\tstate.isLoggedIn = false\r\n\t\t\t\t\treturn null\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\t__f__('error','at utils/store.uts:96','会话用户信息为空')\r\n\t\t\t\tstate.userProfile = { username: '', email: '' } as UserProfile\r\n\t\t\t\tstate.isLoggedIn = false\r\n\t\t\t\treturn null\r\n\t\t\t}\r\n\t\t}\r\n\t\t__f__('log','at utils/store.uts:102',user)\r\n\t\t// 直接用 getString/getNumber,无需兜底属性\t\t\r\n\t\tconst profile : UserProfile = {\r\n\t\t\tid: user.getString('id'),\r\n\t\t\tusername: user.getString('username') ?? \"\",\r\n\t\t\temail: user.getString('email') ?? \"\",\r\n\t\t\tgender: user.getString('gender'),\r\n\t\t\tbirthday: user.getString('birthday'),\r\n\t\t\theight_cm: user.getNumber('height_cm'),\r\n\t\t\tweight_kg: user.getNumber('weight_kg'),\r\n\t\t\tbio: user.getString('bio'),\r\n\t\t\tavatar_url: user.getString('avatar_url'),\r\n\t\t\tpreferred_language: user.getString('preferred_language'),\r\n\t\t\trole: user.getString('role'),\r\n\t\t\tschool_id: user.getString('school_id'),\r\n\t\t\tgrade_id: user.getString('grade_id'),\r\n\t\t\tclass_id: user.getString('class_id')\r\n\t\t}\r\n\t\tstate.userProfile = profile\r\n\t\tstate.isLoggedIn = true // 登录成功\r\n\t\treturn profile\r\n\t} else {\r\n\t\tstate.userProfile = { username: '', email: '' } as UserProfile\r\n\t\tstate.isLoggedIn = false // 未登录\r\n\t\treturn null\r\n\t}\r\n}\r\n\r\n// 登出并清空用户信息\r\nexport function logout() {\r\n\tsupa.signOut()\r\n\tstate.userProfile = { username: '', email: '' } as UserProfile\r\n\tstate.isLoggedIn = false // 登出\r\n}\r\n\r\n// 获取当前用户ID(优先级:state.userProfile.id > session > localStorage)\r\nexport function getCurrentUserId() : string {\r\n\ttry {\r\n\t\tconst profile = state.userProfile\r\n\t\tif (profile != null && profile.id != null) {\r\n\t\t\tconst profileId = profile.id\r\n\t\t\tif (profileId != null) {\r\n\t\t\t\treturn profileId\r\n\t\t\t}\r\n\t\t}\r\n\t} catch (e) { }\r\n\ttry {\r\n\t\tconst session = supa.getSession()\r\n\t\tif (session != null) {\r\n\t\t\tconst curuser = session.user\r\n\t\t\tconst userId = curuser?.getString('id')\r\n\t\t\tif (userId != null) return userId\r\n\t\t}\r\n\t} catch (e) { }\r\n\treturn ''\r\n}\r\n\r\n// 获取当前用户的class_id\r\nexport function getCurrentUserClassId() : string | null {\r\n\ttry {\r\n\t\tconst profile = state.userProfile\r\n\t\tif (profile != null && profile.class_id != null) {\r\n\t\t\treturn profile.class_id\r\n\t\t}\r\n\t} catch (e) {\r\n\t\t__f__('error','at utils/store.uts:167','获取用户class_id失败:', e)\r\n\t}\r\n\treturn null\r\n}\r\n\r\n// User store API for component compatibility\r\nexport function getUserStore() {\r\n\treturn {\r\n\t\tgetUserId() : string | null {\r\n\t\t\tconst sessionInfo = supa.getSession()\r\n\t\t\treturn sessionInfo.user?.getString(\"id\") ?? null\r\n\t\t},\r\n\r\n\t\tgetUserName() : string | null {\r\n\t\t\treturn state.userProfile?.username ?? null\r\n\t\t},\r\n\r\n\t\tgetUserRole() : string | null {\r\n\t\t\t// Default role logic - can be enhanced based on your needs\r\n\t\t\tconst sessionInfo = supa.getSession()\r\n\t\t\tif (sessionInfo.user == null) return null\r\n\r\n\t\t\t// You can add role detection logic here\r\n\t\t\t// For now, return a default role\r\n\t\t\treturn 'teacher' // or determine from user profile/database\r\n\t\t},\r\n\r\n\t\tgetProfile() : UserProfile | null {\r\n\t\t\treturn state.userProfile\r\n\t\t}\r\n\t}\r\n}\r\n\r\n// ========== 设备状态管理方法 ==========\r\n\r\n/**\r\n * 设置设备加载状态\r\n */\r\nexport const setDeviceLoading = (loading : boolean) => {\r\n\tstate.deviceState.isLoading = loading\r\n}\r\n\r\n/**\r\n * 设置设备列表\r\n */\r\nexport const setDevices = (devices : Array) => {\r\n\tstate.deviceState.devices = devices\r\n\tstate.deviceState.lastUpdated = Date.now()\r\n}\r\n\r\n/**\r\n * 添加设备到列表\r\n */\r\nexport const addDevice = (device : DeviceInfo) => {\r\n\tconst existingIndex = state.deviceState.devices.findIndex(d => d.id === device.id)\r\n\tif (existingIndex >= 0) {\r\n\t\t// 更新现有设备\r\n\t\tstate.deviceState.devices[existingIndex] = device\r\n\t} else {\r\n\t\t// 添加新设备\r\n\t\tstate.deviceState.devices.push(device)\r\n\t}\r\n\tstate.deviceState.lastUpdated = Date.now()\r\n}\r\n\r\n/**\r\n * 从列表中移除设备\r\n */\r\nexport const removeDevice = (deviceId : string) => {\r\n\tconst index = state.deviceState.devices.findIndex(d => d.id === deviceId)\r\n\tif (index >= 0) {\r\n\t\tstate.deviceState.devices.splice(index, 1)\r\n\t\t// 如果移除的是当前设备,清空当前设备\r\n\t\tif (state.deviceState.currentDevice?.id === deviceId) {\r\n\t\t\tstate.deviceState.currentDevice = null\r\n\t\t}\r\n\t\tstate.deviceState.lastUpdated = Date.now()\r\n\t}\r\n}\r\n\r\n/**\r\n * 更新设备信息\r\n */\r\nexport const updateDevice = (device : DeviceInfo) => {\r\n\tconst index = state.deviceState.devices.findIndex(d => d.id === device.id)\r\n\tif (index >= 0) {\r\n\t\tstate.deviceState.devices[index] = device\r\n\t\t// 如果更新的是当前设备,也更新当前设备\r\n\t\tif (state.deviceState.currentDevice?.id === device.id) {\r\n\t\t\tstate.deviceState.currentDevice = device\r\n\t\t}\r\n\t\tstate.deviceState.lastUpdated = Date.now()\r\n\t}\r\n}\r\n\r\n/**\r\n * 设置当前选中的设备\r\n */\r\nexport const setCurrentDevice = (device : DeviceInfo | null) => {\r\n\tstate.deviceState.currentDevice = device\r\n}\r\n\r\n/**\r\n * 根据设备ID获取设备信息\r\n */\r\nexport const getDeviceById = (deviceId : string) : DeviceInfo | null => {\r\n\treturn state.deviceState.devices.find(d => d.id === deviceId) ?? null\r\n}\r\n\r\n/**\r\n * 获取在线设备列表\r\n */\r\nexport const getOnlineDevices = () : Array => {\r\n\treturn state.deviceState.devices.filter(d => d.status === 'online')\r\n}\r\n\r\n/**\r\n * 从服务器加载设备列表\r\n */\r\nexport const loadDevices = async (forceRefresh : boolean) : Promise => {\r\n\tconst userId = getCurrentUserId()\r\n\tif (userId == null || userId === '') {\r\n\t\t__f__('log','at utils/store.uts:289','用户未登录,无法加载设备列表')\r\n\t\treturn false\r\n\t}\r\n\r\n\t// 如果不是强制刷新且数据较新(5分钟内),直接返回\r\n\tconst now = Date.now()\r\n\tconst lastUpdated = state.deviceState.lastUpdated\r\n\tif (forceRefresh == false && lastUpdated != null && (now - lastUpdated < 5 * 60 * 1000)) {\r\n\t\t__f__('log','at utils/store.uts:297','设备数据较新,跳过刷新')\r\n\t\treturn true\r\n\t}\r\n\tsetDeviceLoading(true)\r\n\ttry {\r\n\t\tconst result = await SenseDataService.getDevices({ user_id: userId })\r\n\t\tif (result.error === null && result.data != null) {\r\n\t\t\tconst devices = result.data as Array\r\n\t\t\tsetDevices(devices)\r\n\t\t\t__f__('log','at utils/store.uts:306',`加载设备列表成功,共${devices.length}个设备`)\r\n\t\t\treturn true\r\n\t\t} else {\r\n\t\t\t__f__('log','at utils/store.uts:309','加载设备列表失败:', result.error?.message ?? '未知错误')\r\n\t\t\treturn false\r\n\t\t}\r\n\t} catch (error) {\r\n\t\t__f__('log','at utils/store.uts:313','加载设备列表异常:', error)\r\n\t\treturn false\r\n\t} finally {\r\n\t\tsetDeviceLoading(false)\r\n\t}\r\n}\r\n\r\n/**\r\n * 从服务器加载设备列表 - 带默认参数的重载版本\r\n */\r\nexport const loadDevicesWithDefault = async () : Promise => {\r\n\treturn await loadDevices(false)\r\n}\r\n\r\n/**\r\n * 绑定新设备\r\n */\r\nexport const bindNewDevice = async (deviceData : UTSJSONObject) : Promise => {\r\n\tconst userId = getCurrentUserId()\r\n\tif (userId == null) {\r\n\t\t__f__('log','at utils/store.uts:333','用户未登录,无法绑定设备')\r\n\t\treturn false\r\n\t}\r\n\r\n\t// 确保设备数据中包含用户ID\r\n\tdeviceData.set('user_id', userId)\r\n\ttry {\r\n\t\tconst result = await SenseDataService.bindDevice(deviceData)\r\n\t\tif (result.error === null && result.data != null) {\r\n\t\t\t// 添加到本地状态\r\n\t\t\taddDevice(result.data as DeviceInfo)\r\n\t\t\tconst deviceName = (result.data as DeviceInfo).device_name ?? '未知设备'\r\n\t\t\t__f__('log','at utils/store.uts:345','设备绑定成功:', deviceName)\r\n\t\t\treturn true\r\n\t\t} else {\r\n\t\t\t__f__('log','at utils/store.uts:348','设备绑定失败:', result.error?.message ?? '未知错误')\r\n\t\t\treturn false\r\n\t\t}\r\n\t} catch (error) {\r\n\t\t__f__('log','at utils/store.uts:352','设备绑定异常:', error)\r\n\t\treturn false\r\n\t}\r\n}\r\n\r\n/**\r\n * 解绑设备\r\n */\r\nexport const unbindDevice = async (deviceId : string) : Promise => {\r\n\ttry {\r\n\t\tconst result = await SenseDataService.unbindDevice(deviceId)\r\n\t\tif (result.error === null) {\r\n\t\t\t// 从本地状态中移除\r\n\t\t\tremoveDevice(deviceId)\r\n\t\t\t__f__('log','at utils/store.uts:366','设备解绑成功')\r\n\t\t\treturn true\r\n\t\t} else {\r\n\t\t\t__f__('log','at utils/store.uts:369','设备解绑失败:', result.error?.message ?? '未知错误')\r\n\t\t\treturn false\r\n\t\t}\r\n\t} catch (error) {\r\n\t\t__f__('log','at utils/store.uts:373','设备解绑异常:', error)\r\n\t\treturn false\r\n\t}\r\n}\r\n\r\n/**\r\n * 更新设备配置\r\n */\r\nexport const updateDeviceConfig = async (deviceId : string, configData : UTSJSONObject) : Promise => {\r\n\ttry {\r\n\t\tconst result = await SenseDataService.updateDevice(deviceId, configData)\r\n\t\tif (result.error === null && result.data != null) {\r\n\t\t\t// 更新本地状态\r\n\t\t\tupdateDevice(result.data as DeviceInfo)\r\n\t\t\t__f__('log','at utils/store.uts:387','设备配置更新成功')\r\n\t\t\treturn true\r\n\t\t} else {\r\n\t\t\t__f__('log','at utils/store.uts:390','设备配置更新失败:', result.error?.message ?? '未知错误')\r\n\t\t\treturn false\r\n\t\t}\r\n\t} catch (error) {\r\n\t\t__f__('log','at utils/store.uts:394','设备配置更新异常:', error)\r\n\t\treturn false\r\n\t}\r\n}\r\n\r\n// ========== 设备管理 API ==========\r\n\r\n/**\r\n * 获取设备管理相关的API\r\n */\r\nexport function getDeviceStore() {\r\n\treturn {\r\n\t\t// 获取设备状态\r\n\t\tgetDevices() : Array {\r\n\t\t\treturn state.deviceState.devices\r\n\t\t},\r\n\r\n\t\tgetCurrentDevice() : DeviceInfo | null {\r\n\t\t\treturn state.deviceState.currentDevice\r\n\t\t},\r\n\r\n\t\tisLoading() : boolean {\r\n\t\t\treturn state.deviceState.isLoading\r\n\t\t},\r\n\t\tgetLastUpdated() : number | null {\r\n\t\t\treturn state.deviceState.lastUpdated\r\n\t\t},\r\n\r\n\t\t// 设备操作方法\r\n\t\tasync loadDevices(forceRefresh : boolean) : Promise {\r\n\t\t\treturn await loadDevices(forceRefresh)\r\n\t\t},\r\n\r\n\t\tasync refreshDevices() : Promise {\r\n\t\t\treturn await loadDevicesWithDefault()\r\n\t\t},\r\n\r\n\t\tasync bindDevice(deviceData : UTSJSONObject) : Promise {\r\n\t\t\treturn await bindNewDevice(deviceData)\r\n\t\t},\r\n\r\n\t\tasync unbindDevice(deviceId : string) : Promise {\r\n\t\t\treturn await unbindDevice(deviceId)\r\n\t\t},\r\n\r\n\t\tasync updateDevice(deviceId : string, configData : UTSJSONObject) : Promise {\r\n\t\t\treturn await updateDeviceConfig(deviceId, configData)\r\n\t\t},\r\n\r\n\t\t// 设备查询方法\r\n\t\tgetDeviceById(deviceId : string) : DeviceInfo | null {\r\n\t\t\treturn getDeviceById(deviceId)\r\n\t\t},\r\n\r\n\t\tgetOnlineDevices() : Array {\r\n\t\t\treturn getOnlineDevices()\r\n\t\t},\r\n\r\n\t\t// 设备选择\r\n\t\tsetCurrentDevice(device : DeviceInfo | null) {\r\n\t\t\tsetCurrentDevice(device)\r\n\t\t}\r\n\t}\r\n}","import 'D:/HBuilderX/HBuilderX/plugins/uniapp-cli-vite/node_modules/@dcloudio/uni-console/src/runtime/app/index.ts';// 简化的main.uts,移除i18n依赖\r\nimport { createSSRApp } from 'vue'\r\nimport App from './App.uvue'\r\nimport i18n from '@/uni_modules/i18n/index.uts'\r\n\r\nexport function createApp() {\r\n const app = createSSRApp(App)\r\n \r\n // 注册 i18n 全局属性,使组件可以使用 $t 方法\r\n\tapp.config.globalProperties.$t = (key: string, values?: any, locale?: string): string => {\r\n\t\tif (i18n.global == null) {\r\n\t\t\t__f__('error','at main.uts:12','i18n is not initialized')\r\n\t\t\treturn key\r\n\t\t}\r\n const params = values as UTSJSONObject | null\r\n\t\tconst res = i18n.global.t(key, params, locale)\r\n if (res.length > 0) {\r\n return res\r\n }\r\n return key\r\n\t}\r\n \r\n return { app }\r\n}\r\n\nexport function main(app: IApp) {\n definePageRoutes();\n defineAppConfig();\n (createApp()['app'] as VueApp).mount(app, GenUniApp());\n}\n\nexport class UniAppConfig extends io.dcloud.uniapp.appframe.AppConfig {\n override name: string = \"mall\"\n override appid: string = \"__UNI__EC68BC3\"\n override versionName: string = \"1.0.0\"\n override versionCode: string = \"100\"\n override uniCompilerVersion: string = \"4.87\"\n \n constructor() { super() }\n}\n\nimport GenPagesUserLoginClass from './pages/user/login.uvue'\nimport GenPagesUserBootClass from './pages/user/boot.uvue'\nimport GenPagesUserRegisterClass from './pages/user/register.uvue'\nimport GenPagesUserForgotPasswordClass from './pages/user/forgot-password.uvue'\nimport GenPagesUserTermsClass from './pages/user/terms.uvue'\nimport GenPagesUserCenterClass from './pages/user/center.uvue'\nimport GenPagesUserProfileClass from './pages/user/profile.uvue'\nimport GenPagesUserChangePasswordClass from './pages/user/change-password.uvue'\nimport GenPagesUserBindPhoneClass from './pages/user/bind-phone.uvue'\nimport GenPagesUserBindEmailClass from './pages/user/bind-email.uvue'\nimport GenPagesMallConsumerIndexClass from './pages/mall/consumer/index.uvue'\nimport GenPagesMallConsumerCategoryClass from './pages/mall/consumer/category.uvue'\nimport GenPagesMallConsumerMessagesClass from './pages/mall/consumer/messages.uvue'\nimport GenPagesMallConsumerCartClass from './pages/mall/consumer/cart.uvue'\nimport GenPagesMallConsumerProfileClass from './pages/mall/consumer/profile.uvue'\nimport GenPagesMallConsumerSettingsClass from './pages/mall/consumer/settings.uvue'\nimport GenPagesMallConsumerWalletClass from './pages/mall/consumer/wallet.uvue'\nimport GenPagesMallConsumerWithdrawClass from './pages/mall/consumer/withdraw.uvue'\nimport GenPagesMallConsumerSearchClass from './pages/mall/consumer/search.uvue'\nimport GenPagesMallConsumerProductDetailClass from './pages/mall/consumer/product-detail.uvue'\nimport GenPagesMallConsumerShopDetailClass from './pages/mall/consumer/shop-detail.uvue'\nimport GenPagesMallConsumerCouponsClass from './pages/mall/consumer/coupons.uvue'\nimport GenPagesMallConsumerFavoritesClass from './pages/mall/consumer/favorites.uvue'\nimport GenPagesMallConsumerFootprintClass from './pages/mall/consumer/footprint.uvue'\nimport GenPagesMallConsumerAddressListClass from './pages/mall/consumer/address-list.uvue'\nimport GenPagesMallConsumerAddressEditClass from './pages/mall/consumer/address-edit.uvue'\nimport GenPagesMallConsumerCheckoutClass from './pages/mall/consumer/checkout.uvue'\nimport GenPagesMallConsumerPaymentClass from './pages/mall/consumer/payment.uvue'\nimport GenPagesMallConsumerPaymentSuccessClass from './pages/mall/consumer/payment-success.uvue'\nimport GenPagesMallConsumerOrdersClass from './pages/mall/consumer/orders.uvue'\nimport GenPagesMallConsumerOrderDetailClass from './pages/mall/consumer/order-detail.uvue'\nimport GenPagesMallConsumerLogisticsClass from './pages/mall/consumer/logistics.uvue'\nimport GenPagesMallConsumerReviewClass from './pages/mall/consumer/review.uvue'\nimport GenPagesMallConsumerRefundClass from './pages/mall/consumer/refund.uvue'\nimport GenPagesMallConsumerApplyRefundClass from './pages/mall/consumer/apply-refund.uvue'\nimport GenPagesMallConsumerRefundReviewClass from './pages/mall/consumer/refund-review.uvue'\nimport GenPagesMallConsumerChatClass from './pages/mall/consumer/chat.uvue'\nimport GenPagesMallConsumerSubscriptionFollowedShopsClass from './pages/mall/consumer/subscription/followed-shops.uvue'\nimport GenPagesMallConsumerPointsIndexClass from './pages/mall/consumer/points/index.uvue'\nimport GenPagesMallConsumerRedPacketsIndexClass from './pages/mall/consumer/red-packets/index.uvue'\nimport GenPagesMallConsumerBankCardsIndexClass from './pages/mall/consumer/bank-cards/index.uvue'\nimport GenPagesMallConsumerBankCardsAddClass from './pages/mall/consumer/bank-cards/add.uvue'\nfunction definePageRoutes() {\n__uniRoutes.push({ path: \"pages/user/login\", component: GenPagesUserLoginClass, meta: { isQuit: true } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"用户登录\"],[\"navigationStyle\",\"custom\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/user/boot\", component: GenPagesUserBootClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/user/register\", component: GenPagesUserRegisterClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"注册\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/user/forgot-password\", component: GenPagesUserForgotPasswordClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"忘记密码\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/user/terms\", component: GenPagesUserTermsClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"用户协议与隐私政策\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/user/center\", component: GenPagesUserCenterClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"用户中心\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/user/profile\", component: GenPagesUserProfileClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"个人资料\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/user/change-password\", component: GenPagesUserChangePasswordClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"修改密码\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/user/bind-phone\", component: GenPagesUserBindPhoneClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"绑定手机\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/user/bind-email\", component: GenPagesUserBindEmailClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"绑定邮箱\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/index\", component: GenPagesMallConsumerIndexClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"首页\"],[\"navigationStyle\",\"custom\"],[\"enablePullDownRefresh\",false]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/category\", component: GenPagesMallConsumerCategoryClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"分类\"],[\"navigationStyle\",\"custom\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/messages\", component: GenPagesMallConsumerMessagesClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"消息\"],[\"enablePullDownRefresh\",true]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/cart\", component: GenPagesMallConsumerCartClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"购物车\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/profile\", component: GenPagesMallConsumerProfileClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"我的\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/settings\", component: GenPagesMallConsumerSettingsClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"设置\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/wallet\", component: GenPagesMallConsumerWalletClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"我的钱包\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/withdraw\", component: GenPagesMallConsumerWithdrawClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"余额提现\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/search\", component: GenPagesMallConsumerSearchClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"搜索\"],[\"navigationStyle\",\"custom\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/product-detail\", component: GenPagesMallConsumerProductDetailClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"商品详情\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/shop-detail\", component: GenPagesMallConsumerShopDetailClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"店铺详情\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/coupons\", component: GenPagesMallConsumerCouponsClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"我的优惠券\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/favorites\", component: GenPagesMallConsumerFavoritesClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"我的收藏\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/footprint\", component: GenPagesMallConsumerFootprintClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"我的足迹\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/address-list\", component: GenPagesMallConsumerAddressListClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"收货地址\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/address-edit\", component: GenPagesMallConsumerAddressEditClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"编辑地址\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/checkout\", component: GenPagesMallConsumerCheckoutClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"确认订单\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/payment\", component: GenPagesMallConsumerPaymentClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"收银台\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/payment-success\", component: GenPagesMallConsumerPaymentSuccessClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"支付成功\"],[\"navigationStyle\",\"custom\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/orders\", component: GenPagesMallConsumerOrdersClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"我的订单\"],[\"enablePullDownRefresh\",true]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/order-detail\", component: GenPagesMallConsumerOrderDetailClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"订单详情\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/logistics\", component: GenPagesMallConsumerLogisticsClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"物流详情\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/review\", component: GenPagesMallConsumerReviewClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"评价晒单\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/refund\", component: GenPagesMallConsumerRefundClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"退款/售后\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/apply-refund\", component: GenPagesMallConsumerApplyRefundClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"申请售后\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/refund-review\", component: GenPagesMallConsumerRefundReviewClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"服务评价\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/chat\", component: GenPagesMallConsumerChatClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"客服聊天\"],[\"navigationStyle\",\"custom\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/subscription/followed-shops\", component: GenPagesMallConsumerSubscriptionFollowedShopsClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"关注店铺\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/points/index\", component: GenPagesMallConsumerPointsIndexClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"积分管理\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/red-packets/index\", component: GenPagesMallConsumerRedPacketsIndexClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"我的红包\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/bank-cards/index\", component: GenPagesMallConsumerBankCardsIndexClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"银行卡管理\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/bank-cards/add\", component: GenPagesMallConsumerBankCardsAddClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"添加银行卡\"]]) } as UniPageRoute)\n}\nconst __uniTabBar: Map | null = _uM([[\"color\",\"#999999\"],[\"selectedColor\",\"#ff5000\"],[\"backgroundColor\",\"#ffffff\"],[\"borderStyle\",\"black\"],[\"list\",[_uM([[\"pagePath\",\"pages/mall/consumer/index\"],[\"text\",\"首页\"],[\"iconPath\",\"static/tabbar/home.png\"],[\"selectedIconPath\",\"static/tabbar/home-active.png\"]]),_uM([[\"pagePath\",\"pages/mall/consumer/category\"],[\"text\",\"分类\"],[\"iconPath\",\"static/tabbar/category.png\"],[\"selectedIconPath\",\"static/tabbar/category-active.png\"]]),_uM([[\"pagePath\",\"pages/mall/consumer/messages\"],[\"text\",\"消息\"],[\"iconPath\",\"static/tabbar/messages.png\"],[\"selectedIconPath\",\"static/tabbar/messages-active.png\"]]),_uM([[\"pagePath\",\"pages/mall/consumer/cart\"],[\"text\",\"购物车\"],[\"iconPath\",\"static/tabbar/cart.png\"],[\"selectedIconPath\",\"static/tabbar/cart-active.png\"]]),_uM([[\"pagePath\",\"pages/mall/consumer/profile\"],[\"text\",\"我的\"],[\"iconPath\",\"static/tabbar/profile.png\"],[\"selectedIconPath\",\"static/tabbar/profile-active.png\"]])]]])\nconst __uniLaunchPage: Map = _uM([[\"url\",\"pages/user/login\"],[\"style\",_uM([[\"navigationBarTitleText\",\"用户登录\"],[\"navigationStyle\",\"custom\"]])]])\nfunction defineAppConfig(){\n __uniConfig.entryPagePath = '/pages/user/login'\n __uniConfig.globalStyle = _uM([[\"navigationBarTextStyle\",\"black\"],[\"navigationBarTitleText\",\"mall\"],[\"navigationBarBackgroundColor\",\"#FFFFFF\"],[\"backgroundColor\",\"#F8F8F8\"]])\n __uniConfig.getTabBarConfig = ():Map | null => _uM([[\"color\",\"#999999\"],[\"selectedColor\",\"#ff5000\"],[\"backgroundColor\",\"#ffffff\"],[\"borderStyle\",\"black\"],[\"list\",[_uM([[\"pagePath\",\"pages/mall/consumer/index\"],[\"text\",\"首页\"],[\"iconPath\",\"static/tabbar/home.png\"],[\"selectedIconPath\",\"static/tabbar/home-active.png\"]]),_uM([[\"pagePath\",\"pages/mall/consumer/category\"],[\"text\",\"分类\"],[\"iconPath\",\"static/tabbar/category.png\"],[\"selectedIconPath\",\"static/tabbar/category-active.png\"]]),_uM([[\"pagePath\",\"pages/mall/consumer/messages\"],[\"text\",\"消息\"],[\"iconPath\",\"static/tabbar/messages.png\"],[\"selectedIconPath\",\"static/tabbar/messages-active.png\"]]),_uM([[\"pagePath\",\"pages/mall/consumer/cart\"],[\"text\",\"购物车\"],[\"iconPath\",\"static/tabbar/cart.png\"],[\"selectedIconPath\",\"static/tabbar/cart-active.png\"]]),_uM([[\"pagePath\",\"pages/mall/consumer/profile\"],[\"text\",\"我的\"],[\"iconPath\",\"static/tabbar/profile.png\"],[\"selectedIconPath\",\"static/tabbar/profile-active.png\"]])]]])\n __uniConfig.tabBar = __uniConfig.getTabBarConfig()\n __uniConfig.conditionUrl = ''\n __uniConfig.uniIdRouter = new Map()\n \n __uniConfig.ready = true\n}\n","import supa from '@/components/supadb/aksupainstance.uts'\r\nimport type { AkReqResponse } from '@/uni_modules/ak-req/index.uts'\r\n\r\n// 使用单例 Supabase 客户端\r\n// const supa = createClient(SUPA_URL, SUPA_KEY)\r\n\r\n// 类型定义\r\nexport type Brand = {\r\n id: string\r\n name: string\r\n logo_url: string\r\n description: string\r\n}\r\n\r\nexport type Category = {\r\n id: string\r\n name: string\r\n icon: string\r\n description: string\r\n color: string\r\n parent_id?: string\r\n level?: number\r\n slug?: string\r\n created_at?: string\r\n}\r\n\r\nexport type Product = {\r\n id: string\r\n category_id: string\r\n merchant_id: string\r\n name: string\r\n subtitle?: string\r\n description?: string\r\n base_price?: number\r\n market_price?: number\r\n cost_price?: number\r\n main_image_url?: string\r\n image_url?: string\r\n image_urls?: string\r\n video_urls?: string\r\n images?: string[]\r\n sale_count?: number\r\n view_count?: number\r\n total_stock?: number\r\n available_stock?: number\r\n is_hot?: boolean\r\n is_new?: boolean\r\n is_featured?: boolean\r\n status?: number\r\n rating_avg?: number\r\n rating_count?: number\r\n rating?: number\r\n review_count?: number\r\n brand_id?: string\r\n shop_id?: string\r\n tags?: string\r\n attributes?: string\r\n created_at?: string\r\n updated_at?: string\r\n price?: number\r\n original_price?: number\r\n stock?: number\r\n sales?: number\r\n cover?: string\r\n brand_name?: string\r\n category_name?: string\r\n shop_name?: string\r\n merchant_name?: string\r\n}\r\n\r\nexport type Shop = {\r\n id: string\r\n merchant_id: string\r\n shop_name: string\r\n shop_logo?: string\r\n shop_banner?: string\r\n description?: string\r\n contact_name?: string\r\n contact_phone?: string\r\n rating_avg?: number\r\n total_sales?: number\r\n product_count?: number\r\n total_sales_count?: number\r\n created_at?: string\r\n}\r\n\r\nexport type CartItem = {\r\n id: string\r\n user_id: string\r\n product_id: string\r\n sku_id?: string\r\n merchant_id?: string\r\n quantity: number\r\n selected: boolean\r\n product_name?: string\r\n product_image?: string\r\n product_price?: number\r\n product_specification?: string\r\n shop_id?: string\r\n shop_name?: string\r\n created_at?: string\r\n updated_at?: string\r\n}\r\n\r\nexport type UserAddress = {\r\n id: string\r\n user_id: string\r\n recipient_name: string\r\n phone: string\r\n province: string\r\n city: string\r\n district: string\r\n detail_address: string\r\n postal_code?: string\r\n is_default: boolean\r\n label?: string\r\n created_at?: string\r\n updated_at?: string\r\n}\r\n\r\nexport type UserCoupon = {\r\n id: string\r\n user_id: string\r\n template_id: string\r\n coupon_code: string\r\n status: number // 1: unused, 2: used, 3: expired\r\n received_at: string\r\n expire_at: string\r\n used_at?: string\r\n // join fields from template or view\r\n template_name?: string\r\n amount?: number\r\n min_spend?: number\r\n name?: string\r\n title?: string\r\n}\r\n\r\nexport type ChatRoom = {\r\n id: string\r\n user_id: string\r\n merchant_id: string\r\n shop_name: string\r\n shop_logo?: string\r\n last_message?: string\r\n last_message_at?: string\r\n unread_count: number\r\n is_top: boolean\r\n created_at?: string\r\n updated_at?: string\r\n}\r\n\r\nexport type Notification = {\r\n id: string\r\n user_id: string\r\n type: string\r\n title: string\r\n content: string\r\n icon_url?: string\r\n link_url?: string\r\n is_read: boolean\r\n extra_data?: string\r\n created_at?: string\r\n}\r\n\r\nexport type ChatMessage = {\r\n id: string\r\n session_id?: string\r\n sender_id?: string\r\n receiver_id?: string\r\n content: string\r\n msg_type: string\r\n is_read: boolean\r\n is_from_user: boolean\r\n extra_data?: string\r\n created_at?: string\r\n}\r\n\r\nexport type PaginatedResponse = {\r\n data: T[]\r\n total: number\r\n page: number\r\n limit: number\r\n hasmore: boolean\r\n}\r\n\r\nexport type ProductSku = {\r\n id: string\r\n product_id: string\r\n sku_code: string\r\n specifications: string // JSON string\r\n price: number\r\n market_price?: number\r\n cost_price?: number\r\n stock?: number\r\n warning_stock?: number\r\n image_url?: string\r\n weight?: number\r\n status?: number\r\n created_at?: string\r\n}\r\n\r\nexport type AddAddressParams = {\r\n recipient_name: string\r\n phone: string\r\n province: string\r\n city: string\r\n district: string\r\n detail_address: string\r\n postal_code?: string\r\n is_default?: boolean\r\n label?: string\r\n}\r\n\r\nexport type UpdateAddressParams = {\r\n recipient_name?: string\r\n phone?: string\r\n province?: string\r\n city?: string\r\n district?: string\r\n detail_address?: string\r\n postal_code?: string\r\n is_default?: boolean\r\n label?: string\r\n}\r\n\r\nexport type CreateOrderParams = {\r\n merchant_id: string\r\n product_amount: number\r\n shipping_fee: number\r\n total_amount: number\r\n shipping_address: any\r\n items: any[]\r\n}\r\n\r\nexport type ShopOrderParams = {\r\n shipping_address: any\r\n shopGroups: any[]\r\n deliveryFee: number\r\n discountAmount: number\r\n}\r\n\r\nexport type ShopOrderResponse = {\r\n success: boolean\r\n orderIds: string[]\r\n error?: string\r\n}\r\n\r\nexport type RefundResponse = {\r\n success: boolean\r\n message: string\r\n}\r\n\r\nexport type ConfirmReceiptResponse = {\r\n success: boolean\r\n error?: string\r\n}\r\n\r\nclass SupabaseService {\r\n // 获取当前用户ID\r\n public getCurrentUserId(): string | null {\r\n try {\r\n // 1. 优先从 Supabase 会话获取\r\n const session = supa.getSession()\r\n if (session != null && session.user != null) {\r\n return session.user.getString('id')\r\n }\r\n \r\n // 2. 尝试从 Storage 恢复 Session (针对 App 重启后内存丢失的情况)\r\n // 注意:这里无法异步调用 hydrate,所以只能依赖 UI 层或 init 层的预加载\r\n // 但我们可以返回本地存储 ID 作为 fallback,前提是 Token 有效\r\n \r\n // 后备:尝试从本地存储获取\r\n const userId = uni.getStorageSync('user_id')\r\n return userId != null ? userId as string : null\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:276','获取用户ID失败:', e)\r\n return null\r\n }\r\n }\r\n\r\n // 确保会话有效 (异步)\r\n async ensureSession(): Promise {\r\n let session = supa.getSession()\r\n if (session.user == null) {\r\n __f__('log','at utils/supabaseService.uts:285','Session user is null, attempting to hydrate from storage...')\r\n await supa.hydrateSessionFromStorage()\r\n session = supa.getSession()\r\n }\r\n \r\n if (session.user != null) {\r\n // 同步 user_id 到 storage 保持一致\r\n const uid = session.user!!.getString('id')\r\n if (uid != null) {\r\n uni.setStorageSync('user_id', uid)\r\n return uid\r\n }\r\n }\r\n return this.getCurrentUserId()\r\n }\r\n\r\n // 获取所有分类\r\n async getCategories(): Promise {\r\n try {\r\n const response = await supa\r\n .from('ml_categories')\r\n .select('*')\r\n .order('name', { ascending: true })\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:311','获取分类失败:', response.error)\r\n return []\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) {\r\n return []\r\n }\r\n \r\n const categories: Category[] = []\r\n const rawList = rawData as any[]\r\n for (let i: number = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const catObj = item as UTSJSONObject\r\n const idVal = catObj.get('id')\r\n const nameVal = catObj.get('name')\r\n const iconVal = catObj.get('icon')\r\n const iconUrlVal = catObj.get('icon_url')\r\n const descVal = catObj.get('description')\r\n const colorVal = catObj.get('color')\r\n const parentIdVal = catObj.get('parent_id')\r\n const levelVal = catObj.get('level')\r\n \r\n const cat: Category = {\r\n id: (typeof idVal == 'string') ? (idVal as string) : '',\r\n name: (typeof nameVal == 'string') ? (nameVal as string) : '',\r\n icon: (typeof iconVal == 'string') ? (iconVal as string) : ((typeof iconUrlVal == 'string') ? (iconUrlVal as string) : ''),\r\n description: (typeof descVal == 'string') ? (descVal as string) : '',\r\n color: (typeof colorVal == 'string') ? (colorVal as string) : '#4CAF50',\r\n parent_id: (typeof parentIdVal == 'string') ? (parentIdVal as string) : null,\r\n level: (typeof levelVal == 'number') ? (levelVal as number) : 0\r\n } as Category\r\n categories.push(cat)\r\n }\r\n return categories\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:347','获取分类异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 获取一级分类\r\n async getParentCategories(): Promise {\r\n try {\r\n const response = await supa\r\n .from('ml_categories')\r\n .select('*')\r\n .is('parent_id', null)\r\n .order('sort_order', { ascending: true })\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:363','获取一级分类失败:', response.error)\r\n return []\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) {\r\n return []\r\n }\r\n\r\n const categories: Category[] = []\r\n const rawList = rawData as Array\r\n for (let i = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const icon = this.getCategoryIcon(item)\r\n const cat: Category = {\r\n id: item['id'] as string,\r\n name: item['name'] as string,\r\n icon: icon,\r\n description: (item['description'] as string) ?? '',\r\n color: (item['color'] as string) ?? '#4CAF50',\r\n level: 1,\r\n slug: item['slug'] as string\r\n }\r\n categories.push(cat)\r\n }\r\n return categories\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:390','获取一级分类异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 获取子分类\r\n async getSubCategories(parentId: string): Promise {\r\n try {\r\n const response = await supa\r\n .from('ml_categories')\r\n .select('*')\r\n .eq('parent_id', parentId)\r\n .order('sort_order', { ascending: true })\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:406','获取子分类失败:', response.error)\r\n return []\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) {\r\n return []\r\n }\r\n\r\n const categories: Category[] = []\r\n const rawList = rawData as Array\r\n for (let i = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const icon = this.getCategoryIcon(item)\r\n const cat: Category = {\r\n id: item['id'] as string,\r\n name: item['name'] as string,\r\n icon: icon,\r\n description: (item['description'] as string) ?? '',\r\n color: (item['color'] as string) ?? '#4CAF50',\r\n level: 2,\r\n parent_id: item['parent_id'] as string,\r\n slug: item['slug'] as string\r\n }\r\n categories.push(cat)\r\n }\r\n return categories\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:434','获取子分类异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 获取分类图标的辅助方法\r\n getCategoryIcon(item: UTSJSONObject): string {\r\n const icon = item['icon'] as string | null\r\n if (icon != null && icon.length > 0) {\r\n return icon\r\n }\r\n const iconUrl = item['icon_url'] as string | null\r\n if (iconUrl != null && iconUrl.length > 0) {\r\n return iconUrl\r\n }\r\n // 根据分类名称返回默认图标\r\n const name = (item['name'] as string) ?? ''\r\n if (name.includes('数码') || name.includes('电器') || name.includes('手机')) return '📱'\r\n if (name.includes('服装') || name.includes('衣服') || name.includes('鞋')) return '👕'\r\n if (name.includes('食品') || name.includes('水果') || name.includes('零食')) return '🍎'\r\n if (name.includes('美妆') || name.includes('护肤') || name.includes('化妆')) return '💄'\r\n if (name.includes('母婴') || name.includes('婴儿') || name.includes('儿童')) return '👶'\r\n if (name.includes('家居') || name.includes('家具') || name.includes('装饰')) return '🏠'\r\n if (name.includes('图书') || name.includes('文具')) return '📚'\r\n if (name.includes('运动') || name.includes('户外') || name.includes('健身')) return '⚽'\r\n if (name.includes('医药') || name.includes('保健') || name.includes('健康')) return '💊'\r\n return '📦'\r\n }\r\n\r\n // 获取所有品牌\r\n async getBrands(): Promise {\r\n try {\r\n const response = await supa\r\n .from('ml_brands')\r\n .select('*')\r\n .eq('is_active', true)\r\n .order('name', { ascending: true })\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:474','获取品牌失败:', response.error)\r\n return []\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) {\r\n return []\r\n }\r\n \r\n const brands: Brand[] = []\r\n const rawList = rawData as any[]\r\n for (let i: number = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const brandObj = item as UTSJSONObject\r\n const idVal = brandObj.get('id')\r\n const nameVal = brandObj.get('name')\r\n const logoVal = brandObj.get('logo_url')\r\n const descVal = brandObj.get('description')\r\n \r\n const brand: Brand = {\r\n id: (typeof idVal == 'string') ? (idVal as string) : '',\r\n name: (typeof nameVal == 'string') ? (nameVal as string) : '',\r\n logo_url: (typeof logoVal == 'string') ? (logoVal as string) : '',\r\n description: (typeof descVal == 'string') ? (descVal as string) : ''\r\n } as Brand\r\n brands.push(brand)\r\n }\r\n return brands\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:503','获取品牌异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 获取指定分类的商品\r\n async getProductsByCategory(\r\n categoryId: string, \r\n page: number = 1, \r\n limit: number = 20\r\n ): Promise> {\r\n try {\r\n const response = await supa\r\n .from('ml_products_detail_view')\r\n .select('*', { count: 'exact' })\r\n .eq('category_id', categoryId)\r\n .eq('status', 1) \r\n .order('sale_count', { ascending: false })\r\n .page(page)\r\n .limit(limit)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:526','获取商品失败:', response.error)\r\n return {\r\n data: [] as Product[],\r\n total: 0,\r\n page,\r\n limit,\r\n hasmore: false\r\n }\r\n }\r\n \r\n return {\r\n data: response.data as Product[],\r\n total: response.total ?? 0,\r\n page,\r\n limit,\r\n hasmore: response.hasmore ?? false\r\n }\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:544','获取商品异常:', error)\r\n return {\r\n data: [] as Product[],\r\n total: 0,\r\n page,\r\n limit,\r\n hasmore: false\r\n }\r\n }\r\n }\r\n\r\n // 根据商品ID获取SKU列表\r\n async getProductSkus(productId: string): Promise {\r\n try {\r\n const response = await supa\r\n .from('ml_product_skus')\r\n .select('*')\r\n .eq('product_id', productId)\r\n .eq('status', 1)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:566','获取商品SKU失败:', response.error)\r\n return []\r\n }\r\n \r\n return response.data as ProductSku[]\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:572','获取商品SKU异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 搜索商品\r\n async searchProducts(\r\n keyword: string, \r\n page: number = 1, \r\n limit: number = 20,\r\n sortBy: string = 'sales',\r\n ascending: boolean = false\r\n ): Promise> {\r\n try {\r\n let query = supa\r\n .from('ml_products_detail_view')\r\n .select('*', { count: 'exact' })\r\n .eq('status', 1)\r\n .or(`name.ilike.%${keyword}%,description.ilike.%${keyword}%,subtitle.ilike.%${keyword}%,brand_name.ilike.%${keyword}%`)\r\n \r\n // 根据sortBy和ascending设置排序\r\n if (sortBy === 'price') {\r\n query = query.order('base_price', { ascending })\r\n } else if (sortBy === 'sales' || sortBy === 'sale_count') {\r\n query = query.order('sale_count', { ascending: false }) // 销量总是降序\r\n } else {\r\n // 默认按销量降序\r\n query = query.order('sale_count', { ascending: false })\r\n }\r\n \r\n const response = await query\r\n .page(page)\r\n .limit(limit)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:608','搜索商品失败:', response.error)\r\n return {\r\n data: [] as Product[],\r\n total: 0,\r\n page,\r\n limit,\r\n hasmore: false\r\n }\r\n }\r\n \r\n return {\r\n data: response.data as Product[],\r\n total: response.total ?? 0,\r\n page,\r\n limit,\r\n hasmore: response.hasmore ?? false\r\n }\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:626','搜索商品异常:', error)\r\n return {\r\n data: [] as Product[],\r\n total: 0,\r\n page,\r\n limit,\r\n hasmore: false\r\n }\r\n }\r\n }\r\n\r\n // 搜索店铺\r\n async searchShops(\r\n keyword: string,\r\n page: number = 1,\r\n limit: number = 20\r\n ): Promise> {\r\n try {\r\n const response = await supa\r\n .from('ml_shops')\r\n .select('*', { count: 'exact' })\r\n .eq('status', 1)\r\n .ilike('shop_name', `%${keyword}%`)\r\n .order('product_count', { ascending: false })\r\n .page(page)\r\n .limit(limit)\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:655','搜索店铺失败:', response.error)\r\n return { data: [] as Shop[], total: 0, page, limit, hasmore: false }\r\n }\r\n\r\n // 映射数据确保类型安全\r\n const shops: Shop[] = []\r\n const dataList = response.data as any[]\r\n for (let i = 0; i < dataList.length; i++) {\r\n shops.push(dataList[i] as Shop)\r\n }\r\n\r\n return {\r\n data: shops,\r\n total: response.total ?? 0,\r\n page,\r\n limit,\r\n hasmore: response.hasmore ?? false\r\n }\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:674','搜索店铺异常:', error)\r\n return { data: [] as Shop[], total: 0, page, limit, hasmore: false }\r\n }\r\n }\r\n\r\n // 获取单个商品详情\r\n async getProductById(productId: string): Promise {\r\n try {\r\n const response = await supa\r\n .from('ml_products_detail_view')\r\n .select('*')\r\n .eq('id', productId)\r\n .single()\r\n .executeAs()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:690','获取商品详情失败:', response.error)\r\n return null\r\n }\r\n \r\n return response.data as Product\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:696','获取商品详情异常:', error)\r\n return null\r\n }\r\n }\r\n\r\n // --- 关注店铺相关 ---\r\n\r\n // 检查是否已关注店铺\r\n async isShopFollowed(shopId: string, userId: string): Promise {\r\n try {\r\n const res = await supa\r\n .from('ml_shop_follows')\r\n .select('id', { count: 'exact' })\r\n .eq('shop_id', shopId)\r\n .eq('user_id', userId)\r\n .limit(1)\r\n .execute()\r\n \r\n return (res.total != null && res.total! > 0)\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:716','Check follow error:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 关注店铺\r\n async followShop(shopId: string, userId: string): Promise {\r\n try {\r\n const res = await supa\r\n .from('ml_shop_follows')\r\n .insert({\r\n user_id: userId,\r\n shop_id: shopId\r\n })\r\n .execute()\r\n \r\n return res.error == null\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:734','Follow shop error:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 取消关注\r\n async unfollowShop(shopId: string, userId: string): Promise {\r\n try {\r\n const res = await supa\r\n .from('ml_shop_follows')\r\n .eq('shop_id', shopId)\r\n .eq('user_id', userId)\r\n .delete()\r\n .execute()\r\n \r\n return res.error == null\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:751','Unfollow shop error:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 获取我关注的店铺列表\r\n async getFollowedShops(userId: string): Promise {\r\n try {\r\n // 关联查询店铺信息\r\n const res = await supa\r\n .from('ml_shop_follows')\r\n .select('*, ml_shops(*)') \r\n .eq('user_id', userId)\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n \r\n if (res.error != null) {\r\n __f__('error','at utils/supabaseService.uts:768','getFollowedShops error:', res.error)\r\n return []\r\n }\r\n \r\n return res.data as any[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:774','getFollowedShops exception:', e)\r\n return []\r\n }\r\n }\r\n\r\n // 根据商户ID获取店铺信息\r\n async getShopByMerchantId(merchantId: string): Promise {\r\n try {\r\n // 1. Try querying by merchant_id\r\n let response = await supa\r\n .from('ml_shops')\r\n .select('*')\r\n .eq('merchant_id', merchantId)\r\n .limit(1)\r\n .execute()\r\n \r\n if (response.error == null && response.data != null && (response.data as any[]).length > 0) {\r\n return (response.data as any[])[0] as Shop\r\n }\r\n\r\n // 2. Fallback: Try querying by id (Maybe the passed ID is the Shop ID?)\r\n __f__('log','at utils/supabaseService.uts:795','getShopByMerchantId: merchant_id not found, trying id...', merchantId)\r\n response = await supa\r\n .from('ml_shops')\r\n .select('*')\r\n .eq('id', merchantId)\r\n .limit(1)\r\n .execute()\r\n\r\n if (response.error == null && response.data != null && (response.data as any[]).length > 0) {\r\n __f__('log','at utils/supabaseService.uts:804','Found shop by ID instead of MerchantID')\r\n // Fix the merchant_id reference if we found it by ID\r\n const shop = (response.data as any[])[0] as Shop\r\n return shop\r\n }\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:811','获取店铺信息失败:', response.error)\r\n }\r\n return null\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:815','获取店铺信息异常:', error)\r\n return null\r\n }\r\n }\r\n\r\n // 根据商户ID获取商品列表\r\n async getProductsByMerchantId(merchantId: string, page: number = 1, limit: number = 20): Promise> {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:823','getProductsByMerchantId querying for:', merchantId)\r\n \r\n // 1. Try fetching from view strictly first\r\n let query = supa\r\n .from('ml_products_detail_view')\r\n .select('*', { count: 'exact' })\r\n .eq('merchant_id', merchantId)\r\n // .eq('status', 1) // Temporarily disabled status check to see if products exist at all\r\n .order('created_at', { ascending: false })\r\n .page(page)\r\n .limit(limit)\r\n \r\n const response = await query.execute()\r\n \r\n // 检查视图结果:如果有错误 OR 数据为空,都尝试去查原始表\r\n if (response.error != null || (response.data != null && (response.data as any[]).length === 0)) {\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:840','获取商户商品失败 (View):', response.error)\r\n } else {\r\n __f__('log','at utils/supabaseService.uts:842','View returned 0 products, trying raw table fallback...')\r\n }\r\n \r\n // Fallback: Try raw table\r\n __f__('log','at utils/supabaseService.uts:846','Falling back to raw ml_products table...')\r\n const query2 = supa\r\n .from('ml_products')\r\n .select('*', { count: 'exact' })\r\n .eq('merchant_id', merchantId)\r\n // .eq('status', 1) // Also disabled here\r\n .order('created_at', { ascending: false })\r\n .page(page)\r\n .limit(limit)\r\n \r\n const res2 = await query2.execute()\r\n if (res2.error != null) {\r\n __f__('error','at utils/supabaseService.uts:858','获取商户商品失败 (Raw):', res2.error)\r\n return {data:[] as Product[], total:0, page, limit, hasmore:false}\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:862',`Fallback (Raw) found: ${(res2.data as any[]).length} products`)\r\n \r\n // Map raw data to Product interface (manually if needed for extra safety)\r\n const mappedData: Product[] = []\r\n const rawData = res2.data as any[]\r\n for(let i = 0; i < rawData.length; i++) {\r\n mappedData.push(rawData[i] as Product)\r\n }\r\n\r\n return {\r\n data: mappedData,\r\n total: res2.total ?? 0,\r\n page,\r\n limit,\r\n hasmore: res2.hasmore ?? false\r\n }\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:880',`Merchant products found: ${(response.data as any[]).length}`)\r\n return {\r\n data: response.data as Product[],\r\n total: response.total ?? 0,\r\n page,\r\n limit,\r\n hasmore: response.hasmore ?? false\r\n }\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:889','获取商户商品异常:', error)\r\n return {\r\n data: [] as Product[],\r\n total: 0,\r\n page,\r\n limit,\r\n hasmore: false\r\n }\r\n }\r\n }\r\n\r\n // 获取热销商品(按销量排序)\r\n async getHotProducts(limit: number = 10): Promise {\r\n try {\r\n const response = await supa\r\n .from('ml_products_detail_view')\r\n .select('*')\r\n .eq('is_hot', true)\r\n .eq('status', 1)\r\n .order('sale_count', { ascending: false })\r\n .limit(limit)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:913','获取热销商品失败:', response.error)\r\n return []\r\n }\r\n \r\n return response.data as Product[]\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:919','获取热销商品异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 获取按价格排序的商品(升序:从低到高)\r\n async getProductsByPrice(limit: number = 10, ascending: boolean = true): Promise {\r\n try {\r\n const response = await supa\r\n .from('ml_products_detail_view')\r\n .select('*')\r\n .eq('status', 1)\r\n .order('base_price', { ascending })\r\n .limit(limit)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:936','获取价格排序商品失败:', response.error)\r\n return []\r\n }\r\n \r\n return response.data as Product[]\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:942','获取价格排序商品异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 获取新品(按创建时间排序,最新的在前)\r\n async getProductsByNewest(limit: number = 10): Promise {\r\n try {\r\n const response = await supa\r\n .from('ml_products_detail_view')\r\n .select('*')\r\n .eq('is_new', true) \r\n .eq('status', 1)\r\n .order('published_at', { ascending: false }) // Use published_at for newest\r\n .limit(limit)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:960','获取新品失败:', response.error)\r\n return []\r\n }\r\n \r\n return response.data as Product[]\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:966','获取新品异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 获取推荐商品(is_featured=true)\r\n async getRecommendedProducts(limit: number = 10): Promise {\r\n try {\r\n // 查询 is_featured = true 的商品\r\n const response = await supa\r\n .from('ml_products_detail_view')\r\n .select('*')\r\n .eq('is_featured', true)\r\n .eq('status', 1)\r\n .order('sale_count', { ascending: false })\r\n .limit(limit)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:985','获取推荐商品失败:', response.error)\r\n return []\r\n }\r\n \r\n const rawData = response.data\r\n if (rawData == null) {\r\n return []\r\n }\r\n \r\n const products: Product[] = []\r\n const rawList = rawData as any[]\r\n for (let i: number = 0; i < rawList.length; i++) {\r\n const item = rawList[i]\r\n const prodObj = item as UTSJSONObject\r\n const imagesRaw = prodObj.getArray('images')\r\n const product: Product = {\r\n id: prodObj.getString('id') ?? '',\r\n name: prodObj.getString('name') ?? '',\r\n description: prodObj.getString('description') ?? '',\r\n price: prodObj.getNumber('price') ?? 0,\r\n original_price: prodObj.getNumber('original_price') ?? 0,\r\n image_url: prodObj.getString('image_url') ?? '',\r\n images: imagesRaw != null ? (imagesRaw as string[]) : [] as string[],\r\n category_id: prodObj.getString('category_id') ?? '',\r\n brand_id: prodObj.getString('brand_id') ?? '',\r\n shop_id: prodObj.getString('shop_id') ?? '',\r\n stock: prodObj.getNumber('stock') ?? 0,\r\n sale_count: prodObj.getNumber('sale_count') ?? 0,\r\n status: prodObj.getNumber('status') ?? 1,\r\n is_featured: prodObj.getBoolean('is_featured') ?? false,\r\n is_new: prodObj.getBoolean('is_new') ?? false,\r\n rating: prodObj.getNumber('rating') ?? 0,\r\n review_count: prodObj.getNumber('review_count') ?? 0,\r\n merchant_id: ''\r\n } as Product\r\n products.push(product)\r\n }\r\n return products\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1024','获取推荐商品异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 获取特价商品(这里假设没有specific flag, just use logic or tag if exists, defaulting to hot for now or just skip)\r\n // Modify to use compatible logic if badge column doesn't exist\r\n async getDiscountProducts(limit: number = 10): Promise {\r\n return [] as Product[] // 暂无特价字段\r\n }\r\n\r\n // 获取当前用户的购物车商品(关联商品和店铺信息)\r\n async getCartItems(): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('warn','at utils/supabaseService.uts:1040','用户未登录,无法获取购物车')\r\n return []\r\n }\r\n\r\n // 查询购物车表,并关联商品表(使用内联关联)\r\n // 注意:使用 !inner 进行内连接,或者 left join (默认)\r\n // 修改查询语法以符合 PostgREST 规范\r\n // 尝试简化查询,先只查购物车,再查商品,避免复杂的嵌套查询导致 400 错误\r\n const response = await supa\r\n .from('ml_shopping_cart')\r\n .select('*')\r\n .eq('user_id', userId)\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1056','获取购物车失败:', response.error)\r\n return []\r\n }\r\n \r\n const cartData = response.data as any[]\r\n // __f__('log','at utils/supabaseService.uts:1061','Raw Cart Data:', JSON.stringify(cartData))\r\n \r\n if (cartData == null || cartData.length === 0) {\r\n return []\r\n }\r\n\r\n // 收集所有 product_id 和 sku_id\r\n const productIds: string[] = []\r\n const skuIds: string[] = []\r\n for (let i = 0; i < cartData.length; i++) {\r\n let item = cartData[i]\r\n let pid: string = ''\r\n let sid: string = ''\r\n if (item instanceof UTSJSONObject) {\r\n pid = item.getString('product_id') ?? ''\r\n sid = item.getString('sku_id') ?? ''\r\n } else {\r\n const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n pid = itemObj.getString('product_id') ?? ''\r\n sid = itemObj.getString('sku_id') ?? ''\r\n }\r\n if (pid !== '' && !productIds.includes(pid)) {\r\n productIds.push(pid)\r\n }\r\n if (sid !== '' && !skuIds.includes(sid)) {\r\n skuIds.push(sid)\r\n }\r\n }\r\n\r\n // 批量查询商品详情 (使用视图关联店铺信息,修复字段名 specification -> attributes)\r\n const productMap = new Map()\r\n \r\n if (productIds.length > 0) {\r\n // Convert string array to any array for .in()\r\n const productIdsAny: any[] = []\r\n for(let i=0; i()\r\n if (skuIds.length > 0) {\r\n const skuIdsAny: any[] = []\r\n for(let i=0; i 0) {\r\n productPrice = skuPrice\r\n }\r\n const skuImg = sku.getString('image_url')\r\n if (skuImg != null && skuImg !== '') {\r\n productImage = skuImg\r\n }\r\n \r\n const specRaw = sku.get('specifications')\r\n if (specRaw != null) {\r\n // 优先使用SKU的规格\r\n if (typeof specRaw === 'string') {\r\n productSpec = specRaw\r\n } else if (specRaw instanceof UTSJSONObject) {\r\n const keys = UTSJSONObject.keys(specRaw)\r\n const parts: string[] = []\r\n for(let k = 0; k < keys.length; k++) {\r\n let val = specRaw.get(keys[k])\r\n if (val != null) {\r\n parts.push(`${keys[k]}: ${val}`)\r\n }\r\n }\r\n productSpec = parts.join('; ')\r\n } else {\r\n try {\r\n let jsonStr = JSON.stringify(specRaw)\r\n productSpec = jsonStr.replace(/[\"{}]/g, '').replace(/,/g, '; ')\r\n } catch (e) {}\r\n }\r\n }\r\n } else {\r\n const sObj = JSON.parse(JSON.stringify(sku)) as UTSJSONObject\r\n const skuPrice = sObj.getNumber('price') ?? 0\r\n if (skuPrice > 0) productPrice = skuPrice\r\n\r\n const skuImg = sObj.getString('image_url') ?? ''\r\n if (skuImg !== '') productImage = skuImg\r\n\r\n const specRaw = sObj.get('specifications')\r\n if (specRaw != null) {\r\n // 优先使用SKU的规格\r\n if (typeof specRaw === 'string') {\r\n productSpec = specRaw\r\n } else if (specRaw instanceof UTSJSONObject) {\r\n const keys = UTSJSONObject.keys(specRaw)\r\n const parts: string[] = []\r\n for(let k = 0; k < keys.length; k++) {\r\n let val = specRaw.get(keys[k])\r\n if (val != null) {\r\n parts.push(`${keys[k]}: ${val}`)\r\n }\r\n }\r\n productSpec = parts.join('; ')\r\n } else {\r\n try {\r\n let jsonStr = JSON.stringify(specRaw)\r\n productSpec = jsonStr.replace(/[\"{}]/g, '').replace(/,/g, '; ')\r\n } catch (e) {}\r\n }\r\n }\r\n }\r\n }\r\n\r\n \r\n \r\n let shopIdStr = merchantId != '' ? merchantId : 'unknown_shop'\r\n\r\n \r\n cartItems.push({\r\n id: itemId,\r\n user_id: userIdVal,\r\n product_id: productId,\r\n sku_id: skuId,\r\n merchant_id: merchantId,\r\n quantity: quantity,\r\n selected: selected,\r\n product_name: productName,\r\n product_image: productImage,\r\n product_price: productPrice,\r\n product_specification: productSpec,\r\n shop_id: shopIdStr,\r\n shop_name: shopNameStr,\r\n created_at: createdAt,\r\n updated_at: updatedAt\r\n } as CartItem)\r\n }\r\n }\r\n \r\n return cartItems\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1371','获取购物车异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 获取用户通知 (系统、活动、订单)\r\n async getUserNotifications(type: string | null = null): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return []\r\n\r\n let query = supa\r\n .from('ml_notifications')\r\n .select('*')\r\n .eq('user_id', userId)\r\n \r\n if (type != null) {\r\n query = query.eq('type', type)\r\n }\r\n \r\n const response = await query.order('created_at', { ascending: false }).execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1394','获取通知失败:', response.error)\r\n return []\r\n }\r\n return response.data as Notification[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:1399','获取通知异常:', e)\r\n return []\r\n }\r\n }\r\n\r\n // 获取聊天会话列表\r\n async getChatRooms(): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return []\r\n\r\n const response = await supa\r\n .from('ml_chat_rooms')\r\n .select('*')\r\n .eq('user_id', userId)\r\n .order('updated_at', { ascending: false })\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1418','获取聊天会话失败:', response.error)\r\n return []\r\n }\r\n return response.data as ChatRoom[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:1423','获取聊天会话异常:', e)\r\n return []\r\n }\r\n }\r\n\r\n // 获取用户聊天消息\r\n async getUserChatMessages(): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return []\r\n\r\n const response = await supa\r\n .from('ml_chat_messages')\r\n .select('*')\r\n .or(`sender_id.eq.${userId},receiver_id.eq.${userId}`)\r\n .order('created_at', { ascending: false })\r\n .limit(50)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1443','获取聊天记录失败:', response.error)\r\n return []\r\n }\r\n return response.data as ChatMessage[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:1448','获取聊天记录异常:', e)\r\n return []\r\n }\r\n }\r\n\r\n // 获取与特定商家的聊天记录\r\n async getChatMessages(merchantId: string): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return []\r\n\r\n const response = await supa\r\n .from('ml_chat_messages')\r\n .select('*')\r\n .or(`and(sender_id.eq.${userId},receiver_id.eq.${merchantId}),and(sender_id.eq.${merchantId},receiver_id.eq.${userId})`)\r\n .order('created_at', { ascending: false })\r\n .limit(50)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1468','获取聊天记录失败:', response.error)\r\n return []\r\n }\r\n return response.data as ChatMessage[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:1473','获取聊天记录异常:', e)\r\n return []\r\n }\r\n }\r\n\r\n // 发送聊天消息\r\n async sendChatMessage(content: string, toId: string | null = null, type: string = 'text'): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return false\r\n \r\n const payload = {\r\n sender_id: userId,\r\n content: content,\r\n msg_type: type,\r\n is_from_user: true,\r\n created_at: new Date().toISOString()\r\n } as UTSJSONObject\r\n if (toId != null) {\r\n payload.set('receiver_id', toId)\r\n }\r\n\r\n const response = await supa\r\n .from('ml_chat_messages')\r\n .insert(payload)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1501','发送消息失败:', response.error)\r\n return false\r\n }\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:1506','发送消息异常:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 模拟客服回复\r\n async simulateServiceReply(content: string): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return false\r\n\r\n const response = await supa\r\n .from('ml_chat_messages')\r\n .insert({\r\n receiver_id: userId,\r\n content: content,\r\n msg_type: 'text',\r\n is_from_user: false,\r\n created_at: new Date().toISOString()\r\n })\r\n .execute()\r\n return response.error == null\r\n } catch (e) {\r\n return false\r\n }\r\n }\r\n\r\n // 添加商品到购物车\r\n async addToCart(productId: string, quantity: number = 1, skuId?: string): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:1538','用户未登录,无法添加商品到购物车')\r\n return false\r\n }\r\n \r\n const realSkuId = (skuId != null && skuId.length > 0) ? skuId : null\r\n\r\n // 检查商品是否已在购物车中\r\n // 注意:必须处理 sku_id 为空的情况,使用 is.null 过滤器\r\n let query = supa\r\n .from('ml_shopping_cart')\r\n .select('*')\r\n .eq('user_id', userId)\r\n .eq('product_id', productId)\r\n \r\n if (realSkuId != null) {\r\n query = query.eq('sku_id', realSkuId)\r\n } else {\r\n query = query.is('sku_id', null)\r\n }\r\n\r\n const existingResponse = await query.single().execute()\r\n\r\n let existingItem: any | null = null\r\n \r\n if (existingResponse.data != null) {\r\n const rawData = existingResponse.data as any\r\n if (Array.isArray(rawData)) {\r\n if (rawData.length > 0) {\r\n existingItem = rawData[0]\r\n }\r\n } else {\r\n existingItem = rawData\r\n }\r\n }\r\n\r\n let response: AkReqResponse\r\n if (existingItem != null) {\r\n // 商品已存在,更新数量\r\n __f__('log','at utils/supabaseService.uts:1576','Found existing cart item:', JSON.stringify(existingItem))\r\n\r\n // 确保 existingItem.id 存在\r\n let itemId: string | null = null\r\n let itemQty: any | null = null\r\n\r\n if (existingItem instanceof UTSJSONObject) {\r\n itemId = existingItem.getString('id')\r\n itemQty = existingItem.getNumber('quantity')\r\n } else {\r\n const obj = JSON.parse(JSON.stringify(existingItem)) as UTSJSONObject\r\n itemId = obj.getString('id')\r\n itemQty = obj.getNumber('quantity')\r\n }\r\n\r\n if (itemId != null) {\r\n let currentQty = 0\r\n if (typeof itemQty === 'number') {\r\n currentQty = itemQty as number\r\n } else {\r\n const qStr = `${itemQty ?? 0}`\r\n currentQty = parseInt(qStr)\r\n }\r\n const newQty = currentQty + quantity\r\n\r\n response = await supa\r\n .from('ml_shopping_cart')\r\n .update({\r\n quantity: newQty,\r\n updated_at: new Date().toISOString()\r\n })\r\n .eq('id', itemId)\r\n .execute()\r\n } else {\r\n __f__('error','at utils/supabaseService.uts:1610','购物车已有商品但缺少ID,无法更新. Data:', JSON.stringify(existingItem))\r\n return false\r\n }\r\n } else {\r\n // 商品不存在,添加新记录\r\n response = await supa\r\n .from('ml_shopping_cart')\r\n .insert({\r\n user_id: userId,\r\n product_id: productId,\r\n sku_id: realSkuId,\r\n quantity: quantity,\r\n selected: true,\r\n created_at: new Date().toISOString(),\r\n updated_at: new Date().toISOString()\r\n })\r\n .execute()\r\n }\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1630','添加商品到购物车失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1636','添加商品到购物车异常:', error)\r\n return false\r\n }\r\n }\r\n\r\n // 更新购物车商品数量\r\n async updateCartItemQuantity(cartItemId: string, quantity: number): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:1646','用户未登录,无法更新购物车')\r\n return false\r\n }\r\n\r\n if (quantity < 1) {\r\n // 数量小于1时删除商品\r\n return await this.deleteCartItem(cartItemId)\r\n }\r\n\r\n const response = await supa\r\n .from('ml_shopping_cart')\r\n .update({\r\n quantity: quantity,\r\n updated_at: new Date().toISOString()\r\n })\r\n .eq('id', cartItemId)\r\n .eq('user_id', userId)\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1666','更新购物车商品数量失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1672','更新购物车商品数量异常:', error)\r\n return false\r\n }\r\n }\r\n\r\n // 更新购物车商品选中状态\r\n async updateCartItemSelection(cartItemId: string, selected: boolean): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:1682','用户未登录,无法更新购物车')\r\n return false\r\n }\r\n\r\n const response = await supa\r\n .from('ml_shopping_cart')\r\n .update({\r\n selected: selected,\r\n updated_at: new Date().toISOString()\r\n })\r\n .eq('id', cartItemId)\r\n .eq('user_id', userId)\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1697','更新购物车商品选中状态失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1703','更新购物车商品选中状态异常:', error)\r\n return false\r\n }\r\n }\r\n\r\n // 批量更新购物车商品选中状态\r\n async batchUpdateCartItemSelection(cartItemIds: string[], selected: boolean): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:1713','用户未登录,无法更新购物车')\r\n return false\r\n }\r\n\r\n const response = await supa\r\n .from('ml_shopping_cart')\r\n .update({\r\n selected: selected,\r\n updated_at: new Date().toISOString()\r\n })\r\n .eq('user_id', userId)\r\n .in('id', cartItemIds as any[])\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1728','批量更新购物车商品选中状态失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1734','批量更新购物车商品选中状态异常:', error)\r\n return false\r\n }\r\n }\r\n\r\n // 删除购物车商品\r\n async deleteCartItem(cartItemId: string): Promise {\r\n return true\r\n /*\r\n try {\r\n __f__('log','at utils/supabaseService.uts:1744','正在执行删除购物车商品,ID:', cartItemId)\r\n const userId = this.getCurrentUserId()\r\n if (!userId) {\r\n __f__('error','at utils/supabaseService.uts:1747','用户未登录,无法删除购物车商品')\r\n return false\r\n }\r\n\r\n const response = await supa\r\n .from('ml_shopping_cart')\r\n .eq('id', cartItemId)\r\n .eq('user_id', userId)\r\n .delete()\r\n .execute()\r\n\r\n if (response.error) {\r\n __f__('error','at utils/supabaseService.uts:1759','删除购物车商品失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1765','删除购物车商品异常:', error)\r\n return false\r\n }\r\n */\r\n }\r\n\r\n // 批量删除购物车商品\r\n async batchDeleteCartItems(cartItemIds: string[]): Promise {\r\n return true\r\n /*\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (!userId) {\r\n __f__('error','at utils/supabaseService.uts:1778','用户未登录,无法删除购物车商品')\r\n return false\r\n }\r\n\r\n const response = await supa\r\n .from('ml_shopping_cart')\r\n .eq('user_id', userId)\r\n .in('id', cartItemIds)\r\n .delete()\r\n .execute()\r\n\r\n if (response.error) {\r\n __f__('error','at utils/supabaseService.uts:1790','批量删除购物车商品失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1796','批量删除购物车商品异常:', error)\r\n return false\r\n }\r\n */\r\n }\r\n\r\n // 清空购物车\r\n async clearCart(): Promise {\r\n return true\r\n /*\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (!userId) {\r\n __f__('error','at utils/supabaseService.uts:1809','用户未登录,无法清空购物车')\r\n return false\r\n }\r\n\r\n const response = await supa\r\n .from('ml_shopping_cart')\r\n .eq('user_id', userId)\r\n .delete()\r\n .execute()\r\n\r\n if (response.error) {\r\n __f__('error','at utils/supabaseService.uts:1820','清空购物车失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1826','清空购物车异常:', error)\r\n return false\r\n }\r\n */\r\n }\r\n\r\n // 获取当前用户的所有地址\r\n async getAddresses(): Promise {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('warn','at utils/supabaseService.uts:1836','[getAddresses] 用户未登录,无法获取地址')\r\n return []\r\n }\r\n\r\n try {\r\n __f__('log','at utils/supabaseService.uts:1841','[getAddresses] 查询地址, userId:', userId)\r\n \r\n const response = await supa\r\n .from('ml_user_addresses')\r\n .select('*')\r\n .eq('user_id', userId)\r\n .order('is_default', { ascending: false })\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n \r\n __f__('log','at utils/supabaseService.uts:1851','[getAddresses] response.error:', response.error)\r\n __f__('log','at utils/supabaseService.uts:1852','[getAddresses] response.data:', JSON.stringify(response.data))\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1855','[getAddresses] 获取地址失败:', response.error)\r\n return []\r\n }\r\n \r\n const data = response.data\r\n if (data == null) {\r\n return []\r\n }\r\n \r\n const result: UserAddress[] = []\r\n const rawData = data as any[]\r\n for (let i = 0; i < rawData.length; i++) {\r\n const item = rawData[i]\r\n let itemObj: UTSJSONObject\r\n if (item instanceof UTSJSONObject) {\r\n itemObj = item as UTSJSONObject\r\n } else {\r\n itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n }\r\n \r\n const addrObj = new UTSJSONObject()\r\n addrObj.set('id', itemObj.getString('id') ?? '')\r\n addrObj.set('user_id', itemObj.getString('user_id') ?? '')\r\n addrObj.set('recipient_name', itemObj.getString('receiver_name') ?? itemObj.getString('recipient_name') ?? '')\r\n addrObj.set('phone', itemObj.getString('receiver_phone') ?? itemObj.getString('phone') ?? '')\r\n addrObj.set('province', itemObj.getString('province') ?? '')\r\n addrObj.set('city', itemObj.getString('city') ?? '')\r\n addrObj.set('district', itemObj.getString('district') ?? '')\r\n addrObj.set('detail_address', itemObj.getString('address_detail') ?? itemObj.getString('detail_address') ?? '')\r\n addrObj.set('is_default', itemObj.getBoolean('is_default') ?? false)\r\n result.push(addrObj as UserAddress)\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:1888','[getAddresses] 返回地址数量:', result.length)\r\n return result\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1891','[getAddresses] 获取地址异常:', error)\r\n return []\r\n }\r\n }\r\n\r\n // 根据ID获取地址详情\r\n async getAddressById(addressId: string): Promise {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('warn','at utils/supabaseService.uts:1900','用户未登录,无法获取地址')\r\n return null\r\n }\r\n\r\n try {\r\n const query = supa\r\n .from('ml_user_addresses')\r\n .select('*, recipient_name:receiver_name, phone:receiver_phone, detail_address:address_detail')\r\n .eq('id', addressId)\r\n .eq('user_id', userId)\r\n .single()\r\n \r\n const response = await query.execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1915','获取地址详情失败:', response.error)\r\n return null\r\n }\r\n \r\n return response.data as UserAddress\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1921','获取地址详情异常:', error)\r\n return null\r\n }\r\n }\r\n\r\n // 添加新地址\r\n async addAddress(address: AddAddressParams): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:1931','用户未登录,无法添加地址')\r\n return false\r\n }\r\n\r\n // 如果设置为默认地址,需要先取消其他默认地址\r\n if (address.is_default == true) {\r\n await this.clearDefaultAddress(userId)\r\n }\r\n\r\n const response = await supa\r\n .from('ml_user_addresses')\r\n .insert({\r\n user_id: userId,\r\n receiver_name: address.recipient_name,\r\n receiver_phone: address.phone,\r\n province: address.province,\r\n city: address.city,\r\n district: address.district,\r\n address_detail: address.detail_address,\r\n postal_code: address.postal_code ?? null,\r\n is_default: address.is_default ?? false,\r\n created_at: new Date().toISOString(),\r\n updated_at: new Date().toISOString()\r\n })\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1958','添加地址失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1964','添加地址异常:', error)\r\n return false\r\n }\r\n }\r\n\r\n // 更新地址\r\n async updateAddress(addressId: string, address: UpdateAddressParams): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:1974','用户未登录,无法更新地址')\r\n return false\r\n }\r\n\r\n // 如果设置为默认地址,需要先取消其他默认地址\r\n if (address.is_default == true) {\r\n await this.clearDefaultAddress(userId)\r\n }\r\n \r\n // 构造更新数据,映射字段名到数据库列名\r\n const updateData = {}\r\n if (address.recipient_name != null) updateData['receiver_name'] = address.recipient_name\r\n if (address.phone != null) updateData['receiver_phone'] = address.phone\r\n if (address.province != null) updateData['province'] = address.province\r\n if (address.city != null) updateData['city'] = address.city\r\n if (address.district != null) updateData['district'] = address.district\r\n if (address.detail_address != null) updateData['address_detail'] = address.detail_address\r\n if (address.postal_code != null) updateData['postal_code'] = address.postal_code\r\n if (address.is_default != null) updateData['is_default'] = address.is_default\r\n if (address.label != null) updateData['label'] = address.label\r\n updateData['updated_at'] = new Date().toISOString()\r\n\r\n const response = await supa\r\n .from('ml_user_addresses')\r\n .update(updateData)\r\n .eq('id', addressId)\r\n .eq('user_id', userId)\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2004','更新地址失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:2010','更新地址异常:', error)\r\n return false\r\n }\r\n }\r\n \r\n // 确认收货\r\n async confirmReceipt(orderId: string): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n return { success: false, error: '用户未登录' }\r\n }\r\n\r\n const response = await supa\r\n .from('ml_orders')\r\n .update({\r\n order_status: 4, // 4: 已完成\r\n delivered_at: new Date().toISOString(),\r\n completed_at: new Date().toISOString(), // 也更新完成时间\r\n updated_at: new Date().toISOString()\r\n })\r\n .eq('id', orderId)\r\n .eq('user_id', userId)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n return { success: false, error: response.error.message }\r\n }\r\n \r\n return { success: true }\r\n } catch (e: any) {\r\n return { success: false, error: e.message }\r\n }\r\n }\r\n\r\n // 删除地址\r\n async deleteAddress(addressId: string): Promise {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:2048','正在执行删除地址,ID:', addressId)\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:2051','用户未登录,无法删除地址')\r\n return false\r\n }\r\n\r\n const response = await supa\r\n .from('ml_user_addresses')\r\n .eq('id', addressId)\r\n .eq('user_id', userId)\r\n .delete()\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2063','删除地址失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:2069','删除地址异常:', error)\r\n return false\r\n }\r\n }\r\n\r\n // 清除默认地址(内部使用)\r\n private async clearDefaultAddress(userId: string): Promise {\r\n try {\r\n await supa\r\n .from('ml_user_addresses')\r\n .update({\r\n is_default: false,\r\n updated_at: new Date().toISOString()\r\n })\r\n .eq('user_id', userId)\r\n .eq('is_default', true)\r\n .execute()\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:2087','清除默认地址异常:', error)\r\n }\r\n }\r\n\r\n // 获取用户资料\r\n async getUserProfile(): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return null\r\n \r\n // 联合查询 auth user 和 profile\r\n // 由于 Supabase auth table 不可直接访问,这里查询 ml_user_profiles\r\n const response = await supa\r\n .from('ml_user_profiles')\r\n .select('*')\r\n .eq('user_id', userId)\r\n .single()\r\n .execute()\r\n \r\n if (response.error != null) {\r\n // 如果不存在 profile,可能只有 auth user,这里暂时返回空或创建默认\r\n return null\r\n }\r\n return response.data\r\n } catch (e) {\r\n return null\r\n }\r\n }\r\n \r\n // 创建订单\r\n async createOrder(orderData: CreateOrderParams): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:2121','CreateOrder: User not logged in')\r\n return null\r\n }\r\n \r\n const orderNo = 'ML' + Date.now() + Math.floor(Math.random() * 1000)\r\n \r\n let merchantId = orderData.merchant_id\r\n if (merchantId == null || merchantId == '' || merchantId == 'unknown') {\r\n merchantId = userId\r\n }\r\n \r\n let shippingAddrStr = '{}'\r\n if (orderData.shipping_address != null) {\r\n if (typeof orderData.shipping_address === 'string') {\r\n shippingAddrStr = orderData.shipping_address\r\n } else {\r\n shippingAddrStr = JSON.stringify(orderData.shipping_address)\r\n }\r\n }\r\n \r\n const orderPayload = new UTSJSONObject()\r\n orderPayload.set('user_id', userId)\r\n orderPayload.set('merchant_id', merchantId)\r\n orderPayload.set('order_no', orderNo)\r\n orderPayload.set('product_amount', orderData.product_amount)\r\n orderPayload.set('shipping_fee', orderData.shipping_fee)\r\n orderPayload.set('total_amount', orderData.total_amount)\r\n orderPayload.set('paid_amount', 0)\r\n orderPayload.set('shipping_address', shippingAddrStr)\r\n orderPayload.set('order_status', 1)\r\n orderPayload.set('payment_status', 1)\r\n orderPayload.set('shipping_status', 1)\r\n orderPayload.set('created_at', new Date().toISOString())\r\n orderPayload.set('updated_at', new Date().toISOString())\r\n \r\n __f__('log','at utils/supabaseService.uts:2156','[CreateOrder] 插入订单数据:', JSON.stringify(orderPayload))\r\n __f__('log','at utils/supabaseService.uts:2157','[CreateOrder] 期望的订单号:', orderNo)\r\n \r\n const orderResponse = await supa\r\n .from('ml_orders')\r\n .insert(orderPayload)\r\n .execute()\r\n \r\n __f__('log','at utils/supabaseService.uts:2164','[CreateOrder] insert 完成')\r\n __f__('log','at utils/supabaseService.uts:2165','[CreateOrder] orderResponse.error:', orderResponse.error)\r\n \r\n if (orderResponse.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2168','[CreateOrder] 创建订单失败:', orderResponse.error)\r\n return null\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:2172','[CreateOrder] 开始查询新创建的订单, order_no:', orderNo)\r\n \r\n const queryResponse = await supa\r\n .from('ml_orders')\r\n .select('id, order_no')\r\n .eq('order_no', orderNo)\r\n .execute()\r\n \r\n __f__('log','at utils/supabaseService.uts:2180','[CreateOrder] queryResponse.error:', queryResponse.error)\r\n __f__('log','at utils/supabaseService.uts:2181','[CreateOrder] queryResponse.data:', JSON.stringify(queryResponse.data))\r\n \r\n if (queryResponse.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2184','[CreateOrder] 查询订单失败:', queryResponse.error)\r\n return null\r\n }\r\n \r\n const queryData = queryResponse.data as any\r\n let orderId = ''\r\n \r\n if (Array.isArray(queryData) && queryData.length > 0) {\r\n const firstItem = queryData[0] as Record\r\n orderId = firstItem['id'] as string\r\n __f__('log','at utils/supabaseService.uts:2194','[CreateOrder] 找到新创建的订单, id:', orderId)\r\n } else {\r\n __f__('error','at utils/supabaseService.uts:2196','[CreateOrder] 未找到新创建的订单,插入可能失败')\r\n return null\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:2200','[CreateOrder] 订单创建成功, orderId:', orderId)\r\n \r\n const orderItems: UTSJSONObject[] = []\r\n const rawItems = orderData.items as any[]\r\n \r\n for(let i = 0; i < rawItems.length; i++) {\r\n let item: UTSJSONObject\r\n const rawItem = rawItems[i]\r\n item = rawItem as UTSJSONObject\r\n\r\n const itemJson = new UTSJSONObject()\r\n \r\n let pId = item.get('product_id')\r\n if (pId == null) {\r\n pId = item.get('id')\r\n }\r\n \r\n itemJson.set('order_id', orderId)\r\n itemJson.set('product_id', pId)\r\n \r\n const skuIdVal = item.get('sku_id')\r\n if (skuIdVal != null && skuIdVal !== '') {\r\n itemJson.set('sku_id', skuIdVal)\r\n }\r\n \r\n itemJson.set('product_name', item.get('product_name') ?? '')\r\n \r\n const sName = item.get('sku_name')\r\n itemJson.set('sku_name', sName ?? '')\r\n \r\n const specVal = item.get('specifications')\r\n let skuSnapshot = '{}'\r\n if (specVal != null) {\r\n if (typeof specVal === 'string') {\r\n skuSnapshot = specVal as string\r\n } else {\r\n skuSnapshot = JSON.stringify(specVal)\r\n }\r\n }\r\n itemJson.set('sku_snapshot', skuSnapshot)\r\n itemJson.set('specifications', skuSnapshot)\r\n \r\n const img1 = item.get('product_image')\r\n const img2 = item.get('image_url')\r\n let imgUrl = (img1 ?? img2 ?? '') as string\r\n while (imgUrl.indexOf('`') >= 0) {\r\n imgUrl = imgUrl.replace('`', '')\r\n }\r\n itemJson.set('image_url', imgUrl)\r\n\r\n const iPrice = item.getNumber('price') ?? 0\r\n const iQty = item.getNumber('quantity') ?? 1\r\n itemJson.set('price', iPrice)\r\n itemJson.set('quantity', iQty)\r\n itemJson.set('total_amount', iPrice * iQty)\r\n itemJson.set('created_at', new Date().toISOString())\r\n \r\n orderItems.push(itemJson)\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:2260','[CreateOrder] 插入订单项数量:', orderItems.length)\r\n __f__('log','at utils/supabaseService.uts:2261','[CreateOrder] 订单项数据:', JSON.stringify(orderItems))\r\n \r\n for (let j: number = 0; j < orderItems.length; j++) {\r\n const itemJson = orderItems[j]\r\n const itemsResponse = await supa\r\n .from('ml_order_items')\r\n .insert(itemJson)\r\n .execute()\r\n \r\n if (itemsResponse.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2271','[CreateOrder] 创建订单项失败:', itemsResponse.error)\r\n }\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:2275','[CreateOrder] 订单项创建成功')\r\n \r\n const cartItemIds: string[] = []\r\n for(let i = 0; i < rawItems.length; i++) {\r\n const item = rawItems[i] as UTSJSONObject\r\n const iid = item.getString('id')\r\n if (iid != null && iid.length > 10) {\r\n cartItemIds.push(iid)\r\n }\r\n }\r\n \r\n if (cartItemIds.length > 0) {\r\n await this.batchDeleteCartItems(cartItemIds)\r\n }\r\n \r\n return orderId\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:2292','[CreateOrder] 创建订单异常:', error)\r\n return null\r\n }\r\n }\r\n\r\n // 批量通过店铺创建订单\r\n async createOrdersByShop(params: ShopOrderParams): Promise {\r\n try {\r\n const orderIds: string[] = []\r\n const groups = params.shopGroups as any[]\r\n \r\n let grandTotal = 0.0\r\n for(let k = 0; k < groups.length; k++) {\r\n const g = groups[k] as UTSJSONObject\r\n // 安全获取 items 数组\r\n const gItemsRaw = g.get('items')\r\n if (gItemsRaw == null) continue\r\n const gItems = gItemsRaw as any[]\r\n \r\n for(let gi = 0; gi < gItems.length; gi++) {\r\n const it = gItems[gi] as UTSJSONObject\r\n const itPrice = it.getNumber('price') ?? 0\r\n const itQty = it.getNumber('quantity') ?? 1\r\n grandTotal += itPrice * itQty\r\n }\r\n }\r\n \r\n // 为每个店铺创建一个订单\r\n for (let i = 0; i < groups.length; i++) {\r\n const group = groups[i] as UTSJSONObject\r\n const shopItemsRaw = group.get('items')\r\n if (shopItemsRaw == null) continue\r\n const shopItems = shopItemsRaw as any[]\r\n \r\n let productAmount = 0.0\r\n for(let j = 0; j < shopItems.length; j++) {\r\n const sItem = shopItems[j] as UTSJSONObject\r\n const siPrice = sItem.getNumber('price') ?? 0\r\n const siQty = sItem.getNumber('quantity') ?? 1\r\n productAmount += siPrice * siQty\r\n }\r\n \r\n // 简单平摊运费和优惠 (实际逻辑可能更复杂)\r\n const ratio = grandTotal > 0 ? (productAmount / grandTotal) : 0\r\n const shopShippingFee = params.deliveryFee * ratio\r\n const shopDiscount = params.discountAmount * ratio\r\n const shopTotal = productAmount + shopShippingFee - shopDiscount\r\n \r\n const mId = group.getString('merchant_id')\r\n const sId = group.getString('shopId')\r\n const shopName = group.getString('shopName')\r\n\r\n const orderId = await this.createOrder({\r\n merchant_id: (mId != null && mId != '') ? mId : (sId ?? ''), // 兼容旧字段\r\n product_amount: productAmount,\r\n shipping_fee: shopShippingFee,\r\n total_amount: shopTotal,\r\n shipping_address: params.shipping_address,\r\n items: shopItems\r\n })\r\n \r\n if (orderId != null) {\r\n orderIds.push(orderId)\r\n } else {\r\n return { success: false, orderIds, error: `店铺 ${shopName} 订单创建失败` }\r\n }\r\n }\r\n \r\n return { success: true, orderIds }\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2362','批量创建订单异常:', e)\r\n return { success: false, orderIds: [], error: '系统异常' }\r\n }\r\n }\r\n\r\n // 获取订单列表\r\n async getOrders(status: number = 0): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n \r\n let query = supa\r\n .from('ml_orders')\r\n .select(`\r\n *,\r\n ml_order_items (*)\r\n `)\r\n .eq('user_id', userId)\r\n .order('created_at', { ascending: false })\r\n \r\n if (status > 0) {\r\n query = query.eq('order_status', status)\r\n }\r\n \r\n const response = await query.execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2392','获取订单列表失败:', response.error)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n \r\n const data = response.data\r\n if (data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n return data as any[]\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:2404','获取订单列表异常:', error)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n \r\n // 获取订单详情\r\n async getOrderDetail(orderId: string): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return null\r\n \r\n const response = await supa\r\n .from('ml_orders')\r\n .select(`\r\n *,\r\n ml_order_items (*)\r\n `)\r\n .eq('id', orderId)\r\n .eq('user_id', userId)\r\n .single()\r\n .execute()\r\n \r\n if (response.error != null) {\r\n return null\r\n }\r\n return response.data\r\n } catch (e) {\r\n return null\r\n }\r\n }\r\n\r\n // 支付订单\r\n async payOrder(orderId: string, paymentMethod: string, amount: number): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:2441','[payOrder] 用户未登录')\r\n return false\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:2445','[payOrder] 开始更新订单状态, orderId:', orderId, 'userId:', userId)\r\n \r\n const updatePayload = new UTSJSONObject()\r\n updatePayload.set('order_status', 2)\r\n updatePayload.set('payment_status', 1)\r\n updatePayload.set('payment_method', paymentMethod)\r\n updatePayload.set('payment_time', new Date().toISOString())\r\n updatePayload.set('paid_amount', amount)\r\n updatePayload.set('updated_at', new Date().toISOString())\r\n \r\n __f__('log','at utils/supabaseService.uts:2455','[payOrder] 更新数据:', JSON.stringify(updatePayload))\r\n \r\n const response = await supa\r\n .from('ml_orders')\r\n .update(updatePayload)\r\n .eq('id', orderId)\r\n .eq('user_id', userId)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2465','[payOrder] 更新订单失败:', response.error)\r\n return false\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:2469','[payOrder] 订单状态更新成功')\r\n\r\n if (paymentMethod === 'balance') {\r\n __f__('log','at utils/supabaseService.uts:2472','[payOrder] 余额支付,暂不扣减余额')\r\n }\r\n\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2477','[payOrder] 支付异常:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 根据ID获取订单信息\r\n async getOrderById(orderId: string): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:2487','[getOrderById] 用户未登录')\r\n return null\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:2491','[getOrderById] 查询订单, orderId:', orderId)\r\n \r\n const response = await supa\r\n .from('ml_orders')\r\n .select('*')\r\n .eq('id', orderId)\r\n .eq('user_id', userId)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2501','[getOrderById] 查询订单失败:', response.error)\r\n return null\r\n }\r\n \r\n const data = response.data as any[]\r\n if (data == null || data.length === 0) {\r\n __f__('log','at utils/supabaseService.uts:2507','[getOrderById] 未找到订单')\r\n return null\r\n }\r\n \r\n const orderRaw = data[0]\r\n let orderObj: UTSJSONObject\r\n if (orderRaw instanceof UTSJSONObject) {\r\n orderObj = orderRaw as UTSJSONObject\r\n } else {\r\n orderObj = JSON.parse(JSON.stringify(orderRaw)) as UTSJSONObject\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:2519','[getOrderById] 订单数据:', JSON.stringify(orderObj))\r\n return orderObj\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2522','[getOrderById] 查询异常:', e)\r\n return null\r\n }\r\n }\r\n\r\n // 提交售后申请\r\n async createRefund(data: any): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return { success: false, message: '请先登录' }\r\n \r\n const d = data as UTSJSONObject\r\n const orderId = d.getString('order_id')\r\n const refundType = d.getNumber('refund_type')\r\n const refundReason = d.getString('refund_reason')\r\n const refundAmount = d.getNumber('refund_amount')\r\n const description = d.getString('description')\r\n const images = d.getArray('images')\r\n\r\n const payload = {\r\n user_id: userId,\r\n order_id: orderId,\r\n refund_no: 'REF' + Date.now() + Math.floor(Math.random() * 1000),\r\n refund_type: refundType,\r\n refund_reason: refundReason,\r\n refund_amount: refundAmount,\r\n description: description ?? '',\r\n images: images ?? ([] as any[]),\r\n status: 1 // Pending\r\n }\r\n \r\n const response = await supa\r\n .from('ml_refunds')\r\n .insert(payload)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2559','提交售后失败:', response.error)\r\n return { success: false, message: '提交失败: ' + (response.error.message ?? '未知错误') }\r\n }\r\n \r\n return { success: true, message: '申请提交成功' }\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2565','提交售后异常:', e)\r\n return { success: false, message: '系统异常' }\r\n }\r\n }\r\n\r\n // 再次购买\r\n async rePurchase(order: any): Promise {\r\n try {\r\n // 将 order 转换为 UTSJSONObject 以安全访问属性\r\n const orderObj = order as UTSJSONObject\r\n // 尝试获取 ml_order_items 或 items\r\n let itemsKey = 'ml_order_items'\r\n let itemsRaw = orderObj.get(itemsKey)\r\n \r\n if (itemsRaw == null) {\r\n itemsKey = 'items'\r\n itemsRaw = orderObj.get(itemsKey)\r\n }\r\n \r\n if (itemsRaw == null) return false\r\n \r\n // 断言为数组\r\n const items = itemsRaw as any[]\r\n if (items.length === 0) return false\r\n\r\n // 简单的循环添加,实际项目中可以优化为批量插入\r\n for (let i = 0; i < items.length; i++) {\r\n // 同样,item 也是 UTSJSONObject 或支持访问的对象\r\n const item = items[i] as UTSJSONObject\r\n const productId = item.getString('product_id') \r\n const skuId = item.getString('sku_id')\r\n // 数量可能是数字或字符串\r\n const quantity = item.getNumber('quantity') ?? 1\r\n \r\n if (productId != null) {\r\n await this.addToCart(productId, quantity, skuId ?? null)\r\n }\r\n }\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2605','rePurchase error', e)\r\n return false\r\n }\r\n }\r\n\r\n // 申请售后 (Legacy/Simple update)\r\n async applyRefund(orderId: string, reason: string): Promise {\r\n try {\r\n // 更新订单状态为 退款中 (6)\r\n const response = await supa\r\n .from('ml_orders')\r\n .update({\r\n order_status: 6,\r\n cancel_reason: reason,\r\n updated_at: new Date().toISOString()\r\n })\r\n .eq('id', orderId)\r\n .execute()\r\n \r\n return response.error === null\r\n } catch (e) {\r\n return false\r\n }\r\n }\r\n\r\n // 获取售后记录列表\r\n async getRefunds(statusList: number[] = [], page: number = 1, pageSize: number = 10): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n let query = supa\r\n .from('ml_refunds')\r\n .select(`\r\n *,\r\n order:ml_orders!inner (\r\n order_no,\r\n created_at,\r\n ml_order_items (\r\n product_id,\r\n product_name,\r\n image_url\r\n )\r\n )\r\n `)\r\n .eq('user_id', userId)\r\n .order('created_at', { ascending: false })\r\n\r\n if (statusList.length > 0) {\r\n // 显式转换为 any[] 以匹配 .in 方法的参数要求\r\n const anyList = statusList as any[]\r\n query = query.in('status', anyList)\r\n }\r\n\r\n query = query.range((page - 1) * pageSize, page * pageSize - 1)\r\n\r\n const response = await query.execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2667','获取售后列表失败:', response.error)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n const data = response.data\r\n if (data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n return data\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2680','获取售后列表异常:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n\r\n async deleteRefund(refundId: string): Promise {\r\n try {\r\n const response = await supa\r\n .from('ml_refunds')\r\n .delete()\r\n .eq('id', refundId)\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2695','删除退款记录失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2701','删除退款记录异常:', e)\r\n return false\r\n }\r\n }\r\n\r\n async getUserBalance(): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n __f__('log','at utils/supabaseService.uts:2709','[Supabase] getUserBalance userId:', userId)\r\n if (userId == null) return 0\r\n \r\n // 优先查 ml_user_wallets\r\n const walletRes = await supa\r\n .from('ml_user_wallets')\r\n .select('balance')\r\n .eq('user_id', userId!)\r\n .single()\r\n .execute()\r\n \r\n if (walletRes.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2721','[Supabase] getUserBalance error:', walletRes.error)\r\n } else {\r\n __f__('log','at utils/supabaseService.uts:2723','[Supabase] getUserBalance data:', walletRes.data)\r\n }\r\n\r\n if (walletRes.error == null && walletRes.data != null) {\r\n let data = walletRes.data\r\n // 如果是数组,取第一项\r\n if (Array.isArray(data)) {\r\n const arr = data as any[]\r\n if (arr.length > 0) {\r\n data = arr[0]\r\n }\r\n }\r\n\r\n let val:number = 0\r\n if (data instanceof UTSJSONObject) {\r\n val = data.getNumber('balance') ?? 0\r\n // 尝试字符串转换,防止精度丢失导致转为string\r\n if (val === 0 && data.getString('balance') != null) {\r\n val = parseFloat(data.getString('balance')!)\r\n }\r\n return val\r\n } else {\r\n // 对于 Map 或 loose object\r\n const jsonObj = JSON.parse(JSON.stringify(data)) as UTSJSONObject\r\n val = jsonObj.getNumber('balance') ?? 0\r\n if (val === 0 && jsonObj.getString('balance') != null) {\r\n val = parseFloat(jsonObj.getString('balance')!)\r\n }\r\n return val\r\n }\r\n }\r\n\r\n __f__('log','at utils/supabaseService.uts:2755','[Supabase] Wallet table empty, checking profile...')\r\n\r\n // Fallback to profile\r\n const profile = await this.getUserProfile()\r\n if (profile != null) {\r\n if (profile instanceof UTSJSONObject) {\r\n return profile.getNumber('balance') ?? 0\r\n } else {\r\n const pObj = JSON.parse(JSON.stringify(profile)) as UTSJSONObject\r\n return pObj.getNumber('balance') ?? 0\r\n }\r\n }\r\n return 0\r\n } catch(e) {\r\n __f__('error','at utils/supabaseService.uts:2769','[Supabase] getUserBalance exception:', e)\r\n return 0\r\n }\r\n }\r\n \r\n // 获取用户积分\r\n async getUserPoints(): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n __f__('log','at utils/supabaseService.uts:2778','[Supabase] getUserPoints userId:', userId)\r\n if (userId == null) return 0\r\n \r\n // 查 ml_user_points\r\n const res = await supa\r\n .from('ml_user_points')\r\n .select('points')\r\n .eq('user_id', userId!)\r\n .single()\r\n .execute()\r\n \r\n if (res.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2790','[Supabase] getUserPoints error:', res.error)\r\n } else {\r\n __f__('log','at utils/supabaseService.uts:2792','[Supabase] getUserPoints data:', res.data)\r\n }\r\n\r\n if (res.error == null && res.data != null) {\r\n let data = res.data\r\n // 如果是数组,取第一项\r\n if (Array.isArray(data)) {\r\n const arr = data as any[]\r\n if (arr.length > 0) {\r\n data = arr[0]\r\n }\r\n }\r\n\r\n if (data instanceof UTSJSONObject) {\r\n return data.getNumber('points') ?? 0\r\n } else {\r\n // 尝试转为 UTSJSONObject\r\n const jsonObj = JSON.parse(JSON.stringify(data)) as UTSJSONObject\r\n const val = jsonObj.getNumber('points')\r\n if (val != null) return val\r\n\r\n return 0\r\n }\r\n }\r\n \r\n // Fallback check profile if needed\r\n const profile = await this.getUserProfile()\r\n if (profile != null) {\r\n if (profile instanceof UTSJSONObject) {\r\n return profile.getNumber('points') ?? 0\r\n } else {\r\n const pObj = JSON.parse(JSON.stringify(profile)) as UTSJSONObject\r\n return pObj.getNumber('points') ?? 0\r\n }\r\n }\r\n \r\n return 0\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2830','[Supabase] getUserPoints exception:', e)\r\n return 0\r\n }\r\n }\r\n\r\n // 获取钱包交易记录\r\n async getTransactions(page: number = 1, limit: number = 20): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n const from = (page - 1) * limit\r\n const to = from + limit - 1\r\n\r\n const response = await supa\r\n .from('ml_wallet_transactions')\r\n .select('*')\r\n .eq('user_id', userId!)\r\n .order('created_at', { ascending: false })\r\n .range(from, to)\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2856','获取交易记录失败:', response.error)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n \r\n const data = response.data\r\n if (data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n return data as any[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2869','获取交易记录异常:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n \r\n // 获取积分记录\r\n async getPointRecords(): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n \r\n const res = await supa\r\n .from('ml_point_records')\r\n .select('*')\r\n .eq('user_id', userId!)\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n \r\n if (res.error != null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n const data = res.data\r\n if (data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n return data as any[]\r\n } catch (e) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // 获取用户红包\r\n async getUserRedPackets(): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n const res = await supa\r\n .from('ml_user_red_packets')\r\n .select('*')\r\n .eq('user_id', userId!)\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n\r\n if (res.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2924','获取红包失败:', res.error)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n const data = res.data\r\n if (data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n return data as any[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2935','获取红包异常:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // 获取用户银行卡\r\n async getUserBankCards(): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n const res = await supa\r\n .from('ml_user_bank_cards')\r\n .select('*')\r\n .eq('user_id', userId!)\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n\r\n if (res.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2958','获取银行卡失败:', res.error)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n const data = res.data\r\n if (data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n return data as any[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2969','获取银行卡异常:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // 余额充值 (调用 RPC)\r\n async rechargeBalance(amount: number): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return false\r\n \r\n const res = await supa.rpc('recharge_wallet', { \r\n p_user_id: userId,\r\n p_amount: amount \r\n })\r\n \r\n if (res.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2987','充值失败RPC:', res.error)\r\n return false\r\n }\r\n \r\n // 简单判断: 如果没有error且data里success为true\r\n const data = res.data\r\n if (data instanceof UTSJSONObject) {\r\n return data.getBoolean('success') ?? false\r\n }\r\n // 如果返回不是对象,作为失败处理\r\n return false\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2999','充值异常:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 余额提现 (调用 RPC)\r\n async withdrawBalance(amount: number): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return false\r\n \r\n const res = await supa.rpc('withdraw_wallet', { \r\n p_user_id: userId,\r\n p_amount: amount \r\n })\r\n \r\n if (res.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3016','提现失败RPC:', res.error)\r\n return false\r\n }\r\n \r\n const data = res.data\r\n if (data instanceof UTSJSONObject) {\r\n return data.getBoolean('success') ?? false\r\n }\r\n return false\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3026','提现异常:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 添加银行卡\r\n async addBankCard(card: UTSJSONObject): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return false\r\n \r\n // 补全 user_id\r\n card.set('user_id', userId)\r\n \r\n const res = await supa\r\n .from('ml_user_bank_cards')\r\n .insert(card)\r\n .execute()\r\n \r\n if (res.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3046','添加银行卡失败:', res.error)\r\n return false\r\n }\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3051','添加银行卡异常:', e)\r\n return false\r\n }\r\n }\r\n \r\n // 删除银行卡\r\n async deleteBankCard(cardId: string): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return false\r\n \r\n const res = await supa\r\n .from('ml_user_bank_cards')\r\n .eq('id', cardId)\r\n .eq('user_id', userId!)\r\n .delete()\r\n .execute()\r\n \r\n if (res.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3070','删除银行卡失败:', res.error)\r\n return false\r\n }\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3075','删除银行卡异常:', e)\r\n return false\r\n }\r\n }\r\n\r\n // 收藏相关\r\n async checkFavorite(productId: string): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n __f__('log','at utils/supabaseService.uts:3084',`[CheckFav] Checking for User: ${userId}, Product: ${productId}`)\r\n \r\n if (userId == null) return false\r\n \r\n const response = await supa\r\n .from('ml_user_favorites')\r\n .select('*') // Select all to verify data\r\n .eq('user_id', userId!)\r\n .eq('target_id', productId)\r\n .eq('target_type', 1) // 1 for product\r\n .limit(1)\r\n .execute()\r\n \r\n // __f__('log','at utils/supabaseService.uts:3097',`[CheckFav] Response: ${JSON.stringify(response)}`)\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3100',`[CheckFav] Error: ${JSON.stringify(response.error)}`)\r\n return false\r\n }\r\n \r\n const data = response.data\r\n if (Array.isArray(data)) {\r\n if ((data as any[]).length > 0) {\r\n // Double check: ensure the returned item actually matches the product ID\r\n // This guards against potential query filter failures\r\n const item = data[0]\r\n let targetId = ''\r\n if (item instanceof UTSJSONObject) {\r\n targetId = item.getString('target_id') ?? ''\r\n } else {\r\n const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n targetId = itemObj.getString('target_id') ?? ''\r\n }\r\n \r\n if (targetId !== '' && targetId !== productId) {\r\n __f__('error','at utils/supabaseService.uts:3119',`[CheckFav] ID Mismatch! Query ${productId}, Got ${targetId}`)\r\n return false\r\n }\r\n \r\n return true\r\n }\r\n } else if (data instanceof UTSJSONObject) {\r\n // Handle single object return case (though limit(1) usually returns array)\r\n let targetId = data.getString('target_id') ?? ''\r\n if (targetId !== '' && targetId !== productId) {\r\n return false\r\n }\r\n return true\r\n }\r\n \r\n return false\r\n } catch(e) {\r\n __f__('error','at utils/supabaseService.uts:3136',`[CheckFav] Exception: ${e}`)\r\n return false\r\n }\r\n }\r\n \r\n async toggleFavorite(productId: string): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return false\r\n \r\n __f__('log','at utils/supabaseService.uts:3146',`[ToggleFav] Toggling for ${productId}`)\r\n \r\n // Check if exists\r\n const exists = await this.checkFavorite(productId)\r\n __f__('log','at utils/supabaseService.uts:3150',`[ToggleFav] Current status: ${exists}`)\r\n \r\n if (exists) {\r\n // Delete\r\n const response = await supa\r\n .from('ml_user_favorites')\r\n .eq('user_id', userId!)\r\n .eq('target_id', productId)\r\n .eq('target_type', 1)\r\n .delete()\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3163','取消收藏失败:', response.error)\r\n return true // 仍然是收藏状态\r\n }\r\n return false // 已取消收藏\r\n } else {\r\n // Add\r\n const response = await supa\r\n .from('ml_user_favorites')\r\n .insert({\r\n user_id: userId,\r\n target_id: productId,\r\n target_type: 1,\r\n created_at: new Date().toISOString()\r\n })\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3180','添加收藏失败:', response.error)\r\n return false // 添加失败,仍未收藏\r\n }\r\n return true // 已收藏\r\n }\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3186','切换收藏状态异常:', e)\r\n // 发生异常时,尝试查询当前状态返回\r\n return await this.checkFavorite(productId)\r\n }\r\n }\r\n \r\n async getFavorites(): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n \r\n // 第一步:查询收藏列表\r\n const response = await supa\r\n .from('ml_user_favorites')\r\n .select('*')\r\n .eq('user_id', userId!)\r\n .eq('target_type', 1)\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n \r\n if (response.error != null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n const favorites = response.data as any[]\r\n if (favorites == null || favorites.length === 0) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n \r\n // 第二步:收集商品ID\r\n const productIds: string[] = []\r\n for (let i = 0; i < favorites.length; i++) {\r\n let item: any = favorites[i]\r\n let pid = ''\r\n if (item instanceof UTSJSONObject) {\r\n pid = item.getString('target_id') ?? ''\r\n } else {\r\n const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n pid = itemObj.getString('target_id') ?? ''\r\n }\r\n if (pid !== '') productIds.push(pid)\r\n }\r\n \r\n if (productIds.length === 0) return []\r\n \r\n // 第三步:批量查询商品详情\r\n const anyProductIds = productIds as any[]\r\n const productRes = await supa\r\n .from('ml_products')\r\n .select('id, name, main_image_url, base_price, sale_count')\r\n .in('id', anyProductIds)\r\n .execute()\r\n \r\n if (productRes.error != null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n const products = productRes.data as any[]\r\n const productMap = new Map()\r\n \r\n for (let i = 0; i < products.length; i++) {\r\n // 显式声明类型为 any\r\n let p: any = products[i]\r\n let pid = ''\r\n if (p instanceof UTSJSONObject) {\r\n pid = p.getString('id') ?? ''\r\n } else {\r\n const pObj = JSON.parse(JSON.stringify(p)) as UTSJSONObject\r\n pid = pObj.getString('id') ?? ''\r\n }\r\n if (pid !== '') productMap.set(pid, p)\r\n }\r\n \r\n // 第四步:组合数据\r\n const result: any[] = []\r\n for (let i = 0; i < favorites.length; i++) {\r\n let item: any = favorites[i]\r\n let newItem: UTSJSONObject\r\n \r\n if (item instanceof UTSJSONObject) {\r\n // Deep copy to ensure we have a fresh object to modify\r\n newItem = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n } else {\r\n newItem = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n }\r\n \r\n let targetId = newItem.getString('target_id')\r\n // Careful with null targetId\r\n if (targetId != null) {\r\n const product = productMap.get(targetId as string)\r\n if (product != null) {\r\n newItem.set('ml_products', product)\r\n result.push(newItem)\r\n }\r\n }\r\n }\r\n \r\n return result\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3289','获取收藏列表异常:', e)\r\n return []\r\n }\r\n }\r\n\r\n // 获取足迹列表\r\n async getFootprints(): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('log','at utils/supabaseService.uts:3299','[getFootprints] 用户未登录')\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n __f__('log','at utils/supabaseService.uts:3304','[getFootprints] 查询足迹, userId:', userId)\r\n\r\n // 1. 获取足迹记录\r\n const response = await supa\r\n .from('ml_user_footprints')\r\n .select('*')\r\n .eq('user_id', userId!)\r\n .order('updated_at', { ascending: false })\r\n .limit(50)\r\n .execute()\r\n\r\n __f__('log','at utils/supabaseService.uts:3315','[getFootprints] 足迹查询 error:', response.error)\r\n __f__('log','at utils/supabaseService.uts:3316','[getFootprints] 足迹查询 data:', JSON.stringify(response.data))\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3319','[getFootprints] 获取足迹失败:', response.error)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n const footprints = response.data as any[]\r\n if (footprints == null || footprints.length === 0) {\r\n __f__('log','at utils/supabaseService.uts:3326','[getFootprints] 没有足迹记录')\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n __f__('log','at utils/supabaseService.uts:3331','[getFootprints] 足迹记录数量:', footprints.length)\r\n\r\n // 2. 收集商品ID\r\n const productIds: string[] = []\r\n for (let i = 0; i < footprints.length; i++) {\r\n let item = footprints[i]\r\n let pid = ''\r\n if (item instanceof UTSJSONObject) {\r\n pid = item.getString('product_id') ?? ''\r\n } else {\r\n const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n pid = itemObj.getString('product_id') ?? ''\r\n }\r\n if (pid !== '' && !productIds.includes(pid)) productIds.push(pid)\r\n }\r\n\r\n if (productIds.length === 0) return []\r\n \r\n const productIdsAny: any[] = []\r\n for(let i=0; i()\r\n for(let i=0; i {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('log','at utils/supabaseService.uts:3463','[addFootprint] 用户未登录')\r\n return false\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:3467','[addFootprint] 添加足迹, userId:', userId, 'productId:', productId)\r\n \r\n // 检查是否已存在\r\n const checkRes = await supa\r\n .from('ml_user_footprints')\r\n .select('id')\r\n .eq('user_id', userId!)\r\n .eq('product_id', productId)\r\n .execute()\r\n \r\n __f__('log','at utils/supabaseService.uts:3477','[addFootprint] 检查结果 error:', checkRes.error)\r\n __f__('log','at utils/supabaseService.uts:3478','[addFootprint] 检查结果 data:', JSON.stringify(checkRes.data))\r\n\r\n const checkData = checkRes.data as any[]\r\n const exists = checkData != null && Array.isArray(checkData) && checkData.length > 0\r\n \r\n if (checkRes.error == null && exists) {\r\n __f__('log','at utils/supabaseService.uts:3484','[addFootprint] 足迹已存在,更新时间')\r\n // 更新时间\r\n const updateRes = await supa\r\n .from('ml_user_footprints')\r\n .update({ updated_at: new Date().toISOString() })\r\n .eq('user_id', userId!)\r\n .eq('product_id', productId)\r\n .execute()\r\n __f__('log','at utils/supabaseService.uts:3492','[addFootprint] 更新结果 error:', updateRes.error)\r\n } else {\r\n __f__('log','at utils/supabaseService.uts:3494','[addFootprint] 足迹不存在,插入新记录')\r\n // 插入新记录\r\n const insertPayload = new UTSJSONObject()\r\n insertPayload.set('user_id', userId!)\r\n insertPayload.set('product_id', productId)\r\n insertPayload.set('created_at', new Date().toISOString())\r\n insertPayload.set('updated_at', new Date().toISOString())\r\n \r\n const insertRes = await supa\r\n .from('ml_user_footprints')\r\n .insert(insertPayload)\r\n .execute()\r\n __f__('log','at utils/supabaseService.uts:3506','[addFootprint] 插入结果 error:', insertRes.error)\r\n }\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3510','[addFootprint] 添加足迹异常:', e)\r\n return false\r\n }\r\n }\r\n\r\n async getAddressList(): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: UserAddress[] = []\r\n return empty\r\n }\r\n\r\n const response = await supa\r\n .from('ml_user_addresses')\r\n .select('*, recipient_name:receiver_name, phone:receiver_phone, detail_address:address_detail')\r\n .eq('user_id', userId!)\r\n .order('is_default', { ascending: false })\r\n .order('created_at', { ascending: false })\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3532','获取地址列表失败:', response.error)\r\n const empty: UserAddress[] = []\r\n return empty\r\n }\r\n return response.data as UserAddress[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3538','获取地址列表异常:', e)\r\n const empty: UserAddress[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // 设置默认地址\r\n async setDefaultAddress(addressId: string): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:3549','用户未登录,无法设置默认地址')\r\n return false\r\n }\r\n\r\n // 先取消所有默认地址\r\n await this.clearDefaultAddress(userId!)\r\n\r\n // 设置新的默认地址\r\n const response = await supa\r\n .from('ml_user_addresses')\r\n .update({\r\n is_default: true,\r\n updated_at: new Date().toISOString()\r\n })\r\n .eq('id', addressId)\r\n .eq('user_id', userId!)\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3568','设置默认地址失败:', response.error)\r\n return false\r\n }\r\n\r\n return true\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:3574','设置默认地址异常:', error)\r\n return false\r\n }\r\n }\r\n\r\n // 获取用户优惠券列表\r\n async getUserCoupons(status: number = 1): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: UserCoupon[] = []\r\n return empty\r\n }\r\n\r\n // 假设有一个视图或者直接关联 ml_user_coupons 和 ml_coupon_templates\r\n // 这里简化处理,尝试直接从 ml_user_coupons 读取,并且加入 template 信息\r\n // 如果没有 view,可能需要改为两个查询或者使用 left join\r\n const response = await supa\r\n .from('ml_user_coupons')\r\n .select(`\r\n *,\r\n template:ml_coupon_templates(name, amount, min_spend)\r\n `)\r\n .eq('user_id', userId!)\r\n .eq('status', status)\r\n .order('expire_at', { ascending: true })\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3603','获取优惠券失败:', response.error)\r\n const empty: UserCoupon[] = []\r\n return empty\r\n }\r\n\r\n // 映射数据,将 template 的字段展平\r\n const coupons: UserCoupon[] = []\r\n const rawData = response.data as any[]\r\n for (let i = 0; i < rawData.length; i++) {\r\n const item = rawData[i]\r\n let template: any | null = null\r\n let itemId = ''\r\n let itemUserId = ''\r\n let itemTmplId = ''\r\n let itemCode = ''\r\n let itemStatus = 0\r\n let itemRecv = ''\r\n let itemExpire = ''\r\n \r\n if (item instanceof UTSJSONObject) {\r\n template = item.get('template') as any | null\r\n itemId = item.getString('id') ?? ''\r\n itemUserId = item.getString('user_id') ?? ''\r\n itemTmplId = item.getString('template_id') ?? ''\r\n itemCode = item.getString('coupon_code') ?? ''\r\n itemStatus = item.getNumber('status') ?? 0\r\n itemRecv = item.getString('received_at') ?? ''\r\n itemExpire = item.getString('expire_at') ?? ''\r\n } else {\r\n const iObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject\r\n template = iObj.get('template') as any | null\r\n itemId = iObj.getString('id') ?? ''\r\n itemUserId = iObj.getString('user_id') ?? ''\r\n itemTmplId = iObj.getString('template_id') ?? ''\r\n itemCode = iObj.getString('coupon_code') ?? ''\r\n itemStatus = iObj.getNumber('status') ?? 0\r\n itemRecv = iObj.getString('received_at') ?? ''\r\n itemExpire = iObj.getString('expire_at') ?? ''\r\n }\r\n \r\n if (template == null) template = new UTSJSONObject()\r\n \r\n let tName = ''\r\n let tAmount = 0\r\n let tMin = 0\r\n \r\n if (template instanceof UTSJSONObject) {\r\n tName = template.getString('name') ?? '优惠券'\r\n tAmount = template.getNumber('amount') ?? 0\r\n tMin = template.getNumber('min_spend') ?? 0\r\n } else {\r\n const tObj = JSON.parse(JSON.stringify(template)) as UTSJSONObject\r\n tName = tObj.getString('name') ?? '优惠券'\r\n tAmount = tObj.getNumber('amount') ?? 0\r\n tMin = tObj.getNumber('min_spend') ?? 0\r\n }\r\n\r\n const couponObj = new UTSJSONObject()\r\n couponObj.set('id', itemId)\r\n couponObj.set('user_id', itemUserId)\r\n couponObj.set('template_id', itemTmplId)\r\n couponObj.set('coupon_code', itemCode)\r\n couponObj.set('status', itemStatus)\r\n couponObj.set('received_at', itemRecv)\r\n couponObj.set('expire_at', itemExpire)\r\n couponObj.set('template_name', tName)\r\n couponObj.set('amount', tAmount)\r\n couponObj.set('min_spend', tMin)\r\n \r\n coupons.push(couponObj as UserCoupon)\r\n }\r\n\r\n return coupons\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3677','获取优惠券异常:', e)\r\n const empty: UserCoupon[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // 获取可用优惠券数量\r\n async getUserCouponCount(): Promise {\r\n try {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return 0\r\n\r\n const response = await supa\r\n .from('ml_user_coupons')\r\n .select('id', { count: 'exact' })\r\n .eq('user_id', userId!)\r\n .eq('status', 1) // 1: unused\r\n .gt('expire_at', new Date().toISOString()) // 未过期\r\n .limit(1) // Limit to 1 to reduce data transfer, we only want the count\r\n .execute()\r\n\r\n if (response.error != null) {\r\n return 0\r\n }\r\n return response.total ?? 0\r\n } catch (e) {\r\n return 0\r\n }\r\n }\r\n\r\n // 获取店铺/商品可用优惠券\r\n async getAvailableCoupons(merchantId: string): Promise {\r\n return this.fetchShopCoupons(merchantId)\r\n }\r\n\r\n // ALIAS for Cache busting: 获取店铺优惠券\r\n async fetchShopCoupons(merchantId: string): Promise {\r\n try {\r\n // 查询该商家的优惠券 + 平台通用券 (merchant_id is null)\r\n // 注意:这里简化逻辑,实际可能需要联合查询用户是否已领取\r\n const response = await supa\r\n .from('ml_coupon_templates')\r\n .select('*')\r\n .or(`merchant_id.eq.${merchantId},merchant_id.is.null`)\r\n .eq('status', 1)\r\n .gt('end_time', new Date().toISOString())\r\n .order('discount_value', { ascending: false })\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3727','Fetch coupons failed:', response.error)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n \r\n const data = response.data\r\n if (data == null) {\r\n const empty: any[] = []\r\n return empty\r\n }\r\n return data as any[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3739','Fetch coupons error:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // 领取优惠券\r\n async claimCoupon(templateId: string, userId: string): Promise {\r\n return this.claimShopCoupon(templateId, userId)\r\n }\r\n\r\n // ALIAS for Cache busting\r\n async claimShopCoupon(templateId: string, userId: string): Promise {\r\n try {\r\n __f__('log','at utils/supabaseService.uts:3753','Claiming coupon templateId:', templateId, 'userId:', userId)\r\n\r\n // 1. Fetch template details to get merchant_id and validity\r\n const tmplRes = await supa\r\n .from('ml_coupon_templates')\r\n .select('*')\r\n .eq('id', templateId)\r\n .limit(1)\r\n .execute()\r\n \r\n if (tmplRes.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3764','Claim Coupon: Template query error', tmplRes.error)\r\n return false\r\n }\r\n\r\n // Null check for data\r\n if (tmplRes.data == null) {\r\n __f__('error','at utils/supabaseService.uts:3770','Claim Coupon: Template data response is null')\r\n return false\r\n }\r\n \r\n const dataList = tmplRes.data as any[]\r\n if (dataList.length === 0) {\r\n __f__('error','at utils/supabaseService.uts:3776','Claim Coupon: Template not found (empty list)')\r\n return false\r\n }\r\n\r\n const template = dataList[0]\r\n \r\n // Safe property access\r\n let validDays = 0\r\n let endTimeStr: string | null = null\r\n let merchantId: string | null = null\r\n \r\n if (template instanceof UTSJSONObject) {\r\n validDays = template.getNumber('valid_days') ?? 0\r\n endTimeStr = template.getString('end_time')\r\n merchantId = template.getString('merchant_id')\r\n } else {\r\n const tJson = JSON.parse(JSON.stringify(template)) as UTSJSONObject\r\n validDays = tJson.getNumber('valid_days') ?? 0\r\n endTimeStr = tJson.getString('end_time')\r\n merchantId = tJson.getString('merchant_id')\r\n }\r\n \r\n // Calculate expire_at\r\n let expireAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()\r\n if (validDays > 0) {\r\n expireAt = new Date(Date.now() + (validDays * 24 * 60 * 60 * 1000)).toISOString()\r\n } else if (endTimeStr != null && endTimeStr !== '') {\r\n expireAt = endTimeStr\r\n }\r\n \r\n // Handle UUID fields: Empty string is not valid UUID, must be null\r\n if (merchantId != null && merchantId.length === 0) {\r\n merchantId = null\r\n }\r\n\r\n // 2. Insert into user coupons with merchant_id\r\n const insertData = {\r\n user_id: userId,\r\n template_id: templateId,\r\n merchant_id: merchantId, // Important for shop filtering: null for platform coupons\r\n coupon_code: 'C' + Date.now() + Math.floor(Math.random() * 1000), \r\n status: 1, \r\n expire_at: expireAt,\r\n received_at: new Date().toISOString()\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:3822','Claim Coupon Insert Payload:', JSON.stringify(insertData))\r\n\r\n const response = await supa\r\n .from('ml_user_coupons')\r\n .insert(insertData)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3830','Claim Coupon: Insert failed:', JSON.stringify(response.error))\r\n // 尝试降级:如果 merchant_id 报错,尝试不带 merchant_id (仅调试用,或兼容旧表结构)\r\n if (JSON.stringify(response.error).includes('merchant_id')) {\r\n __f__('log','at utils/supabaseService.uts:3833','Retrying without merchant_id...')\r\n const fallbackData = {\r\n user_id: userId,\r\n template_id: templateId,\r\n coupon_code: 'C' + Date.now() + Math.random().toString().substring(2,6),\r\n status: 1,\r\n expire_at: expireAt,\r\n received_at: new Date().toISOString()\r\n }\r\n const res2 = await supa.from('ml_user_coupons').insert(fallbackData).execute()\r\n if (res2.error == null) return true\r\n }\r\n return false\r\n }\r\n return true\r\n } catch(e) {\r\n __f__('error','at utils/supabaseService.uts:3849','Claim coupon error:', e)\r\n return false\r\n }\r\n }\r\n\r\n // ==========================================\r\n // 聊天相关方法\r\n // ==========================================\r\n\r\n // 获取特定会话的消息历史\r\n async getChatMessages(merchantId: string, page: number = 1, pageSize: number = 20): Promise {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n const empty: ChatMessage[] = []\r\n return empty\r\n }\r\n \r\n // 计算分页 range\r\n const fromIndex = (page - 1) * pageSize\r\n const toIndex = fromIndex + pageSize - 1\r\n\r\n try {\r\n // 使用 or 组合条件查询:(sender_id=me AND receiver_id=merchant) OR (sender_id=merchant AND receiver_id=me)\r\n // 注意:Supabase postgrest-js 的 .or() 语法如果是针对同一列很简单,针对复杂逻辑用 string syntax\r\n // 这里简化处理,如果不加 userId 过滤,全靠 RLS\r\n const response = await supa\r\n .from('ml_chat_messages')\r\n .select('*')\r\n .or(`sender_id.eq.${merchantId},receiver_id.eq.${merchantId}`)\r\n .order('created_at', { ascending: false })\r\n .range(fromIndex, toIndex)\r\n .execute()\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3883','getChatMessages error:', response.error)\r\n const empty: ChatMessage[] = []\r\n return empty\r\n }\r\n \r\n const data = response.data\r\n if (data == null) {\r\n const empty: ChatMessage[] = []\r\n return empty\r\n }\r\n\r\n return data as ChatMessage[]\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3896','getChatMessages exception:', e)\r\n const empty: ChatMessage[] = []\r\n return empty\r\n }\r\n }\r\n\r\n // 发送消息\r\n async sendMessage(merchantId: string, content: string, msgType: string = 'text'): Promise