diff --git a/.hbuilderx/launch.json b/.hbuilderx/launch.json
index 8ae452a8..158b50de 100644
--- a/.hbuilderx/launch.json
+++ b/.hbuilderx/launch.json
@@ -2,6 +2,9 @@
"version" : "1.0",
"configurations" : [
{
+ "customPlaygroundType" : "local",
+ "localRepoPath" : "D:/companyproject/mall",
+ "packageName" : "com.huawei.hisuite",
"playground" : "standard",
"type" : "uni-app:app-android"
}
diff --git a/manifest.json b/manifest.json
index 55841848..bf14093f 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,6 +1,6 @@
{
"name": "mall",
- "appid": "__UNI__YOUR_APP_ID__",
+ "appid": "__UNI__EC68BC3",
"description": "A multi-role e-commerce application.",
"versionName": "1.0.0",
"versionCode": "100",
@@ -73,21 +73,15 @@
},
"app-android": {
"distribute": {
- "modules": {
- "uni-payment": {
- "alipay": {},
- "wxpay": {}
- },
- "uni-push": {
- "hms": {},
- "oppo": {},
- "vivo": {},
- "xiaomi": {},
- "meizu": {},
- "honor": {},
- "fcm": {}
- }
+ "modules": {},
+ "android": {
+ "usesCleartextTraffic": true
}
}
+ },
+ "web": {
+ "router": {
+ "mode": ""
+ }
}
}
\ No newline at end of file
diff --git a/pages.json b/pages.json
index eddfdfcd..f1cc6064 100644
--- a/pages.json
+++ b/pages.json
@@ -243,30 +243,6 @@
"navigationStyle": "custom"
}
},
- {
- "path": "subscription/plan-list",
- "style": {
- "navigationBarTitleText": "软件订阅"
- }
- },
- {
- "path": "subscription/plan-detail",
- "style": {
- "navigationBarTitleText": "订阅详情"
- }
- },
- {
- "path": "subscription/subscribe-checkout",
- "style": {
- "navigationBarTitleText": "确认订阅"
- }
- },
- {
- "path": "subscription/my-subscriptions",
- "style": {
- "navigationBarTitleText": "我的订阅"
- }
- },
{
"path": "subscription/followed-shops",
"style": {
@@ -586,33 +562,321 @@
// }
// }
// ]
-// },
+
// {
-// "root": "pages/mall/merchant",
+// "root": "pages/mall/delivery",
// "pages": [
// {
// "path": "index",
// "style": {
-// "navigationBarTitleText": "商家中心",
+// "navigationBarTitleText": "配送中心",
// "navigationStyle": "custom"
// }
// },
// {
-// "path": "product-detail",
+// "path": "order-detail",
// "style": {
-// "navigationBarTitleText": "商品管理详情",
-// "enablePullDownRefresh": false
+// "navigationBarTitleText": "订单详情页",
+// "navigationStyle": "custom"
// }
// },
// {
// "path": "profile",
// "style": {
-// "navigationBarTitleText": "个人资料"
+// "navigationBarTitleText": "配送个人中心",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "order-history",
+// "style": {
+// "navigationBarTitleText": "历史记录",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "earnings",
+// "style": {
+// "navigationBarTitleText": "收入明细",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "tasks",
+// "style": {
+// "navigationBarTitleText": "全部任务",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "task-detail",
+// "style": {
+// "navigationBarTitleText": "任务详情",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "profile-edit",
+// "style": {
+// "navigationBarTitleText": "编辑个人资料",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "ratings",
+// "style": {
+// "navigationBarTitleText": "评价",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "vehicle",
+// "style": {
+// "navigationBarTitleText": "车辆管理",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "vehicle-add",
+// "style": {
+// "navigationBarTitleText": "添加车辆",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "vehicle-edit",
+// "style": {
+// "navigationBarTitleText": "编辑车辆",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "help-center",
+// "style": {
+// "navigationBarTitleText": "帮助中心",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "about",
+// "style": {
+// "navigationBarTitleText": "关于我们",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "feedback",
+// "style": {
+// "navigationBarTitleText": "意见反馈",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "test",
+// "style": {
+// "navigationBarTitleText": "test",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "settings",
+// "style": {
+// "navigationBarTitleText": "设置",
+// "navigationStyle": "custom"
// }
// }
// ]
// },
// {
+// "root": "pages/mall/analytics",
+// "pages": [
+// {
+// "path": "index",
+// "style": {
+// "navigationBarTitleText": "数据分析",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "profile",
+// "style": {
+// "navigationBarTitleText": "数据分析个人中心"
+// }
+// },
+// {
+// "path": "sales-report",
+// "style": {
+// "navigationBarTitleText": "销售报表"
+// }
+// },
+// {
+// "path": "user-analysis",
+// "style": {
+// "navigationBarTitleText": "用户分析"
+// }
+// },
+// {
+// "path": "product-insights",
+// "style": {
+// "navigationBarTitleText": "商品洞察"
+// }
+// },
+// {
+// "path": "delivery-analysis",
+// "style": {
+// "navigationBarTitleText": "配送效率分析"
+// }
+// },
+// {
+// "path": "coupon-analysis",
+// "style": {
+// "navigationBarTitleText": "优惠券效果分析"
+// }
+// },
+// {
+// "path": "market-trends",
+// "style": {
+// "navigationBarTitleText": "市场趋势"
+// }
+// },
+// {
+// "path": "custom-report",
+// "style": {
+// "navigationBarTitleText": "自定义报表"
+// }
+// },
+// {
+// "path": "report-detail",
+// "style": {
+// "navigationBarTitleText": "报表详情",
+// "enablePullDownRefresh": false
+// }
+// },
+// {
+// "path": "data-detail",
+// "style": {
+// "navigationBarTitleText": "数据分析详情",
+// "enablePullDownRefresh": false
+// }
+// },
+// {
+// "path": "insight-detail",
+// "style": {
+// "navigationBarTitleText": "数据洞察详情",
+// "enablePullDownRefresh": false
+// }
+// }
+// ]
+// },
+// {
+// "root": "pages/mall/admin",
+// "pages": [
+// {
+// "path": "user-management",
+// "style": {
+// "navigationBarTitleText": "用户管理",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "product-management",
+// "style": {
+// "navigationBarTitleText": "商品管理",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "order-management",
+// "style": {
+// "navigationBarTitleText": "订单管理",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "finance/record",
+// "style": {
+// "navigationBarTitleText": "财务管理",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "user-statistics",
+// "style": {
+// "navigationBarTitleText": "用户统计",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "system-settings",
+// "style": {
+// "navigationBarTitleText": "系统设置",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "subscription/plan-management",
+// "style": {
+// "navigationBarTitleText": "订阅方案管理"
+// }
+// },
+// {
+// "path": "subscription/user-subscriptions",
+// "style": {
+// "navigationBarTitleText": "用户订阅管理"
+// }
+// },
+// {
+// "path": "marketing/coupon/list",
+// "style": {
+// "navigationBarTitleText": "优惠券列表"
+// }
+// },
+// {
+// "path": "marketing/coupon/receive",
+// "style": {
+// "navigationBarTitleText": "用户领取记录"
+// }
+// },
+// {
+// "path": "marketing/signin/rule",
+// "style": {
+// "navigationBarTitleText": "签到规则"
+// }
+// },
+// {
+// "path": "marketing/signin/record",
+// "style": {
+// "navigationBarTitleText": "签到记录"
+// }
+// }
+// ]
+// },
+// {
+// "root": "pages/mall/service",
+// "pages": [
+// {
+// "path": "index",
+// "style": {
+// "navigationBarTitleText": "客服工作台",
+// "navigationStyle": "custom"
+// }
+// },
+// {
+// "path": "profile",
+// "style": {
+// "navigationBarTitleText": "客服个人中心"
+// }
+// },
+// {
+// "path": "ticket-detail",
+// "style": {
+// "navigationBarTitleText": "工单详情",
+// "enablePullDownRefresh": false
+// }
+// }
+// ]
+// }
+// {
// "root": "pages/mall/service",
// "pages": [
// {
diff --git a/pages/mall/consumer/666/index.uvue b/pages/mall/consumer/666/index.uvue
new file mode 100644
index 00000000..1b42741b
--- /dev/null
+++ b/pages/mall/consumer/666/index.uvue
@@ -0,0 +1,2526 @@
+
+
+
+
+
+
+
+
+ 请输入药品名称、症状或品牌
+
+
+
+ 🔳
+
+
+
+
+ 📷
+
+
+
+
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ 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
new file mode 100644
index 00000000..a4b3550d
--- /dev/null
+++ b/pages/mall/consumer/666/supabaseService.uts
@@ -0,0 +1,3888 @@
+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 d7391027..e3cbcc2c 100644
--- a/pages/mall/consumer/cart.uvue
+++ b/pages/mall/consumer/cart.uvue
@@ -326,7 +326,7 @@ const loadCartData = async () => {
shopId: p.merchant_id ?? 'unknown',
shopName: p.shop_name ?? '商城推荐',
name: p.name,
- price: p.base_price,
+ price: p.base_price ?? p.price ?? 0,
image: p.main_image_url ?? '/static/images/default-product.png',
skuId: ''
}
diff --git a/pages/mall/consumer/category.uvue b/pages/mall/consumer/category.uvue
index 1d9f78b8..027b6f27 100644
--- a/pages/mall/consumer/category.uvue
+++ b/pages/mall/consumer/category.uvue
@@ -25,8 +25,8 @@
-
-
+
+
@@ -77,9 +77,9 @@
¥
- {{ product.base_price }}
+ {{ product.base_price ?? product.price ?? 0 }}
-
+
¥{{ product.market_price }}
diff --git a/pages/mall/consumer/checkout.uvue b/pages/mall/consumer/checkout.uvue
index f28cc59d..ff620735 100644
--- a/pages/mall/consumer/checkout.uvue
+++ b/pages/mall/consumer/checkout.uvue
@@ -597,6 +597,12 @@ const processCheckoutItems = (items: any[]) => {
}
}
+// 获取当前用户ID
+function getCurrentUserId(): string {
+ const userId = supabaseService.getCurrentUserId()
+ return userId ?? ''
+}
+
// 生命周期
onMounted(() => {
// 监听地址更新事件
@@ -774,14 +780,8 @@ async function loadDefaultAddress(): Promise {
}
}
-// 获取当前用户ID
-function getCurrentUserId(): string {
- const userId = supabaseService.getCurrentUserId()
- return userId ?? ''
-}
-
// 用户登录状态
-const isLoggedIn = computed(() => {
+const isLoggedIn = computed((): boolean => {
const userId = getCurrentUserId()
return userId != ''
})
diff --git a/pages/mall/consumer/doc/CONSUMER_DB_DOC.md b/pages/mall/consumer/doc/CONSUMER_DB_DOC.md
index 268249a9..6d8abaec 100644
--- a/pages/mall/consumer/doc/CONSUMER_DB_DOC.md
+++ b/pages/mall/consumer/doc/CONSUMER_DB_DOC.md
@@ -113,34 +113,163 @@
| `selected` | Boolean | 是否勾选 | 购物车状态 |
| `created_at` | Timestamp | 创建时间 | |
-### 3.2 订单主表 (`ml_orders`) (推测结构)
+### 3.2 订单主表 (`ml_orders`)
商家端处理订单的核心表。
| 字段名 | 类型 | 描述 | 备注 |
| :--- | :--- | :--- | :--- |
| `id` | UUID | 主键 | |
+| `order_no` | VARCHAR | 订单号 | 唯一业务单号 |
| `user_id` | UUID | 用户 ID | |
| `merchant_id` | UUID | 商家 ID | |
-| `order_no` | Text | 订单号 | 唯一业务单号 |
-| `total_amount` | Numeric | 订单总金额 | |
-| `pay_amount` | Numeric | 实付金额 | |
-| `status` | Integer | 订单状态 | 0: 待付款, 1: 待发货, 2: 待收货, 3: 已完成, -1: 已取消 |
-| `address_snapshot` | JSONB | 收货地址快照 | 下单时的地址信息 |
-| `remark` | Text | 订单备注 | |
-| `created_at` | Timestamp | 下单时间 | |
+| `product_amount` | NUMERIC | 商品金额 | 默认0 |
+| `discount_amount` | NUMERIC | 优惠金额 | |
+| `shipping_fee` | NUMERIC | 运费 | |
+| `total_amount` | NUMERIC | 订单总金额 | |
+| `paid_amount` | NUMERIC | 实付金额 | |
+| `shipping_address` | JSONB | 收货地址 | |
+| `order_status` | INTEGER | 订单状态 | 1:待付款, 2:待发货, 3:待收货, 4:已完成, 5:已取消, 0:退款中, -1:已取消 |
+| `payment_status` | INTEGER | 支付状态 | 默认1 |
+| `shipping_status` | INTEGER | 发货状态 | 默认1 |
+| `paid_at` | TIMESTAMP | 支付时间 | |
+| `shipped_at` | TIMESTAMP | 发货时间 | |
+| `delivered_at` | TIMESTAMP | 收货时间 | |
+| `completed_at` | TIMESTAMP | 完成时间 | |
+| `remark` | TEXT | 订单备注 | |
+| `merchant_memo` | TEXT | 商家备注 | |
+| `cancel_reason` | TEXT | 取消原因 | |
+| `created_at` | TIMESTAMP | 创建时间 | 默认now() |
+| `updated_at` | TIMESTAMP | 更新时间 | |
+| `cid` | INTEGER | 序号 | |
+| `payment_method` | VARCHAR | 支付方式 | |
+| `payment_time` | TIMESTAMP | 支付时间 | |
+| `shipping_company` | VARCHAR | 物流公司 | 商家端发货填写 |
+| `tracking_number` | VARCHAR | 物流单号 | 商家端发货填写 |
-### 3.3 订单项表 (`ml_order_items`) (推测结构)
+### 3.3 订单项表 (`ml_order_items`)
| 字段名 | 类型 | 描述 | 备注 |
| :--- | :--- | :--- | :--- |
| `id` | UUID | 主键 | |
| `order_id` | UUID | 订单 ID | |
| `product_id` | UUID | 商品 ID | |
-| `sku_id` | UUID | SKU ID | |
-| `product_name` | Text | 商品名称快照 | |
-| `price` | Numeric | 成交单价 | |
-| `quantity` | Integer | 购买数量 | |
-| `sku_snapshot` | JSONB | 规格快照 | |
+| `sku_id` | UUID | SKU ID | 可空 |
+| `product_name` | VARCHAR | 商品名称快照 | |
+| `sku_name` | VARCHAR | SKU名称 | 可空 |
+| `specifications` | JSONB | 规格信息 | 默认{} |
+| `image_url` | TEXT | 商品图片 | 可空 |
+| `price` | NUMERIC | 成交单价 | |
+| `quantity` | INTEGER | 购买数量 | |
+| `total_amount` | NUMERIC | 小计金额 | |
+| `created_at` | TIMESTAMP | 创建时间 | |
+| `sku_snapshot` | JSONB | SKU快照 | 默认{} |
+
+### 3.4 订单详情视图 (`ml_orders_detail_view`)
+订单联合查询视图。
+
+| 字段名 | 类型 | 描述 | 备注 |
+| :--- | :--- | :--- | :--- |
+| `id` | UUID | 主键 | |
+| `order_no` | VARCHAR | 订单号 | |
+| `user_id` | UUID | 用户 ID | |
+| `merchant_id` | UUID | 商家 ID | |
+| `product_amount` | NUMERIC | 商品金额 | |
+| `discount_amount` | NUMERIC | 优惠金额 | |
+| `shipping_fee` | NUMERIC | 运费 | |
+| `total_amount` | NUMERIC | 订单总金额 | |
+| `paid_amount` | NUMERIC | 实付金额 | |
+| `shipping_address` | JSONB | 收货地址 | |
+| `order_status` | INTEGER | 订单状态 | |
+| `payment_status` | INTEGER | 支付状态 | |
+| `shipping_status` | INTEGER | 发货状态 | |
+| `paid_at` | TIMESTAMP | 支付时间 | |
+| `shipped_at` | TIMESTAMP | 发货时间 | |
+| `delivered_at` | TIMESTAMP | 收货时间 | |
+| `completed_at` | TIMESTAMP | 完成时间 | |
+| `remark` | TEXT | 订单备注 | |
+| `merchant_memo` | TEXT | 商家备注 | |
+| `cancel_reason` | TEXT | 取消原因 | |
+| `created_at` | TIMESTAMP | 创建时间 | |
+| `updated_at` | TIMESTAMP | 更新时间 | |
+| `customer_name` | VARCHAR | 客户姓名 | |
+| `customer_phone` | TEXT | 客户电话 | |
+| `merchant_name` | VARCHAR | 商家名称 | |
+| `shop_name` | VARCHAR | 店铺名称 | |
+| `order_status_name` | TEXT | 订单状态名称 | |
+| `payment_status_name` | TEXT | 支付状态名称 | |
+
+### 3.5 支付订单表 (`pay_order`)
+支付网关订单表。
+
+| 字段名 | 类型 | 描述 | 备注 |
+| :--- | :--- | :--- | :--- |
+| `id` | BIGINT | 主键 | |
+| `merchant_id` | BIGINT | 商户ID | |
+| `app_id` | BIGINT | 应用ID | |
+| `channel_id` | BIGINT | 渠道ID | 可空 |
+| `channel_code` | VARCHAR | 渠道编码 | 可空 |
+| `merchant_order_id` | VARCHAR | 商户订单号 | |
+| `subject` | VARCHAR | 订单标题 | |
+| `body` | VARCHAR | 订单描述 | |
+| `notify_url` | VARCHAR | 通知地址 | |
+| `notify_status` | SMALLINT | 通知状态 | |
+| `amount` | BIGINT | 金额 | |
+| `channel_fee_rate` | DOUBLE | 渠道费率 | 可空 |
+| `channel_fee_amount` | BIGINT | 渠道手续费 | 可空 |
+| `status` | SMALLINT | 状态 | |
+| `user_ip` | VARCHAR | 用户IP | |
+| `expire_time` | TIMESTAMP | 过期时间 | |
+| `success_time` | TIMESTAMP | 成功时间 | 可空 |
+| `notify_time` | TIMESTAMP | 通知时间 | 可空 |
+| `success_extension_id` | BIGINT | 成功扩展ID | 可空 |
+| `refund_status` | SMALLINT | 退款状态 | |
+| `refund_times` | SMALLINT | 退款次数 | |
+| `refund_amount` | BIGINT | 退款金额 | |
+| `channel_user_id` | VARCHAR | 渠道用户ID | 可空 |
+| `channel_order_no` | VARCHAR | 渠道订单号 | 可空 |
+| `creator` | VARCHAR | 创建人 | 可空 |
+| `create_time` | TIMESTAMP | 创建时间 | |
+| `updater` | VARCHAR | 更新人 | 可空 |
+| `update_time` | TIMESTAMP | 更新时间 | |
+| `deleted` | SMALLINT | 删除标记 | 默认0 |
+| `tenant_id` | BIGINT | 租户ID | 默认0 |
+
+### 3.6 支付订单扩展表 (`pay_order_extension`)
+支付订单扩展信息表。
+
+| 字段名 | 类型 | 描述 | 备注 |
+| :--- | :--- | :--- | :--- |
+| `id` | BIGINT | 主键 | |
+| `no` | VARCHAR | 支付流水号 | |
+| `order_id` | BIGINT | 支付订单ID | |
+| `channel_id` | BIGINT | 渠道ID | |
+| `channel_code` | VARCHAR | 渠道编码 | |
+| `user_ip` | VARCHAR | 用户IP | |
+| `status` | SMALLINT | 状态 | |
+| `channel_extras` | VARCHAR | 渠道额外信息 | 可空 |
+| `channel_notify_data` | VARCHAR | 渠道通知数据 | 可空 |
+| `creator` | VARCHAR | 创建人 | 可空 |
+| `create_time` | TIMESTAMP | 创建时间 | |
+| `updater` | VARCHAR | 更新人 | 可空 |
+| `update_time` | TIMESTAMP | 更新时间 | |
+| `deleted` | SMALLINT | 删除标记 | 默认0 |
+| `tenant_id` | BIGINT | 租户ID | 默认0 |
+
+### 3.7 库存订单表 (`stock_orders`)
+库存相关订单表。
+
+| 字段名 | 类型 | 描述 | 备注 |
+| :--- | :--- | :--- | :--- |
+| `id` | UUID | 主键 | |
+| `simulation_id` | UUID | 模拟ID | 可空 |
+| `symbol` | TEXT | 交易标的 | |
+| `side` | TEXT | 交易方向 | |
+| `qty` | NUMERIC | 数量 | |
+| `price` | NUMERIC | 价格 | 可空 |
+| `status` | TEXT | 状态 | 默认created |
+| `placed_at` | TIMESTAMP | 下单时间 | |
+| `executed_at` | TIMESTAMP | 执行时间 | 可空 |
+| `fee` | NUMERIC | 手续费 | 默认0 |
---
diff --git a/pages/mall/consumer/doc/uts.txt b/pages/mall/consumer/doc/uts.txt
index 273098e2..291b535e 100644
--- a/pages/mall/consumer/doc/uts.txt
+++ b/pages/mall/consumer/doc/uts.txt
@@ -1,76 +1,1396 @@
-1、在 uni-app x 的 UTS 语法中:
-- 不能直接访问 `any` 类型对象的属性
-- 需要将对象转换为 `UTSJSONObject` 类型后使用 `getString()`、`getNumber()` 等方法访问属性
-- 数组元素需要明确的类型定义才能在模板中正确访问属性
-- 空数组需要明确指定类型,如 `[] as string[]`
-- 函数必须在调用前定义(不支持函数提升)
-- 访问动态对象属性需要使用 `UTSJSONObject` 的 `getString()`、`getNumber()` 等方法
-- 类型导入需要使用 `type` 关键字
--UTS 不允许直接访问 `any` 类型的属性。需要将参数类型改为 `MessageItem`
-- UTS 不允许直接访问 `any` 类型参数的属性
-- 必须使用明确的类型定义才能访问属性
-- 函数必须先定义后使用(包括生命周期钩子调用的函数)
-- 不允许直接访问 `any` 类型参数的属性
-- `if` 条件必须是 `boolean` 类型,不能是 `Any?`
-- `forEach` 箭头函数中不能使用赋值表达式作为函数体
-- 函数必须先定义后使用
-- `any` 类型不能直接使用点语法访问属性,必须使用索引访问 `obj['property']`
-- 访问后需要类型转换 `as number` / `as string`
-- `if` 条件必须是 `boolean` 类型
-- 函数必须先定义后调用
-- `any` 类型不能直接使用点语法访问属性
-- 需要转换为 `Record` 后使用索引访问 `obj['property']`
-- 模板中的 `||` 运算符左边必须是 boolean 类型
-- 可空类型使用可选链 `?.` 和空值合并 `??`
-- `uni.showModal` 的 `success` 回调不能是 `async` 函数
-- 解决方案:创建独立的 async 函数(如 `doCancelOrder`、`doConfirmReceive`、`doApplyRefund`),在回调中调用
-- 模板中访问可空类型属性前必须先判空 `v-if="order != null"`
-- 使用可选链 `?.` 和空值合并 `??` 处理可空类型
-- `uni.showModal` 的 `success` 回调不能是 `async` 函数
-- 函数必须在使用前定义
-- 不支持 `Record` 对象字面量语法
-- 模板中可空类型必须使用 `?.` 安全访问
-- `uni.showModal` 的 `success` 回调不能是 `async` 函数
-- `any` 类型属性访问需转换为 `Record` 后用索引访问
-- 不支持 `Record` 对象字面量语法
-- `any` 类型属性访问需转换为 `Record` 后用索引访问
-- `uni.showModal` 的 `success` 回调不能是 `async` 函数
-- 模板中可空类型必须使用 `?.` 安全访问
-- 不支持 `Object.values()` 和 `Object.entries()`
-- 嵌套的数组方法调用可能导致类型推断失败,应拆分为独立变量
-- `any` 类型属性访问需转换为 `Record` 后用索引访问
-- 不支持 `Record` 对象字面量语法
-- 不支持 `Object.keys()`、`Object.values()`、`Object.entries()`
-- 嵌套的数组方法调用可能导致类型推断失败,应改用 for 循环
-- `any` 类型属性访问需转换为 `Record` 后用索引访问
-- 函数必须在使用前定义
-- 对象字面量 `{...}` 只能用于构造类型(class),不能用于接口(interface)
-- 需要创建动态对象时,应使用 `new UTSJSONObject()` 然后调用 `.set()` 方法
-- 对于 `type` 定义的对象类型,同样需要使用 `UTSJSONObject`
-- 对于 `any[]` 或 `reactive` 数组,访问元素属性时需要先转换为 `Record` 或 `any[]`
-- 使用索引访问属性时,推荐使用方括号语法 `obj['property']` 而非点语法 `obj.property`
-- 这是因为 UTS 的类型系统比 TypeScript 更严格,需要在运行时明确类型转换
-- `reactive` 对象在 UTS 中不支持索引器赋值操作
-- 对于需要整体替换的数组,推荐使用 `ref` 而非 `reactive`
-- 使用 `ref` 时,通过 `.value` 进行整体替换可以正确触发响应式更新
-- 函数必须在使用前声明(不支持函数提升)
-- 访问 `any` 类型对象的属性需要先转换为 `Record`
-- `ref` 数组元素不能直接整体替换,需要修改元素属性
-- 对于可能为 `null` 的参数,需要显式检查后再传递给函数
-- 函数必须在使用前声明(不支持函数提升)
-- 依赖关系需要明确:被调用的函数必须先定义
-- 这与 JavaScript 的函数提升行为不同,UTS 更接近 C/Java 的编译方式
-- UTS中箭头函数 `() => {}` 有时会导致"Parenthesized expression cannot be empty"错误
-- 解决方案:使用普通函数 `function name(): Type {}` 代替箭头函数
-- 变量声明必须有显式类型或初始化值
-- 对象属性访问使用 `obj['key']` 而非 `obj.key`
-- 数组赋值时类型必须匹配
+================================================================================
+ UTS-Android 兼容性开发规范
+================================================================================
+> 以下为 uni-app-x (UTS) Android 端开发常见注意事项与踩坑点,建议所有开发成员遵循:
+================================================================================
+一、基础语法规范
+================================================================================
+1. 变量声明
+ - 只能使用 let 和 const,不能使用 var
+ - 变量声明必须有显式类型或初始化值
+ - 不支持 undefined 类型,变量未赋值就是 null
+ - 不支持 undefined 关键字,判断是否存在要用 != null
-PS D:\companyproject\mall-trae2> npm run build:app 2>&1
-npm error Missing script: "build:app"
-npm error
-npm error To see a list of scripts, run:
-npm error npm run
-npm error A complete log of this run can be found in: C:\Users\Huawei\AppData\Local\npm-cache\_logs\2026-02-24T00_58_23_018Z-debug-0.log
\ No newline at end of file
+2. 类型定义
+ - 只适合转 type,不适合使用 interface(interface 在 kotlin/swift 中另有不同)
+ - 不支持 Intersection Type(交叉类型)
+ - 不支持 Index Signature(索引签名)
+ - 类型推断严格,必要时用 as Type 明确类型
+ - 不支持内联对象类型(Object Literal Type),需要单独定义 type
+
+3. 函数定义
+ - 函数必须在使用前定义(不支持函数提升)
+ - 在 setup 模式下,调用的函数必须在调用之前定义
+ - 依赖关系需要明确:被调用的函数必须先定义
+ - 这与 JavaScript 的函数提升行为不同,UTS 更接近 C/Java 的编译方式
+
+4. 循环
+ - for 循环的 i 必须写明类型:let i: Int = 0
+ - 不要用 forEach、map,数组遍历用 for 循环
+ - 嵌套的数组方法调用可能导致类型推断失败,应改用 for 循环
+
+================================================================================
+二、类型与对象访问
+================================================================================
+
+1. any 类型访问
+ - 不能直接访问 any 类型对象的属性
+ - 需要将对象转换为 UTSJSONObject 类型后使用 getString()、getNumber() 等方法访问属性
+ - any 类型属性访问需转换为 Record 后用索引访问
+ - 使用索引访问属性时,推荐使用方括号语法 obj['property'] 而非点语法 obj.property
+ - any 类型不支持索引访问 obj['key'],必须先转换为 UTSJSONObject
+
+2. UTSJSONObject 使用
+ - 用 utils/utis 下的 UTSJSONObject 做类型转换
+ - 不要用 safeget,只要 UTSJSONObject 就好了
+ - 需要创建动态对象时,应使用 new UTSJSONObject() 然后调用 .set() 方法
+ - 对于 type 定义的对象类型,同样需要使用 UTSJSONObject
+ - 使用 getString()、getNumber() 方法获取属性值
+
+3. 数组类型
+ - 数组类型建议写成 Array,不要用 Type[] 简写
+ - 空数组需要明确指定类型,如 [] as string[]
+ - 数组元素需要明确的类型定义才能在模板中正确访问属性
+ - 对于 any[] 或 reactive 数组,访问元素属性时需要先转换为 Record 或 any[]
+
+4. 对象操作
+ - 不支持 Object.keys()、Object.values()、Object.entries()
+ - 不支持 Record 对象字面量语法
+ - 对象字面量 {...} 只能用于构造类型(class),不能用于接口(interface)
+ - reactive 对象在 UTS 中不支持索引器赋值操作
+
+================================================================================
+三、条件判断与逻辑运算
+================================================================================
+
+1. if 条件
+ - if 判断只接受 boolean 类型,不能是其他类型的值
+ - 判断空要用 !== null,不能用 !变量(uts android 不支持 !在变量前面的判断空方式)
+ - 模板中的 || 运算符左边必须是 boolean 类型
+ - 可空类型使用可选链 ?. 和空值合并 ??
+ - 字符串判断空要用:variable != null && variable !== ''
+
+2. 逻辑运算符
+ - || 表示逻辑或
+ - && 表示逻辑与
+ - ! 表示逻辑非(但 !变量 不支持用于判断空)
+ - ?? 表示空值合并运算符(当左侧为 null 时返回右侧值)
+ - ts 的为空则使用默认值的语法在 uts 中不能用 ||,要用 ?? 来代替
+
+================================================================================
+四、组件与模板
+================================================================================
+
+1. 表单与输入
+ - 表单优先用 form 组件
+ - 不支持 uni-easyinput,用 input 代替
+ - 时间选择用 uni_modules/lime-date-time-picker
+
+2. 选择器
+ - uts android 不支持 picker,用 picker-view 或 uni.showActionSheet
+ - 一维的优先用 uni.showActionSheet
+ - picker-view 的事件用 UniPickerViewChangeEvent
+
+3. 导航与布局
+ - 不支持 uni-nav-bar,先删除
+ - 不支持 uni-data-select,用 picker-view 代替
+ - 不支持 uni-datetime-picker,用 components/picker-date 或 components/picker-time 代替
+ - 不支持 uni-icons
+
+4. 模板注意事项
+ - 跟 template 交互的变量尽量用一维变量(不要嵌套对象)
+ - 模板中可空类型必须使用 ?. 安全访问
+ - 模板中访问可空类型属性前必须先判空 v-if="order != null"
+
+================================================================================
+五、CSS 样式限制
+================================================================================
+
+1. 布局方式
+ - 只支持 display: flex
+ - 不支持 display: grid
+ - 不支持 gap
+ - 不支持 table、grid、grid-template-columns
+
+2. 单位与计算
+ - 不支持 calc()
+ - 不支持的单位: vh
+ - property value `100%` is not supported for min-height (supported values are: number|pixel)
+ - property value `calc(33.33% - 10px)` is not supported for min-width
+
+3. 选择器
+ - [APP-ANDROID] 不支持伪类选择器
+ - [APP-IOS] 不支持伪类选择器
+ - ERROR: Selector `.login-button[disabled]` is not supported. uvue only support classname selector
+
+4. 其他样式
+ - WARNING: `backdrop-filter` is not a standard property name
+ - style property `white-space` is only supported on `|
{{ product.approval_number }}
-
+
标签
- {{ product.tags.join(', ') }}
+ {{ product.tags!.join(', ') }}
@@ -226,7 +226,7 @@
@@ -503,10 +600,6 @@ const goBack = () => {
border-bottom: 1px solid #f5f5f5;
}
-.product-review:last-child {
- border-bottom: none;
-}
-
.product-header {
display: flex;
margin-bottom: 20px;
@@ -643,9 +736,7 @@ const goBack = () => {
align-items: center;
justify-content: center;
}
-margin-right: 10px;
- margin-bottom: 10px;
-
+
.upload-btn {
width: 70px;
height: 70px;
@@ -738,10 +829,6 @@ margin-right: 10px;
margin-bottom: 5px;
}
-.tip-item:last-child {
- margin-bottom: 0;
-}
-
.submit-section {
background-color: #ffffff;
padding: 15px;
diff --git a/pages/mall/consumer/search.uvue b/pages/mall/consumer/search.uvue
index 5ae51201..90752f1f 100644
--- a/pages/mall/consumer/search.uvue
+++ b/pages/mall/consumer/search.uvue
@@ -51,13 +51,13 @@
-
+
@@ -136,7 +136,7 @@
-
+
相关店铺
-
+
([])
-const hotSearchList = ref([])
-const guessList = ref([])
-const allGuessItems = ref([]) // 缓存所有猜你喜欢商品
-const searchResults = ref([])
-const searchShopResults = ref([]) // 搜索到的店铺
-
-
-
-onMounted(() => {
- initPage()
-})
-
-
-const initPage = () => {
- try {
- const systemInfo = uni.getSystemInfoSync()
- statusBarHeight.value = systemInfo.statusBarHeight ?? 0
- const windowHeight = systemInfo.windowHeight
- // 减去头部高度 (约60px + statusBarHeight)
- scrollHeight.value = windowHeight - (60 + statusBarHeight.value)
-
- loadData()
-
- // 检查页面参数
- const pages = getCurrentPages()
- if (pages.length > 0) {
- const currentPage = pages[pages.length - 1]
- // @ts-ignore
- const options = currentPage.options
- if (options && options['keyword']) {
- const keyword = decodeURIComponent(options['keyword'])
- searchKeyword.value = keyword
-
- if (options['type'] === 'family' || options['type'] === 'brand') {
- // 如果是家庭常备药或品牌类型,直接添加到历史并搜索
- if (options['type'] === 'family') {
- addToHistory(keyword)
- }
- // 立即显示结果区域并设置为加载中
- showResults.value = true
- loading.value = true
- // 确保searchResults不为空数组导致闪烁(虽然loading=true已经拦截了empty-result,但双重保险)
- // 此时不要置空searchResults,或者给一个初始值
-
- // 直接调用搜索,移除setTimeout,防止中间状态
- performSearch()
- }
- }
- }
- } catch (e) {
- console.error('初始化失败', e)
- isError.value = true
- }
+type HotSearchItemType = {
+ keyword: string
+ hot: boolean
}
-// 加载基础数据
-const loadData = async () => {
- isError.value = false
-
- try {
- loadSearchHistory()
- // 获取热门商品作为热门搜索推荐和猜你喜欢
- // 获取更多数据以便"换一批"
- const hotProducts = await supabaseService.getHotProducts(30)
-
- hotSearchList.value = hotProducts.slice(0, 10).map((p: any) => ({
- keyword: p.name,
- hot: true
- }))
-
- allGuessItems.value = hotProducts.map((p: any) => ({
- id: p.id,
- name: p.name,
- price: p.base_price,
- image: p.main_image_url ?? '/static/default.jpg',
- sales: typeof p.sale_count === 'number' ? p.sale_count : 0
- }))
-
- // 初始显示随机6个
- refreshGuessListItems()
-
- } catch (e) {
- console.error('Load data failed', e)
- isError.value = true
- }
+type GuessItemType = {
+ id: string
+ name: string
+ price: number
+ image: string
+ sales: number
}
-// 点击重试
-const retryLoad = () => {
- uni.showLoading({ title: '重新加载中' })
- setTimeout(() => {
- uni.hideLoading()
- loadData()
- }, 1000)
+type SearchResultType = {
+ id: string
+ name: string
+ image: string
+ price: number
+ specification: string
+ tag: string
+ sales: number
}
-// 历史记录管理
+type ShopResultType = {
+ id: string
+ name: string
+ logo: string
+ productCount: number
+}
+
+const searchHistory = ref>([])
+const hotSearchList = ref>([])
+const guessList = ref>([])
+const allGuessItems = ref>([])
+const searchResults = ref>([])
+const searchShopResults = ref>([])
+
const loadSearchHistory = () => {
const history = uni.getStorageSync('searchHistory')
- if (history) {
+ if (history != null) {
try {
- // 确保是数组
const parsed = JSON.parse(history as string)
if (Array.isArray(parsed)) {
searchHistory.value = parsed as string[]
@@ -414,13 +351,258 @@ const deleteHistoryItem = (index: number) => {
saveSearchHistory()
}
-// 搜索建议 - 改为实时获取
-const searchSuggestions = ref([])
-let suggestTimer = 0
+const refreshGuessListItems = () => {
+ if (allGuessItems.value.length > 0) {
+ const arr: Array = []
+ for (let i: number = 0; i < allGuessItems.value.length; i++) {
+ arr.push(allGuessItems.value[i])
+ }
+ for (let i: number = arr.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1))
+ const temp = arr[i]
+ arr[i] = arr[j]
+ arr[j] = temp
+ }
+ const result: Array = []
+ const limit = arr.length < 6 ? arr.length : 6
+ for (let i: number = 0; i < limit; i++) {
+ result.push(arr[i])
+ }
+ guessList.value = result
+ }
+}
+
+const loadData = async (): Promise => {
+ isError.value = false
+
+ try {
+ loadSearchHistory()
+ const hotProducts = await supabaseService.getHotProducts(30)
+
+ const hotList: Array = []
+ const limit1 = hotProducts.length < 10 ? hotProducts.length : 10
+ for (let i: number = 0; i < limit1; i++) {
+ const p = hotProducts[i] as UTSJSONObject
+ const item: HotSearchItemType = {
+ keyword: p.getString('name') ?? '',
+ hot: true
+ }
+ hotList.push(item)
+ }
+ hotSearchList.value = hotList
+
+ const allItems: Array = []
+ for (let i: number = 0; i < hotProducts.length; i++) {
+ const p = hotProducts[i] as UTSJSONObject
+ const saleCount = p.getNumber('sale_count')
+ const item: GuessItemType = {
+ id: p.getString('id') ?? '',
+ name: p.getString('name') ?? '',
+ price: p.getNumber('base_price') ?? 0,
+ image: p.getString('main_image_url') ?? '/static/default.jpg',
+ sales: saleCount != null ? saleCount : 0
+ }
+ allItems.push(item)
+ }
+ allGuessItems.value = allItems
+
+ refreshGuessListItems()
+
+ } catch (e) {
+ console.error('Load data failed', e)
+ isError.value = true
+ }
+}
+
+const retryLoad = () => {
+ uni.showLoading({ title: '重新加载中' })
+ setTimeout(() => {
+ uni.hideLoading()
+ loadData()
+ }, 1000)
+}
+
+const searchSuggestions = ref>([])
+let suggestTimer: number = 0
+
+const fetchSuggestions = async (kw: string): Promise => {
+ if (kw == '' || showResults.value) return
+
+ try {
+ const res = await supabaseService.searchProducts(kw.trim(), 1, 5)
+ if (res.data != null && res.data.length > 0) {
+ const names: Array = []
+ for (let i: number = 0; i < res.data.length; i++) {
+ const p = res.data[i]
+ let name = ''
+ if (p instanceof UTSJSONObject) {
+ name = p.getString('name') ?? ''
+ } else {
+ const pObj = p as UTSJSONObject
+ name = pObj.getString('name') ?? ''
+ }
+ let found = false
+ for (let j: number = 0; j < names.length; j++) {
+ if (names[j] === name) {
+ found = true
+ break
+ }
+ }
+ if (found === false && name !== '') {
+ names.push(name)
+ }
+ }
+ searchSuggestions.value = names
+ } else {
+ searchSuggestions.value = []
+ }
+ } catch(e) {
+ searchSuggestions.value = []
+ }
+}
+
+const currentPage = ref(1)
+
+const performSearch = async (): Promise => {
+ showResults.value = true
+ loading.value = true
+ currentPage.value = 1
+
+ const keyword = searchKeyword.value.trim()
+ if (keyword == '') {
+ loading.value = false
+ return
+ }
+
+ let sortBy = 'sales'
+ let ascending = false
+ if (activeSort.value === 'price') {
+ sortBy = 'price'
+ ascending = priceSortAsc.value
+ } else if (activeSort.value === 'default') {
+ sortBy = 'default'
+ }
+
+ try {
+ const prodResp = await supabaseService.searchProducts(keyword, currentPage.value, 20, sortBy, ascending)
+
+ let shopRespData: Array = []
+ if (currentPage.value === 1 && activeSort.value === 'default') {
+ const shopResp = await supabaseService.searchShops(keyword)
+ if (shopResp.data != null) {
+ const rawData = shopResp.data
+ for (let i: number = 0; i < rawData.length; i++) {
+ shopRespData.push(rawData[i])
+ }
+ }
+ }
+
+ if (shopRespData.length > 0) {
+ const shopList: Array = []
+ for (let i: number = 0; i < shopRespData.length; i++) {
+ const s = shopRespData[i] as UTSJSONObject
+ const shopItem: ShopResultType = {
+ id: s.getString('id') ?? '',
+ name: s.getString('shop_name') ?? '',
+ logo: s.getString('shop_logo') ?? '/static/shop_logo_default.png',
+ productCount: s.getNumber('product_count') ?? 0
+ }
+ shopList.push(shopItem)
+ }
+ searchShopResults.value = shopList
+ } else {
+ searchShopResults.value = []
+ }
+
+ const prodData = prodResp.data != null ? prodResp.data : []
+ const resultList: Array = []
+ for (let i: number = 0; i < prodData.length; i++) {
+ const p = prodData[i] as UTSJSONObject
+ let tag = ''
+ const tagsRaw = p.get('tags')
+ if (tagsRaw != null) {
+ try {
+ const tagsStr = p.getString('tags')
+ if (tagsStr != null) {
+ const tags = JSON.parse(tagsStr)
+ if (Array.isArray(tags) && tags.length > 0) {
+ const firstTag = tags[0]
+ tag = firstTag != null ? (firstTag as string) : ''
+ }
+ }
+ } catch(e) {}
+ }
+
+ const searchItem: SearchResultType = {
+ id: p.getString('id') ?? '',
+ name: p.getString('name') ?? '',
+ image: p.getString('main_image_url') ?? '/static/default.jpg',
+ price: p.getNumber('base_price') ?? 0,
+ specification: p.getString('specification') ?? '标准规格',
+ tag: tag,
+ sales: p.getNumber('sale_count') ?? 0
+ }
+ resultList.push(searchItem)
+ }
+ searchResults.value = resultList
+
+ hasMore.value = prodResp.hasmore
+ } catch(e) {
+ console.error('Search failed', e)
+ } finally {
+ loading.value = false
+ }
+}
+
+const initPage = () => {
+ try {
+ const systemInfo = uni.getSystemInfoSync()
+ statusBarHeight.value = systemInfo.statusBarHeight ?? 0
+ const windowHeight = systemInfo.windowHeight
+ scrollHeight.value = windowHeight - (60 + statusBarHeight.value)
+
+ loadData()
+
+ const pages = getCurrentPages()
+ if (pages.length > 0) {
+ const currentPageObj = pages[pages.length - 1]
+ // @ts-ignore
+ const options = currentPageObj.options
+ if (options != null) {
+ const optObj = options as UTSJSONObject
+ const kwRaw = optObj.getString('keyword')
+ if (kwRaw != null && kwRaw !== '') {
+ const decoded = decodeURIComponent(kwRaw)
+ const keyword = decoded != null ? decoded : kwRaw
+ searchKeyword.value = keyword
+
+ const typeVal = optObj.getString('type')
+ if (typeVal === 'family' || typeVal === 'brand') {
+ if (typeVal === 'family') {
+ addToHistory(keyword)
+ }
+ showResults.value = true
+ loading.value = true
+ performSearch()
+ }
+ }
+ }
+ }
+ } catch (e) {
+ console.error('初始化失败', e)
+ isError.value = true
+ }
+}
+
+onMounted(() => {
+ initPage()
+})
-// 搜索逻辑
const onInput = (e: any) => {
- const val = e.detail.value
+ const eObj = e as UTSJSONObject
+ const detailRaw = eObj.get('detail')
+ const detail = detailRaw != null ? (detailRaw as UTSJSONObject) : (new UTSJSONObject())
+ const val = detail.getString('value') ?? ''
searchKeyword.value = val
if (val == '') {
showResults.value = false
@@ -428,37 +610,12 @@ const onInput = (e: any) => {
return
}
- // Debounce suggestion search
if (suggestTimer > 0) clearTimeout(suggestTimer)
suggestTimer = setTimeout(() => {
fetchSuggestions(val)
}, 300)
}
-const fetchSuggestions = async (kw: string) => {
- if (kw == '' || showResults.value) return
-
- // 简单搜索前5个相关商品作为建议
- try {
- const res = await supabaseService.searchProducts(kw.trim(), 1, 5)
- if (Array.isArray(res.data) && res.data.length > 0) {
- // 去重
- const names = res.data.map((p:any) :string => {
- if(p instanceof UTSJSONObject){
- return p.getString('name') ?? ''
- }
- return p['name'] as string
- })
- // @ts-ignore
- searchSuggestions.value = Array.from(new Set(names))
- } else {
- searchSuggestions.value = []
- }
- } catch(e) {
- searchSuggestions.value = []
- }
-}
-
const clearSearch = () => {
searchKeyword.value = ''
showResults.value = false
@@ -487,84 +644,6 @@ const selectSuggestion = (suggestion: string) => {
performSearch()
}
-const currentPage = ref(1)
-
-const performSearch = async () => {
- // 再次强制设置状态,确保万无一失
- showResults.value = true
- loading.value = true
- // 重置页码
- currentPage.value = 1
-
- // 使用 Supabase 搜索真实数据
- const keyword = searchKeyword.value.trim()
- if (keyword == '') {
- loading.value = false
- return
- }
-
- // 确定排序方式
- let sortBy = 'sales'
- let ascending = false
- if (activeSort.value === 'price') {
- sortBy = 'price'
- ascending = priceSortAsc.value
- } else if (activeSort.value === 'default') {
- sortBy = 'default'
- }
-
- try {
- // 并行请求:商品搜索 + 店铺搜索
- const [prodResp, shopResp] = await Promise.all([
- supabaseService.searchProducts(keyword, currentPage.value, 20, sortBy, ascending),
- // 只有第一页搜索且非价格排序时搜索店铺,避免重复和无关搜索
- currentPage.value === 1 && activeSort.value === 'default'
- ? supabaseService.searchShops(keyword)
- : Promise.resolve({ data: [], total: 0, page: 1, limit: 0, hasmore: false })
- ])
-
- // 处理店铺结果
- if (shopResp.data.length > 0) {
- searchShopResults.value = shopResp.data.map((s: any) => ({
- id: s.id,
- name: s.shop_name,
- logo: s.shop_logo ?? '/static/shop_logo_default.png',
- productCount: s.product_count ?? 0
- }))
- } else {
- searchShopResults.value = []
- }
-
- // 处理商品结果
- searchResults.value = prodResp.data.map((p: any) => {
- let tag = ''
- if (p.tags) {
- try {
- const tags = (typeof p.tags === 'string') ? JSON.parse(p.tags) : p.tags
- if (Array.isArray(tags) && tags.length > 0) tag = String(tags[0])
- } catch(e) {}
- }
-
- return {
- id: p.id,
- name: p.name,
- image: p.main_image_url ?? '/static/default.jpg',
- price: p.base_price,
- specification: p.specification ?? '标准规格',
- tag: tag,
- sales: p.sale_count ?? 0
- }
- })
-
- hasMore.value = prodResp.hasmore
- } catch(e) {
- console.error('Search failed', e)
- } finally {
- loading.value = false
- }
-}
-
-// 切换排序
const switchSort = (type: string) => {
if (type === 'price') {
if (activeSort.value === 'price') {
@@ -580,15 +659,13 @@ const switchSort = (type: string) => {
performSearch()
}
-const loadMore = async () => {
- if (loading.value || !hasMore.value || searchKeyword.value.trim() == '') return
+const loadMore = async (): Promise => {
+ if (loading.value || hasMore.value == false || searchKeyword.value.trim() == '') return
loading.value = true
- // 增加页码
currentPage.value++
const keyword = searchKeyword.value.trim()
- // 确定排序方式
let sortBy = 'sales'
let ascending = false
if (activeSort.value === 'price') {
@@ -600,26 +677,35 @@ const loadMore = async () => {
try {
const response = await supabaseService.searchProducts(keyword, currentPage.value, 20, sortBy, ascending)
- const newItems = response.data.map((p: any) => {
+ const respData = response.data != null ? response.data : []
+ for (let i: number = 0; i < respData.length; i++) {
+ const p = respData[i] as UTSJSONObject
let tag = ''
- if (p.tags) {
+ const tagsRaw = p.get('tags')
+ if (tagsRaw != null) {
try {
- const tags = (typeof p.tags === 'string') ? JSON.parse(p.tags) : p.tags
- if (Array.isArray(tags) && tags.length > 0) tag = String(tags[0])
+ const tagsStr = p.getString('tags')
+ if (tagsStr != null) {
+ const tags = JSON.parse(tagsStr)
+ if (Array.isArray(tags) && tags.length > 0) {
+ const firstTag = tags[0]
+ tag = firstTag != null ? (firstTag as string) : ''
+ }
+ }
} catch(e) {}
}
- return {
- id: p.id,
- name: p.name,
- image: p.main_image_url ?? '/static/default.jpg',
- price: p.base_price,
- specification: p.specification ?? '标准规格',
+ const searchItem: SearchResultType = {
+ id: p.getString('id') ?? '',
+ name: p.getString('name') ?? '',
+ image: p.getString('main_image_url') ?? '/static/default.jpg',
+ price: p.getNumber('base_price') ?? 0,
+ specification: p.getString('specification') ?? '标准规格',
tag: tag,
- sales: p.sale_count ?? 0
+ sales: p.getNumber('sale_count') ?? 0
}
- })
- searchResults.value.push(...newItems)
+ searchResults.value.push(searchItem)
+ }
hasMore.value = response.hasmore
} catch(e) {
console.error('Load more failed', e)
@@ -637,29 +723,22 @@ const refreshGuessList = () => {
}, 500)
}
-const refreshGuessListItems = () => {
- if (allGuessItems.value.length > 0) {
- // 简单的随机乱序并取前6个
- const shuffled = [...allGuessItems.value].sort(() => Math.random() - 0.5)
- guessList.value = shuffled.slice(0, 6)
- }
-}
-
-const viewProductDetail = (item: any) => {
- // 跳转详情页逻辑 - 传递必要的参数作为预加载/fallback
+const viewProductDetail = (item: SearchResultType | GuessItemType) => {
+ const id = (item as GuessItemType).id
+ const price = (item as GuessItemType).price
+ const name = (item as GuessItemType).name
uni.navigateTo({
- url: `/pages/mall/consumer/product-detail?productId=${item.id}&price=${item.price}&name=${encodeURIComponent(item.name)}`
+ url: `/pages/mall/consumer/product-detail?productId=${id}&price=${price}&name=${encodeURIComponent(name)}`
})
}
-const viewShopDetail = (shop: any) => {
+const viewShopDetail = (shop: ShopResultType) => {
uni.navigateTo({
url: `/pages/mall/consumer/shop-detail?id=${shop.id}`
})
}
-// 添加到购物车 - 搜索列表无法选择规格,跳转详情页
-const addToCart = (product: any) => {
+const addToCart = (product: SearchResultType | GuessItemType) => {
uni.showToast({ title: '请选择规格', icon: 'none' })
setTimeout(() => {
viewProductDetail(product)
diff --git a/pages/mall/consumer/settings.uvue b/pages/mall/consumer/settings.uvue
index 07fc1eac..4802e7c4 100644
--- a/pages/mall/consumer/settings.uvue
+++ b/pages/mall/consumer/settings.uvue
@@ -31,8 +31,8 @@
📱
手机绑定
-
- {{ userInfo.phone ? '已绑定' : '未绑定' }}
+
+ {{ userInfo.phone != null && userInfo.phone != '' ? '已绑定' : '未绑定' }}
›
@@ -41,8 +41,8 @@
📧
邮箱绑定
-
- {{ userInfo.email ? '已绑定' : '未绑定' }}
+
+ {{ userInfo.email != null && userInfo.email != '' ? '已绑定' : '未绑定' }}
›
@@ -260,53 +260,64 @@ const currentLanguage = ref('简体中文')
const currentTheme = ref('自动')
const appVersion = ref('1.0.0')
-const statusBarHeight = ref(0)
+const statusBarHeight = ref(0)
-// 生命周期
-onMounted(() => {
- const systemInfo = uni.getSystemInfoSync()
- statusBarHeight.value = systemInfo.statusBarHeight
- loadUserInfo()
- loadSettings()
-})
-
-// 加载用户信息
const loadUserInfo = () => {
const userStore = uni.getStorageSync('userInfo')
- if (userStore) {
- userInfo.value = userStore
+ if (userStore != null) {
+ const storeObj = userStore as UTSJSONObject
+ const user: UserType = {
+ id: storeObj.getString('id') ?? '',
+ phone: storeObj.getString('phone'),
+ email: storeObj.getString('email'),
+ nickname: storeObj.getString('nickname'),
+ avatar_url: storeObj.getString('avatar_url')
+ } as UserType
+ userInfo.value = user
}
}
-// 加载设置
const loadSettings = () => {
- // 从本地存储加载设置
const savedNotifications = uni.getStorageSync('userNotifications')
- if (savedNotifications) {
- notifications.value = savedNotifications
+ if (savedNotifications != null) {
+ const notifObj = savedNotifications as UTSJSONObject
+ const notif: NotificationType = {
+ order: notifObj.getBoolean('order') ?? true,
+ promotion: notifObj.getBoolean('promotion') ?? true,
+ review: notifObj.getBoolean('review') ?? true
+ } as NotificationType
+ notifications.value = notif
}
const savedPrivacy = uni.getStorageSync('userPrivacy')
- if (savedPrivacy) {
- privacy.value = savedPrivacy
+ if (savedPrivacy != null) {
+ const privacyObj = savedPrivacy as UTSJSONObject
+ const priv: PrivacyType = {
+ hidePurchase: privacyObj.getBoolean('hidePurchase') ?? false,
+ allowSearchByPhone: privacyObj.getBoolean('allowSearchByPhone') ?? true,
+ receiveMerchantMsg: privacyObj.getBoolean('receiveMerchantMsg') ?? true
+ } as PrivacyType
+ privacy.value = priv
}
- // 计算缓存大小
- calculateCacheSize()
+ cacheSize.value = '12.5 MB'
- // 获取应用版本
- // @ts-ignore
const appInfo = uni.getAppBaseInfo()
- if (appInfo?.appVersion) {
- appVersion.value = appInfo.appVersion
+ if (appInfo != null) {
+ const infoObj = appInfo as UTSJSONObject
+ const version = infoObj.getString('appVersion')
+ if (version != null) {
+ appVersion.value = version
+ }
}
}
-// 计算缓存大小
-const calculateCacheSize = () => {
- // 这里应该计算实际缓存大小,这里使用模拟数据
- cacheSize.value = '12.5 MB'
-}
+onMounted(() => {
+ const systemInfo = uni.getSystemInfoSync()
+ statusBarHeight.value = systemInfo.statusBarHeight ?? 0
+ loadUserInfo()
+ loadSettings()
+})
// 跳转到个人资料
const goToProfile = () => {
@@ -344,14 +355,26 @@ const bindEmail = () => {
}
// 切换通知设置
-const toggleNotification = (type: keyof NotificationType) => {
- notifications.value[type] = !notifications.value[type]
+const toggleNotification = (type: string) => {
+ if (type === 'order') {
+ notifications.value.order = notifications.value.order === false
+ } else if (type === 'promotion') {
+ notifications.value.promotion = notifications.value.promotion === false
+ } else if (type === 'review') {
+ notifications.value.review = notifications.value.review === false
+ }
uni.setStorageSync('userNotifications', notifications.value)
}
// 切换隐私设置
-const togglePrivacy = (type: keyof PrivacyType) => {
- privacy.value[type] = !privacy.value[type]
+const togglePrivacy = (type: string) => {
+ if (type === 'hidePurchase') {
+ privacy.value.hidePurchase = privacy.value.hidePurchase === false
+ } else if (type === 'allowSearchByPhone') {
+ privacy.value.allowSearchByPhone = privacy.value.allowSearchByPhone === false
+ } else if (type === 'receiveMerchantMsg') {
+ privacy.value.receiveMerchantMsg = privacy.value.receiveMerchantMsg === false
+ }
uni.setStorageSync('userPrivacy', privacy.value)
}
@@ -473,28 +496,12 @@ const feedback = () => {
})
}
-// 给个好评
const rateApp = () => {
- // 这里应该跳转到应用商店评分
uni.showModal({
title: '给个好评',
- content: '如果喜欢我们的应用,请给个好评吧!',
- confirmText: '去评分',
- success: (res) => {
- if (res.confirm) {
- // 跳转到应用商店
- // @ts-ignore
- uni.navigateToMiniProgram({
- appId: 'wx1234567890', // 示例AppID
- fail: () => {
- uni.showToast({
- title: '跳转失败',
- icon: 'none'
- })
- }
- })
- }
- }
+ content: '如果喜欢我们的应用,请给个好评吧!感谢您的支持!',
+ confirmText: '好的',
+ showCancel: false
})
}
@@ -503,116 +510,76 @@ const logout = () => {
uni.showModal({
title: '退出登录',
content: '确定要退出登录吗?',
- success: async (res) => {
+ success: (res) => {
if (res.confirm) {
- try {
- uni.showLoading({
- title: '正在退出...'
- })
- // 调用登出接口
- const { error } = await supa.auth.signOut()
-
- if (error !== null) {
- console.error('登出失败:', error)
- // 即使失败也继续清除本地状态
- }
-
- // 清除本地存储的用户信息
- uni.removeStorageSync('userInfo')
- uni.removeStorageSync('user_id')
- uni.removeStorageSync('access_token')
-
- uni.hideLoading()
-
- uni.showToast({
- title: '已退出登录',
- icon: 'success'
- })
-
- setTimeout(() => {
- uni.reLaunch({
- url: '/pages/user/login'
- })
- }, 1000)
- } catch (e) {
- uni.hideLoading()
- console.error('Logout Exception:', e)
- uni.showToast({
- title: '退出异常',
- icon: 'none'
- })
- // 强制退出
- uni.removeStorageSync('userInfo')
+ uni.showLoading({
+ title: '正在退出...'
+ })
+
+ uni.removeStorageSync('userInfo')
+ uni.removeStorageSync('user_id')
+ uni.removeStorageSync('access_token')
+
+ uni.hideLoading()
+
+ uni.showToast({
+ title: '已退出登录',
+ icon: 'success'
+ })
+
+ setTimeout(() => {
uni.reLaunch({
url: '/pages/user/login'
})
- }
+ }, 1000)
}
}
})
}
-// 注销账号
const deleteAccount = () => {
uni.showModal({
title: '注销账号',
content: '确定要注销账号吗?此操作不可恢复,所有数据将被删除!',
confirmText: '注销',
confirmColor: '#ff4757',
- success: async (res) => {
+ success: (res) => {
if (res.confirm) {
- try {
- uni.showLoading({
- title: '注销中...'
+ uni.showLoading({
+ title: '注销中...'
+ })
+
+ let userId: string | null = userInfo.value.id
+ if (userId == null || userId === '') {
+ const storageId = uni.getStorageSync('user_id')
+ userId = (storageId != null) ? storageId as string : null
+ }
+
+ if (userId != null) {
+ const updateObj: UTSJSONObject = new UTSJSONObject()
+ updateObj.set('status', 3)
+ supa
+ .from('ml_user_profiles')
+ .update(updateObj)
+ .eq('user_id', userId)
+ .execute()
+ }
+
+ uni.removeStorageSync('userInfo')
+ uni.removeStorageSync('user_id')
+ uni.removeStorageSync('access_token')
+
+ uni.hideLoading()
+ uni.showToast({
+ title: '账号已注销',
+ icon: 'success',
+ duration: 2000
+ })
+
+ setTimeout(() => {
+ uni.reLaunch({
+ url: '/pages/user/login'
})
-
- let userId = userInfo.value.getString('id')
- if (userId == null) {
- const storageId = uni.getStorageSync('user_id')
- userId = (storageId != null) ? storageId as string : null
- }
-
- if (userId != null) {
- try {
- // 标记用户状态为注销 (status=3)
- await supa
- .from('ml_user_profiles')
- .update({ status: 3 })
- .eq('user_id', userId)
- } catch(e) {
- console.error('Update status failed', e)
- }
-
- // 登出
- await supa.auth.signOut()
- }
-
- // 清除本地存储
- uni.removeStorageSync('userInfo')
- uni.removeStorageSync('user_id')
- uni.removeStorageSync('access_token')
-
- // 提示并跳转
- uni.hideLoading()
- uni.showToast({
- title: '账号已注销',
- icon: 'success',
- duration: 2000
- })
-
- setTimeout(() => {
- uni.reLaunch({
- url: '/pages/user/login'
- })
- }, 1500)
-
- } catch (err) {
- uni.hideLoading()
- console.error('注销账号失败:', err)
- uni.showToast({
- title: '注销失败',
- icon: 'none'
- })
- }
+ }, 1500)
}
}
})
diff --git a/pages/mall/consumer/shop-detail.uvue b/pages/mall/consumer/shop-detail.uvue
index df627fdd..0cfa7f50 100644
--- a/pages/mall/consumer/shop-detail.uvue
+++ b/pages/mall/consumer/shop-detail.uvue
@@ -31,7 +31,7 @@
¥{{ coupon.discount_value }}
- 满{{ coupon.min_order_amount }}
+ 满{{ coupon.min_order_amount }}
无门槛
@@ -70,7 +70,7 @@
\ No newline at end of file
+
diff --git a/pages/mall/consumer/wallet.uvue b/pages/mall/consumer/wallet.uvue
index b14ecc1c..acacbade 100644
--- a/pages/mall/consumer/wallet.uvue
+++ b/pages/mall/consumer/wallet.uvue
@@ -84,7 +84,7 @@
-
+
💰
暂无交易记录
快去使用钱包功能吧
@@ -117,7 +117,7 @@
加载中...
-
+
没有更多记录了
@@ -156,7 +156,7 @@
+
+
-
\ No newline at end of file
+.picker-actions-button {
+ flex: 1;
+ margin: 0 10rpx;
+ background: #2196f3;
+ color: #fff;
+ border-radius: 10rpx;
+ font-size: 28rpx;
+ height: 80rpx;
+}
+
diff --git a/pages/user/register.uvue b/pages/user/register.uvue
index 66514f23..7819340f 100644
--- a/pages/user/register.uvue
+++ b/pages/user/register.uvue
@@ -18,8 +18,7 @@
email = e.detail.value"
+ v-model="email"
class="input-field"
/>
@@ -32,8 +31,7 @@
password = e.detail.value"
+ v-model="password"
class="input-field"
/>
@@ -46,8 +44,7 @@
confirmPassword = e.detail.value"
+ v-model="confirmPassword"
class="input-field"
/>
@@ -55,7 +52,7 @@
-
+
注册
@@ -71,8 +68,7 @@
已阅读并同意
@@ -96,7 +92,6 @@
import supa from '@/components/supadb/aksupainstance.uts'
import { ensureUserProfile } from '@/utils/sapi.uts'
- // 响应式数据
const email = ref('')
const password = ref('')
const confirmPassword = ref('')
@@ -105,22 +100,21 @@
const isLoading = ref(false)
const logoUrl = ref('/static/logo.png')
- // 处理协议勾选变化
- const handleProtocolChange = (e: any) => {
- protocol.value = !protocol.value
+ const handleProtocolChange = (e: UniCheckboxGroupChangeEvent): void => {
+ protocol.value = protocol.value == false
}
- // 验证邮箱
const validateEmail = (): boolean => {
- if (email.value.trim() === '') {
+ if (email.value.trim() == '') {
uni.showToast({
title: '请填写邮箱',
icon: 'none'
})
return false
}
- // 基础邮箱格式校验(足够用于前端提示)
- if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value)) {
+ const atIndex = email.value.indexOf('@')
+ const dotIndex = email.value.lastIndexOf('.')
+ if (atIndex == -1 || dotIndex == -1 || atIndex > dotIndex) {
uni.showToast({
title: '请输入正确的邮箱',
icon: 'none'
@@ -130,9 +124,8 @@
return true
}
- // 验证密码
const validatePassword = (): boolean => {
- if (password.value.trim() === '') {
+ if (password.value.trim() == '') {
uni.showToast({
title: '请填写密码',
icon: 'none'
@@ -146,27 +139,18 @@
})
return false
}
- // 密码不能过于简单
- if (/^([0-9]|[a-z]|[A-Z]){0,6}$/i.test(password.value)) {
- uni.showToast({
- title: '您输入的密码过于简单',
- icon: 'none'
- })
- return false
- }
return true
}
- // 验证确认密码
const validateConfirmPassword = (): boolean => {
- if (confirmPassword.value.trim() === '') {
+ if (confirmPassword.value.trim() == '') {
uni.showToast({
title: '请确认密码',
icon: 'none'
})
return false
}
- if (confirmPassword.value !== password.value) {
+ if (confirmPassword.value != password.value) {
uni.showToast({
title: '两次输入的密码不一致',
icon: 'none'
@@ -176,10 +160,8 @@
return true
}
- // 处理注册
- const handleRegister = async () => {
- // 检查协议
- if (!protocol.value) {
+ const handleRegister = async (): Promise => {
+ if (protocol.value == false) {
inAnimation.value = true
uni.showToast({
title: '请先阅读并同意协议',
@@ -188,111 +170,85 @@
return
}
- // 表单验证
- if (!validateEmail()) {
+ if (validateEmail() == false) {
return
}
- if (!validatePassword()) {
+ if (validatePassword() == false) {
return
}
- if (!validateConfirmPassword()) {
+ if (validateConfirmPassword() == false) {
return
}
isLoading.value = true
try {
- // 使用 Supabase Auth:邮箱 + 密码注册
const result = await supa.signUp(email.value.trim(), password.value)
- console.log('📝 注册返回结果:', result)
- console.log('📝 注册返回结果(JSON):', JSON.stringify(result))
+ console.log('注册返回结果:', result)
- // 检查是否有错误(邮件发送失败等)
const errorCode = result?.getString('error_code') ?? ''
const errorMsg = result?.getString('msg') ?? ''
const code = result?.getNumber('code') ?? 0
- console.log('📝 错误代码:', errorCode, '错误信息:', errorMsg, '状态码:', code)
+ console.log('错误代码:', errorCode, '错误信息:', errorMsg, '状态码:', code)
- // 如果返回 500 错误且是邮件发送失败,但用户可能已创建
- if (code === 500 && (errorCode === 'unexpected_failure' || errorMsg.includes('confirmation email'))) {
- console.warn('⚠️ 邮件发送失败,但用户可能已创建,尝试获取用户信息')
- // 即使邮件发送失败,用户可能已经在 auth.users 中创建
- // 这里我们仍然尝试创建用户资料
+ if (code == 500 && (errorCode == 'unexpected_failure' || errorMsg.includes('confirmation email'))) {
+ console.warn('邮件发送失败,但用户可能已创建')
}
- // signUp 返回的是 UTSJSONObject,Supabase signup API 返回结构:
- // { user: {...}, session: {...} } - 如果邮箱验证未开启
- // { user: {...} } - 如果邮箱验证已开启(需要验证邮箱后才能登录)
- // { code: 500, error_code: ..., msg: ... } - 如果发生错误(但用户可能已创建)
let user: UTSJSONObject | null = null
let hasSession = false
if (result != null) {
- // 尝试获取 user 字段
const userField = result.getJSON('user')
if (userField != null) {
user = userField
- console.log('✅ 找到 user 字段:', user.getString('id'), user.getString('email'))
+ console.log('找到 user 字段:', user.getString('id'), user.getString('email'))
} else {
- // 如果没有 user 字段,可能 result 本身就是 user 对象
const id = result.getString('id')
- if (id != null && id !== '') {
+ if (id != null && id != '') {
user = result
- console.log('✅ result 本身就是 user 对象:', id)
+ console.log('result 本身就是 user 对象:', id)
} else {
- // console.warn('⚠️ 未找到 user 信息,检查所有字段:', Object.keys(result.toMap() || {}))
- console.warn('⚠️ 未找到 user 信息,检查所有字段')
+ console.warn('未找到 user 信息')
}
}
- // 检查是否有 session(表示注册后自动登录成功)
const sessionField = result.getJSON('session')
if (sessionField != null) {
hasSession = true
- console.log('✅ 找到 session,已自动登录')
- // 如果有 session,说明已经自动登录,token 应该已经设置
- // 此时可以直接创建用户资料
+ console.log('找到 session,已自动登录')
} else {
- console.log('ℹ️ 未找到 session,可能需要邮箱验证')
+ console.log('未找到 session,可能需要邮箱验证')
}
}
- // 如果返回错误且没有用户信息,说明注册失败
- if (user == null && code !== 0 && code !== 200) {
- // 如果是邮件发送失败,给出明确的错误提示
- if (code === 500 && errorMsg.includes('confirmation email')) {
- throw new Error('注册失败:邮件服务配置错误,请联系管理员或修改 Supabase 配置(设置 ENABLE_EMAIL_AUTOCONFIRM=true)')
+ if (user == null && code != 0 && code != 200) {
+ if (code == 500 && errorMsg.includes('confirmation email')) {
+ throw new Error('注册失败:邮件服务配置错误')
} else {
throw new Error(errorMsg != '' ? errorMsg : '注册失败,请重试')
}
}
- // 如果获取到 user,尝试创建业务侧用户资料(ak_users)
if (user != null) {
try {
const profileResult = await ensureUserProfile(user)
if (profileResult != null) {
- console.log('✅ 用户资料创建成功:', profileResult.id)
+ console.log('用户资料创建成功:', profileResult.id)
} else {
- console.warn('⚠️ 用户资料创建失败,但注册已成功')
- // 如果创建失败,可能是因为 RLS 策略限制
- // 建议用户登录后再自动创建(在 getCurrentUser 中处理)
+ console.warn('用户资料创建失败,但注册已成功')
}
} catch (profileError) {
- console.error('❌ 创建用户资料异常:', profileError)
- // 即使创建资料失败,也不阻止注册流程
- // 用户登录时会自动创建(见 utils/store.uts 的 getCurrentUser)
+ console.error('创建用户资料异常:', profileError)
}
} else {
- console.warn('⚠️ 注册成功但未获取到用户信息')
- // 可能需要邮箱验证,用户验证邮箱后登录时会自动创建资料
+ console.warn('注册成功但未获取到用户信息')
}
- // 如果注册后没有自动登录(需要邮箱验证),提示用户
- if (!hasSession && user != null) {
- console.log('ℹ️ 需要邮箱验证,验证后登录时会自动创建用户资料')
+ if (hasSession == false && user != null) {
+ console.log('需要邮箱验证')
}
uni.showToast({
@@ -309,11 +265,10 @@
console.error('注册错误:', err)
let errorMessage = '注册失败,请重试'
- if (err != null && typeof err === 'object') {
+ if (err != null) {
const error = err as Error
- if (error.message != null && error.message.trim() !== '') {
+ if (error.message != null && error.message.trim() != '') {
errorMessage = error.message
- // 如果是邮件发送失败,给出更友好的提示
if (error.message.includes('confirmation email') || error.message.includes('邮件')) {
errorMessage = '注册可能成功,但邮件发送失败,请稍后尝试登录'
}
@@ -330,26 +285,13 @@
}
}
- // 跳转到登录页
- const navigateToLogin = () => {
- const pages = getCurrentPages() as any[]
- const currentPage = pages.length > 0 ? pages[pages.length - 1] : null
- const opts = currentPage?.options as any
- const redirect = opts?.redirect as string | null
-
- if (redirect != null && redirect.length > 0) {
- uni.navigateTo({
- url: `/pages/user/login?redirect=${redirect}`
- })
- } else {
- uni.navigateTo({
- url: '/pages/user/login'
- })
- }
+ const navigateToLogin = (): void => {
+ uni.navigateTo({
+ url: '/pages/user/login'
+ })
}
- // 跳转到协议页面
- const navigateToTerms = (type: number) => {
+ const navigateToTerms = (type: number): void => {
uni.navigateTo({
url: `/pages/user/terms?type=${type}`
})
@@ -357,10 +299,6 @@
\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\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__YOUR_APP_ID__\"\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 GenPagesMallConsumerSubscriptionPlanListClass from './pages/mall/consumer/subscription/plan-list.uvue'\nimport GenPagesMallConsumerSubscriptionPlanDetailClass from './pages/mall/consumer/subscription/plan-detail.uvue'\nimport GenPagesMallConsumerSubscriptionSubscribeCheckoutClass from './pages/mall/consumer/subscription/subscribe-checkout.uvue'\nimport GenPagesMallConsumerSubscriptionMySubscriptionsClass from './pages/mall/consumer/subscription/my-subscriptions.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/plan-list\", component: GenPagesMallConsumerSubscriptionPlanListClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"软件订阅\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/subscription/plan-detail\", component: GenPagesMallConsumerSubscriptionPlanDetailClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"订阅详情\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/subscription/subscribe-checkout\", component: GenPagesMallConsumerSubscriptionSubscribeCheckoutClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"确认订阅\"]]) } as UniPageRoute)\n__uniRoutes.push({ path: \"pages/mall/consumer/subscription/my-subscriptions\", component: GenPagesMallConsumerSubscriptionMySubscriptionsClass, meta: { isQuit: false } as UniPageMeta, style: _uM([[\"navigationBarTitleText\",\"我的订阅\"]]) } 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","// 用户基础信息类型\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; // 所属学校ID\r\n grade_id?: string; // 所属年级ID \r\n class_id?: string; // 所属班级ID\r\n}\r\n\r\n// 语言选项类型 - 对应 ak_languages 表\r\nexport type LanguageOption = {\r\n id: string; // UUID\r\n code: string; // 语言代码,如 'zh-CN', 'en-US'\r\n name: string; // 英文名称\r\n native_name: string; // 本地语言名称\r\n}\r\n\r\nexport type UserStats = {\r\n trainings: number;\r\n points: number;\r\n streak: number;\r\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 interface Brand {\r\n id: string\r\n name: string\r\n logo_url: string\r\n description: string\r\n}\r\n\r\nexport interface Category {\r\n id: string\r\n name: string\r\n icon: string\r\n description: string\r\n color: string\r\n created_at?: string\r\n}\r\n\r\nexport interface 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_urls?: string // JSON string array\r\n video_urls?: string // JSON string array\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 tags?: string // array string in DB\r\n attributes?: string // JSON string\r\n created_at?: string\r\n updated_at?: string\r\n // Alias fields for compatibility\r\n price?: number\r\n original_price?: number\r\n stock?: number\r\n sales?: number\r\n images?: string\r\n cover?: string\r\n // View fields\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 interface 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 interface 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 interface 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 interface 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 interface 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 interface 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:270','获取用户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:279','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:305','获取分类失败:', response.error)\r\n return []\r\n }\r\n \r\n return response.data as Category[]\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:311','获取分类异常:', error)\r\n return []\r\n }\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:327','获取品牌失败:', response.error)\r\n return []\r\n }\r\n \r\n return response.data as Brand[]\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:333','获取品牌异常:', 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:356','获取商品失败:', 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:374','获取商品异常:', 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:396','获取商品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:402','获取商品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:438','搜索商品失败:', 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:456','搜索商品异常:', 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:485','搜索店铺失败:', 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:504','搜索店铺异常:', 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:520','获取商品详情失败:', 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:526','获取商品详情异常:', 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:546','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:564','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:581','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:598','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:604','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:625','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:634','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:641','获取店铺信息失败:', response.error)\r\n }\r\n return null\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:645','获取店铺信息异常:', 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:653','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:670','获取商户商品失败 (View):', response.error)\r\n } else {\r\n __f__('log','at utils/supabaseService.uts:672','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:676','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:688','获取商户商品失败 (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:692',`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:710',`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:719','获取商户商品异常:', 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:743','获取热销商品失败:', 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:749','获取热销商品异常:', 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:766','获取价格排序商品失败:', 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:772','获取价格排序商品异常:', 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:790','获取新品失败:', 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:796','获取新品异常:', 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:815','获取推荐商品失败:', response.error)\r\n return []\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:819','推荐商品查询结果条数:', (response.data as any[])?.length ?? 0)\r\n const data = response.data as Product[]\r\n return data ?? []\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:823','获取推荐商品异常:', 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:839','用户未登录,无法获取购物车')\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:855','获取购物车失败:', 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:860','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:1170','获取购物车异常:', 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:1193','获取通知失败:', 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:1198','获取通知异常:', 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:1217','获取聊天会话失败:', 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:1222','获取聊天会话异常:', 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:1242','获取聊天记录失败:', 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:1247','获取聊天记录异常:', 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:1267','获取聊天记录失败:', 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:1272','获取聊天记录异常:', 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:1300','发送消息失败:', response.error)\r\n return false\r\n }\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:1305','发送消息异常:', 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:1337','用户未登录,无法添加商品到购物车')\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:1375','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:1409','购物车已有商品但缺少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:1429','添加商品到购物车失败:', 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:1435','添加商品到购物车异常:', 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:1445','用户未登录,无法更新购物车')\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:1465','更新购物车商品数量失败:', 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:1471','更新购物车商品数量异常:', 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:1481','用户未登录,无法更新购物车')\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:1496','更新购物车商品选中状态失败:', 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:1502','更新购物车商品选中状态异常:', 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:1512','用户未登录,无法更新购物车')\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:1527','批量更新购物车商品选中状态失败:', 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:1533','批量更新购物车商品选中状态异常:', 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:1543','正在执行删除购物车商品,ID:', cartItemId)\r\n const userId = this.getCurrentUserId()\r\n if (!userId) {\r\n __f__('error','at utils/supabaseService.uts:1546','用户未登录,无法删除购物车商品')\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:1558','删除购物车商品失败:', 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:1564','删除购物车商品异常:', 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:1577','用户未登录,无法删除购物车商品')\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:1589','批量删除购物车商品失败:', 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:1595','批量删除购物车商品异常:', 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:1608','用户未登录,无法清空购物车')\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:1619','清空购物车失败:', 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:1625','清空购物车异常:', 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:1635','[getAddresses] 用户未登录,无法获取地址')\r\n return []\r\n }\r\n\r\n try {\r\n __f__('log','at utils/supabaseService.uts:1640','[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:1650','[getAddresses] response.error:', response.error)\r\n __f__('log','at utils/supabaseService.uts:1651','[getAddresses] response.data:', JSON.stringify(response.data))\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1654','[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:1687','[getAddresses] 返回地址数量:', result.length)\r\n return result\r\n } catch (error) {\r\n __f__('error','at utils/supabaseService.uts:1690','[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:1699','用户未登录,无法获取地址')\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:1714','获取地址详情失败:', 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:1720','获取地址详情异常:', 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:1730','用户未登录,无法添加地址')\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:1757','添加地址失败:', 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:1763','添加地址异常:', 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:1773','用户未登录,无法更新地址')\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:1803','更新地址失败:', 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:1809','更新地址异常:', 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:1847','正在执行删除地址,ID:', addressId)\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:1850','用户未登录,无法删除地址')\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:1862','删除地址失败:', 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:1868','删除地址异常:', 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:1886','清除默认地址异常:', 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:1920','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:1955','[CreateOrder] 插入订单数据:', JSON.stringify(orderPayload))\r\n __f__('log','at utils/supabaseService.uts:1956','[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:1963','[CreateOrder] insert 完成')\r\n __f__('log','at utils/supabaseService.uts:1964','[CreateOrder] orderResponse.error:', orderResponse.error)\r\n \r\n if (orderResponse.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1967','[CreateOrder] 创建订单失败:', orderResponse.error)\r\n return null\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:1971','[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:1979','[CreateOrder] queryResponse.error:', queryResponse.error)\r\n __f__('log','at utils/supabaseService.uts:1980','[CreateOrder] queryResponse.data:', JSON.stringify(queryResponse.data))\r\n \r\n if (queryResponse.error != null) {\r\n __f__('error','at utils/supabaseService.uts:1983','[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:1993','[CreateOrder] 找到新创建的订单, id:', orderId)\r\n } else {\r\n __f__('error','at utils/supabaseService.uts:1995','[CreateOrder] 未找到新创建的订单,插入可能失败')\r\n return null\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:1999','[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:2059','[CreateOrder] 插入订单项数量:', orderItems.length)\r\n __f__('log','at utils/supabaseService.uts:2060','[CreateOrder] 订单项数据:', JSON.stringify(orderItems))\r\n \r\n const itemsResponse = await supa\r\n .from('ml_order_items')\r\n .insert(orderItems)\r\n .execute()\r\n \r\n if (itemsResponse.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2068','[CreateOrder] 创建订单项失败:', itemsResponse.error)\r\n __f__('error','at utils/supabaseService.uts:2069','[CreateOrder] 错误详情:', JSON.stringify(itemsResponse.error))\r\n __f__('log','at utils/supabaseService.uts:2070','[CreateOrder] 订单主表已创建,但订单项插入失败,返回订单ID')\r\n return orderId\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:2074','[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:2091','[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:2161','批量创建订单异常:', 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:2191','获取订单列表失败:', 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:2203','获取订单列表异常:', 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:2240','[payOrder] 用户未登录')\r\n return false\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:2244','[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:2254','[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:2264','[payOrder] 更新订单失败:', response.error)\r\n return false\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:2268','[payOrder] 订单状态更新成功')\r\n\r\n if (paymentMethod === 'balance') {\r\n __f__('log','at utils/supabaseService.uts:2271','[payOrder] 余额支付,暂不扣减余额')\r\n }\r\n\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2276','[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:2286','[getOrderById] 用户未登录')\r\n return null\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:2290','[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:2300','[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:2306','[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:2318','[getOrderById] 订单数据:', JSON.stringify(orderObj))\r\n return orderObj\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2321','[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:2358','提交售后失败:', 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:2364','提交售后异常:', 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:2404','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:2466','获取售后列表失败:', 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:2479','获取售后列表异常:', e)\r\n const empty: any[] = []\r\n return empty\r\n }\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:2489','[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:2501','[Supabase] getUserBalance error:', walletRes.error)\r\n } else {\r\n __f__('log','at utils/supabaseService.uts:2503','[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:2535','[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:2549','[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:2558','[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:2570','[Supabase] getUserPoints error:', res.error)\r\n } else {\r\n __f__('log','at utils/supabaseService.uts:2572','[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:2610','[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:2636','获取交易记录失败:', 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:2649','获取交易记录异常:', 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:2704','获取红包失败:', 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:2715','获取红包异常:', 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:2738','获取银行卡失败:', 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:2749','获取银行卡异常:', 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:2767','充值失败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:2779','充值异常:', 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:2796','提现失败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:2806','提现异常:', 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:2826','添加银行卡失败:', res.error)\r\n return false\r\n }\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2831','添加银行卡异常:', 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:2850','删除银行卡失败:', res.error)\r\n return false\r\n }\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:2855','删除银行卡异常:', 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:2864',`[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:2877',`[CheckFav] Response: ${JSON.stringify(response)}`)\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:2880',`[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:2899',`[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:2916',`[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:2926',`[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:2930',`[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:2943','取消收藏失败:', 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:2960','添加收藏失败:', 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:2966','切换收藏状态异常:', 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:3069','获取收藏列表异常:', 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:3079','[getFootprints] 用户未登录')\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n __f__('log','at utils/supabaseService.uts:3084','[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:3095','[getFootprints] 足迹查询 error:', response.error)\r\n __f__('log','at utils/supabaseService.uts:3096','[getFootprints] 足迹查询 data:', JSON.stringify(response.data))\r\n\r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3099','[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:3106','[getFootprints] 没有足迹记录')\r\n const empty: any[] = []\r\n return empty\r\n }\r\n\r\n __f__('log','at utils/supabaseService.uts:3111','[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:3243','[addFootprint] 用户未登录')\r\n return false\r\n }\r\n \r\n __f__('log','at utils/supabaseService.uts:3247','[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:3257','[addFootprint] 检查结果 error:', checkRes.error)\r\n __f__('log','at utils/supabaseService.uts:3258','[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:3264','[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:3272','[addFootprint] 更新结果 error:', updateRes.error)\r\n } else {\r\n __f__('log','at utils/supabaseService.uts:3274','[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:3286','[addFootprint] 插入结果 error:', insertRes.error)\r\n }\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3290','[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:3312','获取地址列表失败:', 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:3318','获取地址列表异常:', 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:3329','用户未登录,无法设置默认地址')\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:3348','设置默认地址失败:', 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:3354','设置默认地址异常:', 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:3383','获取优惠券失败:', 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:3457','获取优惠券异常:', 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:3507','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:3519','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:3533','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:3544','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:3550','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:3556','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:3602','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:3610','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:3613','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:3629','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:3663','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:3676','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 {\r\n // 确保 session 有效\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) {\r\n __f__('error','at utils/supabaseService.uts:3687',\"sendMessage failed: user not logged in or session lost\")\r\n return false\r\n }\r\n\r\n try {\r\n // Debug check\r\n // const session = supa.getSession()\r\n // __f__('log','at utils/supabaseService.uts:3694',\"Sending check: UserID\", userId, \"AuthID:\", session.user?.getString('id'))\r\n \r\n const msg = {\r\n sender_id: userId!,\r\n receiver_id: merchantId,\r\n content: content,\r\n msg_type: msgType,\r\n is_read: false,\r\n is_from_user: true\r\n }\r\n \r\n const response = await supa\r\n .from('ml_chat_messages')\r\n .insert(msg)\r\n .execute()\r\n \r\n if (response.error != null) {\r\n __f__('error','at utils/supabaseService.uts:3711','sendMessage error:', response.error)\r\n return false\r\n }\r\n return true\r\n } catch (e) {\r\n __f__('error','at utils/supabaseService.uts:3716','sendMessage exception:', e)\r\n return false\r\n }\r\n }\r\n \r\n // 标记会话已读\r\n async markRead(merchantId: string): Promise {\r\n const userId = this.getCurrentUserId()\r\n if (userId == null) return false\r\n try {\r\n const response = await supa\r\n .from('ml_chat_messages')\r\n .update({ is_read: true })\r\n .eq('sender_id', merchantId)\r\n .eq('receiver_id', userId)\r\n .eq('is_read', false)\r\n .execute() \r\n\r\n if (response.error != null) return false\r\n } catch (e) { return false }\r\n return true\r\n }\r\n}\r\n\r\n// 导出单例实例\r\nexport const supabaseService = new SupabaseService()\r\n\r\n// 默认导出\r\nexport default supabaseService\r\n","\r\n\r\n\t\r\n\t\t\r\n\t\t\r\n\t\t\t