Files
medical-mall/utils/supabaseService.uts
2026-04-13 11:32:31 +08:00

7738 lines
251 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import supa from '@/components/supadb/aksupainstance.uts'
import type { AkReqResponse } from '@/uni_modules/ak-req/index.uts'
// 导出 supa 实例,供 services 层统一使用
export { supa }
import type { OrderOptions } from '@/components/supadb/aksupa.uts'
const OLD_URL = '192.168.1.61:18000'
// const NEW_URL = '119.146.131.237:9126'
// 医疗项目 Supabase 实例地址(与 ak/config.uts 中的 SUPA_URL 保持一致,去掉 http:// 前缀)
const NEW_URL = '119.146.131.237:9127'
function fixImageUrl(url: string | null): string {
if (url == null) return ''
if (url.indexOf(OLD_URL) >= 0) {
return url.replace(OLD_URL, NEW_URL)
}
return url
}
function fixImageUrls(urls: any): string[] {
if (urls == null) return []
if (Array.isArray(urls)) {
const result: string[] = []
const arr = urls as any[]
for (let i = 0; i < arr.length; i++) {
try {
const urlStr = JSON.stringify(arr[i])
if (urlStr != null && urlStr.startsWith('"') && urlStr.endsWith('"')) {
const fixed = fixImageUrl(urlStr.substring(1, urlStr.length - 1))
if (fixed !== '') result.push(fixed)
}
} catch (e) {}
}
return result
}
return []
}
// 使用单例 Supabase 客户端
// const supa = createClient(SUPA_URL, SUPA_KEY)
// 辅助函数:安全获取字符串值
function safeGetString(obj: UTSJSONObject, key: string): string {
try {
const rawVal = obj.get(key)
if (rawVal == null) return ''
const strVal = JSON.stringify(rawVal)
if (strVal == null) return ''
if (strVal.startsWith('"') && strVal.endsWith('"')) {
return strVal.substring(1, strVal.length - 1)
}
return strVal
} catch (e) {
console.error('safeGetString error for key:', key, e)
return ''
}
}
// 辅助函数:安全获取数值
function safeGetNumber(obj: UTSJSONObject, key: string): number {
try {
const rawVal = obj.get(key)
if (rawVal == null) return 0
try {
const numVal = rawVal as number
if (!isNaN(numVal)) return numVal
} catch (e) {}
return 0
} catch (e) {
console.error('safeGetNumber error for key:', key, e)
return 0
}
}
// 辅助函数:安全获取布尔值
function safeGetBoolean(obj: UTSJSONObject, key: string): boolean {
try {
const rawVal = obj.get(key)
if (rawVal == null) return false
try {
const boolVal = rawVal as boolean
return boolVal
} catch (e) {}
return false
} catch (e) {
console.error('safeGetBoolean error for key:', key, e)
return false
}
}
// 辅助函数:安全获取字符串数组
function safeGetStringArray(obj: UTSJSONObject, key: string): string[] {
try {
const rawVal = obj.get(key)
if (rawVal != null && Array.isArray(rawVal)) {
return rawVal as string[]
}
return [] as string[]
} catch (e) {
console.error('safeGetStringArray error for key:', key, e)
return [] as string[]
}
}
// 辅助函数:从原始数据解析商品
function parseProductFromRaw(item: any): Product {
try {
console.log('[parseProductFromRaw] 开始解析商品')
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
console.log('[parseProductFromRaw] JSON转换成功')
const mainImageUrl = fixImageUrl(safeGetString(itemObj, 'main_image_url'))
const imageUrls = fixImageUrls(safeGetStringArray(itemObj, 'image_urls'))
console.log('[parseProductFromRaw] 图片处理完成')
const result: Product = {
id: safeGetString(itemObj, 'id'),
name: safeGetString(itemObj, 'name'),
description: safeGetString(itemObj, 'description'),
base_price: safeGetNumber(itemObj, 'base_price'),
price: safeGetNumber(itemObj, 'base_price'),
original_price: safeGetNumber(itemObj, 'market_price'),
market_price: safeGetNumber(itemObj, 'market_price'),
main_image_url: mainImageUrl,
image_url: mainImageUrl,
images: imageUrls,
category_id: safeGetString(itemObj, 'category_id'),
brand_id: safeGetString(itemObj, 'brand_id'),
merchant_id: safeGetString(itemObj, 'merchant_id'),
total_stock: safeGetNumber(itemObj, 'total_stock'),
stock: safeGetNumber(itemObj, 'total_stock'),
sale_count: safeGetNumber(itemObj, 'sale_count'),
status: safeGetNumber(itemObj, 'status'),
is_featured: safeGetBoolean(itemObj, 'is_featured'),
is_new: safeGetBoolean(itemObj, 'is_new'),
is_hot: safeGetBoolean(itemObj, 'is_hot'),
specification: safeGetString(itemObj, 'specification'),
usage: safeGetString(itemObj, 'usage'),
side_effects: safeGetString(itemObj, 'side_effects'),
precautions: safeGetString(itemObj, 'precautions'),
expiry_date: safeGetString(itemObj, 'expiry_date'),
storage_conditions: safeGetString(itemObj, 'storage_conditions'),
approval_number: safeGetString(itemObj, 'approval_number'),
created_at: safeGetString(itemObj, 'created_at')
}
console.log('[parseProductFromRaw] 商品解析成功:', result.name)
return result
} catch (e) {
console.error('parseProductFromRaw error:', e)
return {
id: '',
name: '',
description: '',
base_price: 0,
price: 0,
original_price: 0,
market_price: 0,
main_image_url: '',
image_url: '',
images: [] as string[],
category_id: '',
brand_id: '',
merchant_id: '',
total_stock: 0,
stock: 0,
sale_count: 0,
status: 0,
is_featured: false,
is_new: false,
is_hot: false,
specification: '',
usage: '',
side_effects: '',
precautions: '',
expiry_date: '',
storage_conditions: '',
approval_number: '',
created_at: ''
} as Product
}
}
// 类型定义
export type Brand = {
id: string
name: string
logo_url: string
description: string
}
export type Category = {
id: string
name: string
icon: string
description: string
color: string
parent_id?: string
level?: number
slug?: string
created_at?: string
}
export type Product = {
id: string
category_id: string
merchant_id: string
name: string
subtitle?: string
description?: string
base_price?: number
market_price?: number
cost_price?: number
main_image_url?: string
image_url?: string
image_urls?: string
video_urls?: string
images?: string[]
sale_count?: number
view_count?: number
total_stock?: number
available_stock?: number
is_hot?: boolean
is_new?: boolean
is_featured?: boolean
status?: number
rating_avg?: number
rating_count?: number
rating?: number
review_count?: number
brand_id?: string
shop_id?: string
tags?: string
attributes?: string
specification?: string
usage?: string
side_effects?: string
precautions?: string
expiry_date?: string
storage_conditions?: string
approval_number?: string
created_at?: string
updated_at?: string
price?: number
original_price?: number
stock?: number
sales?: number
cover?: string
brand_name?: string
category_name?: string
shop_name?: string
merchant_name?: string
}
export type Shop = {
id: string
merchant_id: string
shop_name: string
shop_logo?: string
shop_banner?: string
description?: string
contact_name?: string
contact_phone?: string
rating_avg?: number
total_sales?: number
product_count?: number
total_sales_count?: number
created_at?: string
}
export type CartItem = {
id: string
user_id: string
product_id: string
sku_id?: string
merchant_id?: string
quantity: number
selected: boolean
product_name?: string
product_image?: string
product_price?: number
product_specification?: string
shop_id?: string
shop_name?: string
created_at?: string
updated_at?: string
}
export type UserAddress = {
id: string
user_id: string
recipient_name: string
phone: string
province: string
city: string
district: string
detail_address: string
postal_code?: string
is_default: boolean
label?: string
created_at?: string
updated_at?: string
}
export type UserCoupon = {
id: string
user_id: string
template_id: string
coupon_code: string
status: number // 1: unused, 2: used, 3: expired
received_at: string
expire_at: string
used_at?: string
// join fields from template or view
template_name?: string
amount?: number
min_spend?: number
name?: string
title?: string
}
export type ChatRoom = {
id: string
user_id: string
merchant_id: string
shop_name: string
shop_logo?: string
last_message?: string
last_message_at?: string
unread_count: number
is_top: boolean
created_at?: string
updated_at?: string
}
export type Notification = {
id: string
user_id: string
type: string
title: string
content: string
icon_url?: string
link_url?: string
is_read: boolean
extra_data?: string
created_at?: string
}
export type ChatMessage = {
id: string
session_id?: string
sender_id?: string
receiver_id?: string
content: string
msg_type: string
is_read: boolean
is_from_user: boolean
extra_data?: string
created_at?: string
}
export type PaginatedResponse<T> = {
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')
}
// 移除基于 storage 的后备获取,严格只认当前 Tab 独立 session
return null
//cyh
// 2. 尝试从 Storage 恢复 Session (针对 App 重启后内存丢失的情况)
// 注意:这里无法异步调用 hydrate所以只能依赖 UI 层或 init 层的预加载
// 但我们可以返回本地存储 ID 作为 fallback前提是 Token 有效
// 后备:尝试从本地存储获取
// const userId = uni.getStorageSync('user_id')
// return userId != null ? userId as string : null
} catch (e) {
console.error('获取用户ID失败:', e)
return null
}
}
// 确保会话有效 (异步)
async ensureSession(): Promise<string | null> {
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<Category[]> {
try {
const response = await supa
.from('ml_categories')
.select('*')
.order('name', { ascending: true })
.execute()
if (response.error != null) {
console.error('获取分类失败:', response.error)
return []
}
const rawData = response.data
if (rawData == null) {
return []
}
const categories: Category[] = []
const rawList = rawData as any[]
for (let i: number = 0; i < rawList.length; i++) {
const item = rawList[i]
const catObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
const idVal = catObj.get('id')
const nameVal = catObj.get('name')
const iconVal = catObj.get('icon')
const iconUrlVal = catObj.get('icon_url')
const descVal = catObj.get('description')
const colorVal = catObj.get('color')
const parentIdVal = catObj.get('parent_id')
const levelVal = catObj.get('level')
const cat: Category = {
id: (typeof idVal == 'string') ? (idVal as string) : '',
name: (typeof nameVal == 'string') ? (nameVal as string) : '',
icon: (typeof iconVal == 'string') ? (iconVal as string) : ((typeof iconUrlVal == 'string') ? (iconUrlVal as string) : ''),
description: (typeof descVal == 'string') ? (descVal as string) : '',
color: (typeof colorVal == 'string') ? (colorVal as string) : '#4CAF50',
parent_id: (typeof parentIdVal == 'string') ? (parentIdVal as string) : null,
level: (typeof levelVal == 'number') ? (levelVal as number) : 0
} as Category
categories.push(cat)
}
return categories
} catch (error) {
console.error('获取分类异常:', error)
return []
}
}
// 根据ID获取单个分类
async getCategoryById(categoryId: string): Promise<Category | null> {
try {
const response = await supa
.from('ml_categories')
.select('*')
.eq('id', categoryId)
.limit(1)
.execute()
if (response.error != null) {
console.error('获取分类失败:', response.error)
return null
}
const rawData = response.data
if (rawData == null) {
return null
}
// 处理数组返回值
const rawList = rawData as any[]
if (rawList.length == 0) {
return null
}
const item = rawList[0]
const catObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
const idVal = catObj.get('id')
const nameVal = catObj.get('name')
const iconVal = catObj.get('icon')
const iconUrlVal = catObj.get('icon_url')
const descVal = catObj.get('description')
const colorVal = catObj.get('color')
const parentIdVal = catObj.get('parent_id')
const levelVal = catObj.get('level')
const cat: Category = {
id: (typeof idVal == 'string') ? (idVal as string) : '',
name: (typeof nameVal == 'string') ? (nameVal as string) : '',
icon: (typeof iconVal == 'string') ? (iconVal as string) : ((typeof iconUrlVal == 'string') ? (iconUrlVal as string) : ''),
description: (typeof descVal == 'string') ? (descVal as string) : '',
color: (typeof colorVal == 'string') ? (colorVal as string) : '#4CAF50',
parent_id: (typeof parentIdVal == 'string') ? (parentIdVal as string) : null,
level: (typeof levelVal == 'number') ? (levelVal as number) : 0
} as Category
return cat
} catch (error) {
console.error('获取分类异常:', error)
return null
}
}
// 获取一级分类
async getParentCategories(): Promise<Category[]> {
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<UTSJSONObject>
for (let i = 0; i < rawList.length; i++) {
const item = rawList[i]
const icon = this.getCategoryIcon(item)
// 安全获取属性
const idVal = item['id']
const nameVal = item['name']
const descVal = item['description']
const colorVal = item['color']
const slugVal = item['slug']
const cat: Category = {
id: (typeof idVal == 'string') ? (idVal as string) : '',
name: (typeof nameVal == 'string') ? (nameVal as string) : '',
icon: icon,
description: (typeof descVal == 'string') ? (descVal as string) : '',
color: (typeof colorVal == 'string') ? (colorVal as string) : '#ff5000',
level: 1,
slug: (typeof slugVal == 'string') ? (slugVal as string) : ''
}
categories.push(cat)
}
return categories
} catch (error) {
console.error('获取一级分类异常:', error)
return []
}
}
// 获取子分类
async getSubCategories(parentId: string): Promise<Category[]> {
try {
console.log('[getSubCategories] 开始获取子分类, parentId:', parentId)
const response = await supa
.from('ml_categories')
.select('*')
.order('sort_order', { ascending: true })
.execute()
console.log('[getSubCategories] 查询完成')
if (response.error != null) {
console.error('获取子分类失败:', response.error)
return []
}
const rawData = response.data
if (rawData == null) {
console.log('[getSubCategories] 数据为空')
return []
}
const categories: Category[] = []
const rawList = rawData as any[]
console.log('[getSubCategories] 原始数据条数:', rawList.length)
for (let i = 0; i < rawList.length; i++) {
const item = rawList[i]
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
// 手动过滤 parent_id
const itemParentId = safeGetString(itemObj, 'parent_id')
const isMatch = (itemParentId.length > 0 && itemParentId == parentId)
if (!isMatch) {
continue
}
const icon = this.getCategoryIcon(itemObj)
const cat: Category = {
id: safeGetString(itemObj, 'id'),
name: safeGetString(itemObj, 'name'),
icon: icon,
description: safeGetString(itemObj, 'description'),
color: safeGetString(itemObj, 'color').length > 0 ? safeGetString(itemObj, 'color') : '#ff5000',
level: 2,
parent_id: safeGetString(itemObj, 'parent_id'),
slug: safeGetString(itemObj, 'slug')
}
categories.push(cat)
}
console.log('[getSubCategories] 返回分类数量:', categories.length)
return categories
} catch (error) {
console.error('获取子分类异常:', error)
return []
}
}
// 获取分类图标的辅助方法
getCategoryIcon(item: UTSJSONObject): string {
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
const icon = safeGetString(itemObj, 'icon')
if (icon.length > 0) {
return icon
}
const iconUrl = safeGetString(itemObj, 'icon_url')
if (iconUrl.length > 0) {
return iconUrl
}
const name = safeGetString(itemObj, 'name')
if (name.includes('数码') || name.includes('电器') || name.includes('手机')) return '📱'
if (name.includes('服装') || name.includes('衣服') || name.includes('鞋')) return '👕'
if (name.includes('食品') || name.includes('水果') || name.includes('零食')) return '🍎'
if (name.includes('美妆') || name.includes('护肤') || name.includes('化妆')) return '💄'
if (name.includes('母婴') || name.includes('婴儿') || name.includes('儿童')) return '👶'
if (name.includes('家居') || name.includes('家具') || name.includes('装饰')) return '🏠'
if (name.includes('图书') || name.includes('文具')) return '📚'
if (name.includes('运动') || name.includes('户外') || name.includes('健身')) return '⚽'
if (name.includes('医药') || name.includes('保健') || name.includes('健康')) return '💊'
return '📦'
}
// 获取所有品牌
async getBrands(): Promise<Brand[]> {
try {
console.log('[getBrands] 开始获取品牌数据...')
const response = await supa
.from('ml_brands')
.select('id, name, logo_url, description, is_active')
.order('name', { ascending: true })
.execute()
if (response.error != null) {
console.error('获取品牌失败:', response.error)
return []
}
const rawData = response.data
if (rawData == null) {
console.log('[getBrands] 数据为空')
return []
}
const brands: Brand[] = []
const rawList = rawData as any[]
console.log('[getBrands] 数据条数:', rawList.length)
for (let i: number = 0; i < rawList.length; i++) {
const item = rawList[i]
const brandObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
const idVal = brandObj.get('id')
const nameVal = brandObj.get('name')
const logoVal = brandObj.get('logo_url')
const descVal = brandObj.get('description')
const isActiveVal = brandObj.get('is_active')
let isActiveBool: boolean = true
if (isActiveVal != null) {
if (typeof isActiveVal == 'boolean') {
isActiveBool = isActiveVal as boolean
} else if (typeof isActiveVal == 'number') {
isActiveBool = (isActiveVal as number) === 1
}
}
if (!isActiveBool) {
continue
}
const brand: Brand = {
id: (typeof idVal == 'string') ? (idVal as string) : '',
name: (typeof nameVal == 'string') ? (nameVal as string) : '',
logo_url: (typeof logoVal == 'string') ? (logoVal as string) : '',
description: (typeof descVal == 'string') ? (descVal as string) : ''
} as Brand
brands.push(brand)
}
console.log('[getBrands] 返回品牌数量:', brands.length)
return brands
} catch (error) {
console.error('获取品牌异常:', error)
return []
}
}
// 获取指定分类的商品
async getProductsByCategory(
categoryId: string,
page: number = 1,
limit: number = 20
): Promise<PaginatedResponse<Product>> {
try {
console.log('[getProductsByCategory] 开始查询分类ID:', categoryId, '页码:', page)
// 在数据库层面进行分类过滤
const response = await supa
.from('ml_products_detail_view')
.select('*', { count: 'exact' })
.eq('category_id', categoryId)
.eq('status', '1') // 使用字符串 '1' 而不是整数 1
.order('sale_count', { ascending: false })
.page(page)
.limit(limit)
.execute()
console.log('[getProductsByCategory] 查询完成total:', response.total)
if (response.error != null) {
console.error('获取商品失败:', response.error)
return {
data: [] as Product[],
total: 0,
page,
limit,
hasmore: false
}
}
const rawData = response.data
if (rawData == null) {
return {
data: [] as Product[],
total: 0,
page,
limit,
hasmore: false
}
}
const products: Product[] = []
const rawList = rawData as any[]
console.log('[getProductsByCategory] 返回数据条数:', rawList.length)
for (let i: number = 0; i < rawList.length; i++) {
const item = rawList[i]
products.push(parseProductFromRaw(item))
}
return {
data: products,
total: response.total ?? products.length,
page,
limit,
hasmore: response.hasmore ?? false
}
} catch (error) {
console.error('获取商品异常:', error)
return {
data: [] as Product[],
total: 0,
page,
limit,
hasmore: false
}
}
}
// 根据商品ID获取SKU列表
async getProductSkus(productId: string): Promise<ProductSku[]> {
try {
console.log('[getProductSkus] 开始获取SKU商品ID:', productId)
const response = await supa
.from('ml_product_skus')
.select('*')
.eq('product_id', productId)
.eq('status', '1')
.execute()
if (response.error != null) {
console.error('获取商品SKU失败:', response.error)
return []
}
const rawData = response.data
if (rawData == null) return []
const skus: ProductSku[] = []
const rawList = rawData as any[]
console.log('[getProductSkus] 获取到SKU数量:', rawList.length)
for (let i = 0; i < rawList.length; i++) {
const item = rawList[i]
const skuObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
const rawId = skuObj.get('id')
const rawSkuCode = skuObj.get('sku_code')
const rawProdId = skuObj.get('product_id')
const rawPrice = skuObj.get('price')
const rawStock = skuObj.get('stock')
const rawImageUrl = skuObj.get('image_url')
const rawSpecs = skuObj.get('specifications')
let specsStr = ''
if (rawSpecs != null) {
try {
if (typeof rawSpecs == 'string') {
specsStr = rawSpecs as string
} else {
specsStr = JSON.stringify(rawSpecs)
}
} catch(e) {
console.error('解析SKU规格失败', e)
}
}
const sku: ProductSku = {
id: (typeof rawId == 'string') ? (rawId as string) : '',
product_id: (typeof rawProdId == 'string') ? (rawProdId as string) : '',
sku_code: (typeof rawSkuCode == 'string') ? (rawSkuCode as string) : '',
specifications: specsStr,
price: (typeof rawPrice == 'number') ? (rawPrice as number) : 0,
stock: (typeof rawStock == 'number') ? (rawStock as number) : 0,
image_url: (typeof rawImageUrl == 'string') ? (rawImageUrl as string) : '',
status: 1
}
skus.push(sku)
}
return skus
} catch (error) {
console.error('获取商品SKU异常:', error)
return []
}
}
// 搜索商品
async searchProducts(
keyword: string,
page: number = 1,
limit: number = 20,
sortBy: string = 'sales',
ascending: boolean = false
): Promise<PaginatedResponse<Product>> {
try {
const keywordLower = keyword.toLowerCase()
const encodedKeyword = encodeURIComponent(keywordLower)
const orString = `name.ilike.%${encodedKeyword}%,description.ilike.%${encodedKeyword}%,subtitle.ilike.%${encodedKeyword}%,brand_name.ilike.%${encodedKeyword}%`
console.log('[searchProducts] 搜索关键词:', keyword, '编码后:', encodedKeyword)
console.log('[searchProducts] or条件:', orString)
let query = supa
.from('ml_products_detail_view')
.select('*', { count: 'exact' })
.eq('status', 1)
.or(orString)
if (sortBy === 'price') {
query = query.order('base_price', { ascending })
} else if (sortBy === 'sales' || sortBy === 'sale_count') {
query = query.order('sale_count', { ascending: false })
} else {
query = query.order('sale_count', { ascending: false })
}
const response = await query
.page(page)
.limit(limit)
.execute()
let dataLength = 0
try {
const respData = response.data
if (respData != null && Array.isArray(respData)) {
dataLength = (respData as any[]).length
}
} catch (e) {
console.error('[searchProducts] 获取数据长度失败:', e)
}
let statusNum = 0
try {
statusNum = response.status as number
} catch (e) {}
console.log('[searchProducts] 响应状态:', statusNum, '数据条数:', dataLength)
let hasError = false
try {
hasError = response.error != null
} catch (e) {}
if (hasError) {
console.error('[searchProducts] 搜索商品失败:', response.error)
return {
data: [] as Product[],
total: 0,
page,
limit,
hasmore: false
}
}
const rawData = response.data
console.log('[searchProducts] rawData:', rawData != null ? 'not null' : 'null')
if (rawData == null) {
return {
data: [] as Product[],
total: 0,
page,
limit,
hasmore: false
}
}
const products: Product[] = []
let rawList: any[] = []
try {
rawList = rawData as any[]
console.log('[searchProducts] rawList长度:', rawList.length)
} catch (e) {
console.error('[searchProducts] 转换rawList失败:', e)
return {
data: [] as Product[],
total: 0,
page,
limit,
hasmore: false
}
}
for (let i = 0; i < rawList.length; i++) {
const item = rawList[i]
console.log('[searchProducts] 处理第', i + 1, '个商品')
products.push(parseProductFromRaw(item))
}
let totalNum = 0
try {
totalNum = response.total as number
} catch (e) {}
let hasmoreVal = false
try {
hasmoreVal = response.hasmore as boolean
} catch (e) {}
return {
data: products,
total: totalNum > 0 ? totalNum : products.length,
page,
limit,
hasmore: hasmoreVal
}
} catch (error) {
console.error('搜索商品异常:', error)
return {
data: [] as Product[],
total: 0,
page,
limit,
hasmore: false
}
}
}
// 搜索店铺
async searchShops(
keyword: string,
page: number = 1,
limit: number = 20
): Promise<PaginatedResponse<Shop>> {
try {
const encodedKeyword = encodeURIComponent(keyword)
const response = await supa
.from('ml_shops')
.select('*', { count: 'exact' })
.ilike('shop_name', `%${encodedKeyword}%`)
.order('product_count', { ascending: false })
.page(page)
.limit(limit)
.execute()
if (response.error != null) {
console.error('搜索店铺失败:', response.error)
return { data: [] as Shop[], total: 0, page, limit, hasmore: false }
}
const rawData = response.data
if (rawData == null) {
return { data: [] as Shop[], total: 0, page, limit, hasmore: false }
}
const shops: Shop[] = []
const dataList = rawData as any[]
for (let i = 0; i < dataList.length; i++) {
const item = dataList[i]
const shopObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
// 手动过滤 status
const rawStatus = shopObj.get('status')
let statusNum: number = 0
if (typeof rawStatus == 'number') {
statusNum = rawStatus as number
}
if (statusNum !== 1) continue
// 手动创建 Shop 对象,避免安卓端类型转换错误
const shop: Shop = {
id: shopObj.getString('id') ?? '',
merchant_id: shopObj.getString('merchant_id') ?? '',
shop_name: shopObj.getString('shop_name') ?? '',
shop_logo: shopObj.getString('shop_logo'),
shop_banner: shopObj.getString('shop_banner'),
description: shopObj.getString('description'),
contact_name: shopObj.getString('contact_name'),
contact_phone: shopObj.getString('contact_phone'),
rating_avg: shopObj.getNumber('rating_avg'),
total_sales: shopObj.getNumber('total_sales'),
product_count: shopObj.getNumber('product_count'),
total_sales_count: shopObj.getNumber('total_sales_count'),
created_at: shopObj.getString('created_at')
}
shops.push(shop)
}
return {
data: shops,
total: response.total ?? shops.length,
page,
limit,
hasmore: response.hasmore ?? false
}
} catch (error) {
console.error('搜索店铺异常:', error)
return { data: [] as Shop[], total: 0, page, limit, hasmore: false }
}
}
// 获取单个商品详情
async getProductById(productId: string): Promise<Product | null> {
try {
console.log('[getProductById] 开始获取商品详情ID:', productId)
const response = await supa
.from('ml_products_detail_view')
.select('*')
.eq('id', productId)
.limit(1)
.execute()
if (response.error != null) {
console.error('获取商品详情失败:', response.error)
return null
}
const rawData = response.data
if (rawData == null) {
console.log('[getProductById] 数据为空')
return null
}
const rawList = rawData as any[]
if (rawList.length == 0) {
console.log('[getProductById] 未找到商品')
return null
}
const item = rawList[0]
const product = parseProductFromRaw(item)
console.log('[getProductById] 成功获取商品:', product.name)
return product
} catch (error) {
console.error('获取商品详情异常:', error)
return null
}
}
// --- 关注店铺相关 ---
// 检查是否已关注店铺
async isShopFollowed(shopId: string, userId: string): Promise<boolean> {
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<boolean> {
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<boolean> {
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<any[]> {
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<Shop | null> {
try {
console.log('[getShopByMerchantId] 开始获取店铺信息merchantId:', merchantId)
// 1. Try querying by merchant_id
let response = await supa
.from('ml_shops')
.select('*')
.eq('merchant_id', merchantId)
.limit(1)
.execute()
if (response.error == null && response.data != null && (response.data as any[]).length > 0) {
const shopData = (response.data as any[])[0]
const shop = this.parseShopFromRaw(shopData)
console.log('[getShopByMerchantId] 通过 merchant_id 找到店铺:', shop.shop_name)
return shop
}
// 2. Fallback: Try querying by id (Maybe the passed ID is the Shop ID?)
console.log('getShopByMerchantId: merchant_id not found, trying id...', merchantId)
response = await supa
.from('ml_shops')
.select('*')
.eq('id', merchantId)
.limit(1)
.execute()
if (response.error == null && response.data != null && (response.data as any[]).length > 0) {
console.log('Found shop by ID instead of MerchantID')
const shopData = (response.data as any[])[0]
const shop = this.parseShopFromRaw(shopData)
return shop
}
if (response.error != null) {
console.error('获取店铺信息失败:', response.error)
}
return null
} catch (error) {
console.error('获取店铺信息异常:', error)
return null
}
}
// 解析店铺数据
parseShopFromRaw(item: any): Shop {
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
const getSafeString = (key: string): string => {
const val = itemObj.get(key)
if (val == null) return ''
if (typeof val == 'string') return val
return ''
}
const getSafeNumber = (key: string): number => {
const val = itemObj.get(key)
if (val == null) return 0
if (typeof val == 'number') return val
return 0
}
return {
id: getSafeString('id'),
merchant_id: getSafeString('merchant_id'),
shop_name: getSafeString('shop_name'),
shop_logo: getSafeString('shop_logo'),
shop_banner: getSafeString('shop_banner'),
description: getSafeString('description'),
contact_name: getSafeString('contact_name'),
contact_phone: getSafeString('contact_phone'),
rating_avg: getSafeNumber('rating_avg'),
total_sales: getSafeNumber('total_sales'),
product_count: getSafeNumber('product_count'),
total_sales_count: getSafeNumber('total_sales_count'),
created_at: getSafeString('created_at')
} as Shop
}
// 根据商户ID获取商品列表
async getProductsByMerchantId(merchantId: string, page: number = 1, limit: number = 20): Promise<PaginatedResponse<Product>> {
try {
console.log('getProductsByMerchantId querying for:', merchantId)
// 1. Try fetching from view strictly first
let query = supa
.from('ml_products_detail_view')
.select('*', { count: 'exact' })
.eq('merchant_id', merchantId)
// .eq('status', 1) // Temporarily disabled status check to see if products exist at all
.order('created_at', { ascending: false })
.page(page)
.limit(limit)
const response = await query.execute()
// 检查视图结果:如果有错误 OR 数据为空,都尝试去查原始表
if (response.error != null || (response.data != null && (response.data as any[]).length === 0)) {
if (response.error != null) {
console.error('获取商户商品失败 (View):', response.error)
} else {
console.log('View returned 0 products, trying raw table fallback...')
}
// Fallback: Try raw table
console.log('Falling back to raw ml_products table...')
const query2 = supa
.from('ml_products')
.select('*', { count: 'exact' })
.eq('merchant_id', merchantId)
// .eq('status', 1) // Also disabled here
.order('created_at', { ascending: false })
.page(page)
.limit(limit)
const res2 = await query2.execute()
if (res2.error != null) {
console.error('获取商户商品失败 (Raw):', res2.error)
return {data:[] as Product[], total:0, page, limit, hasmore:false}
}
console.log(`Fallback (Raw) found: ${(res2.data as any[]).length} products`)
const mappedData: Product[] = []
const rawData = res2.data as any[]
for(let i = 0; i < rawData.length; i++) {
const item = JSON.parse(JSON.stringify(rawData[i])) as UTSJSONObject
const images: string[] = []
const mainImageUrl = fixImageUrl(item.getString('main_image_url'))
if (mainImageUrl != null && mainImageUrl !== '') {
images.push(mainImageUrl)
}
const imageUrlsRaw = item.get('image_urls')
if (imageUrlsRaw != null) {
try {
if (Array.isArray(imageUrlsRaw)) {
const arr = imageUrlsRaw as string[]
if (arr.length > 0 && images.length === 0) {
for (let j = 0; j < arr.length; j++) {
const fixedUrl = fixImageUrl(arr[j])
if (fixedUrl !== '') images.push(fixedUrl)
}
}
} else {
const rawUrlStr = imageUrlsRaw as string
if (rawUrlStr.startsWith('[')) {
const parsed = JSON.parse(rawUrlStr)
if (Array.isArray(parsed) && images.length === 0) {
for (let j = 0; j < parsed.length; j++) {
const fixedUrl = fixImageUrl(parsed[j] as string)
if (fixedUrl !== '') images.push(fixedUrl)
}
}
} else {
const fixedUrl = fixImageUrl(rawUrlStr)
if (fixedUrl !== '' && images.indexOf(fixedUrl) === -1) images.push(fixedUrl)
}
}
} catch(e) {
console.error('解析图片数组失败:', e)
}
}
if (images.length === 0) {
images.push('/static/default-product.png')
}
let safePrice = item.getNumber('base_price')
if (safePrice == null) {
const p = item.getNumber('price')
safePrice = p != null ? p : 0
}
let safeOriginalPrice = item.getNumber('market_price')
if (safeOriginalPrice == null) {
const op = item.getNumber('original_price')
safeOriginalPrice = op != null ? op : safePrice
}
let safeStock = item.getNumber('total_stock')
if (safeStock == null) {
let as_ = item.getNumber('available_stock')
if (as_ == null) {
const s = item.getNumber('stock')
safeStock = s != null ? s : 0
} else {
safeStock = as_
}
}
let safeSales = item.getNumber('sale_count')
if (safeSales == null) {
const s = item.getNumber('sales')
safeSales = s != null ? s : 0
}
const product: Product = {
id: item.getString('id') ?? '',
category_id: item.getString('category_id') ?? '',
merchant_id: item.getString('merchant_id') ?? '',
name: item.getString('name') ?? '',
description: item.getString('description') ?? '',
images: images,
price: safePrice,
original_price: safeOriginalPrice,
stock: safeStock,
sales: safeSales,
status: item.getNumber('status') ?? 1,
created_at: item.getString('created_at') ?? '',
base_price: safePrice,
market_price: safeOriginalPrice,
main_image_url: images.length > 0 ? images[0] : '',
sale_count: safeSales,
total_stock: safeStock
} as Product
mappedData.push(product)
}
return {
data: mappedData,
total: res2.total ?? 0,
page,
limit,
hasmore: res2.hasmore ?? false
}
}
console.log(`Merchant products found: ${(response.data as any[]).length}`)
const viewData = response.data as any[]
const parsedProducts: Product[] = []
for (let i = 0; i < viewData.length; i++) {
parsedProducts.push(parseProductFromRaw(viewData[i]))
}
return {
data: parsedProducts,
total: response.total ?? 0,
page,
limit,
hasmore: response.hasmore ?? false
}
} catch (error) {
console.error('获取商户商品异常:', error)
return {
data: [] as Product[],
total: 0,
page,
limit,
hasmore: false
}
}
}
// 根据店铺ID获取商品列表新增
async getProductsByShopId(shopId: string, page: number = 1, limit: number = 20): Promise<PaginatedResponse<Product>> {
try {
console.log('getProductsByShopId querying for:', shopId)
// 1. Try fetching from view with shop_id
let query = supa
.from('ml_products_detail_view')
.select('*', { count: 'exact' })
.eq('shop_id', shopId)
.order('created_at', { ascending: false })
.page(page)
.limit(limit)
const response = await query.execute()
// 检查视图结果:如果有错误 OR 数据为空,都尝试去查原始表
if (response.error != null || (response.data != null && (response.data as any[]).length === 0)) {
if (response.error != null) {
console.error('获取店铺商品失败 (View):', response.error)
} else {
console.log('View returned 0 products, trying raw table fallback...')
}
// Fallback: Try raw table with shop_id
console.log('Falling back to raw ml_products table with shop_id...')
const query2 = supa
.from('ml_products')
.select('*', { count: 'exact' })
.eq('shop_id', shopId)
.order('created_at', { ascending: false })
.page(page)
.limit(limit)
const res2 = await query2.execute()
if (res2.error != null) {
console.error('获取店铺商品失败 (Raw):', res2.error)
return {data:[] as Product[], total:0, page, limit, hasmore:false}
}
console.log(`Fallback (Raw) found: ${(res2.data as any[]).length} products`)
const mappedData: Product[] = []
const rawData = res2.data as any[]
for(let i = 0; i < rawData.length; i++) {
const item = JSON.parse(JSON.stringify(rawData[i])) as UTSJSONObject
const images: string[] = []
const mainImageUrl = fixImageUrl(item.getString('main_image_url'))
if (mainImageUrl != null && mainImageUrl !== '') {
images.push(mainImageUrl)
}
const imageUrlsRaw = item.get('image_urls')
if (imageUrlsRaw != null) {
try {
if (Array.isArray(imageUrlsRaw)) {
const arr = imageUrlsRaw as string[]
if (arr.length > 0 && images.length === 0) {
for (let j = 0; j < arr.length; j++) {
const fixedUrl = fixImageUrl(arr[j])
if (fixedUrl !== '') images.push(fixedUrl)
}
}
} else {
const rawUrlStr = imageUrlsRaw as string
if (rawUrlStr.startsWith('[')) {
const parsed = JSON.parse(rawUrlStr)
if (Array.isArray(parsed) && images.length === 0) {
for (let j = 0; j < parsed.length; j++) {
const fixedUrl = fixImageUrl(parsed[j] as string)
if (fixedUrl !== '') images.push(fixedUrl)
}
}
} else {
const fixedUrl = fixImageUrl(rawUrlStr)
if (fixedUrl !== '' && images.indexOf(fixedUrl) === -1) images.push(fixedUrl)
}
}
} catch(e) {
console.error('解析图片数组失败:', e)
}
}
if (images.length === 0) {
images.push('/static/default-product.png')
}
let safePrice = item.getNumber('base_price')
if (safePrice == null) {
const p = item.getNumber('price')
safePrice = p != null ? p : 0
}
let safeOriginalPrice = item.getNumber('market_price')
if (safeOriginalPrice == null) {
const op = item.getNumber('original_price')
safeOriginalPrice = op != null ? op : safePrice
}
let safeStock = item.getNumber('total_stock')
if (safeStock == null) {
let as_ = item.getNumber('available_stock')
if (as_ == null) {
const s = item.getNumber('stock')
safeStock = s != null ? s : 0
} else {
safeStock = as_
}
}
let safeSales = item.getNumber('sale_count')
if (safeSales == null) {
const s = item.getNumber('sales')
safeSales = s != null ? s : 0
}
const product: Product = {
id: item.getString('id') ?? '',
category_id: item.getString('category_id') ?? '',
merchant_id: item.getString('merchant_id') ?? '',
name: item.getString('name') ?? '',
description: item.getString('description') ?? '',
images: images,
price: safePrice,
original_price: safeOriginalPrice,
stock: safeStock,
sales: safeSales,
status: item.getNumber('status') ?? 1,
created_at: item.getString('created_at') ?? '',
base_price: safePrice,
market_price: safeOriginalPrice,
main_image_url: images.length > 0 ? images[0] : '',
sale_count: safeSales,
total_stock: safeStock
} as Product
mappedData.push(product)
}
return {
data: mappedData,
total: res2.total ?? 0,
page,
limit,
hasmore: res2.hasmore ?? false
}
}
console.log(`Shop products found: ${(response.data as any[]).length}`)
const viewData = response.data as any[]
const parsedProducts: Product[] = []
for (let i = 0; i < viewData.length; i++) {
parsedProducts.push(parseProductFromRaw(viewData[i]))
}
return {
data: parsedProducts,
total: response.total ?? 0,
page,
limit,
hasmore: response.hasmore ?? false
}
} catch (error) {
console.error('获取店铺商品异常:', error)
return {
data: [] as Product[],
total: 0,
page,
limit,
hasmore: false
}
}
}
// 获取热销商品(按销量排序)
async getHotProducts(limit: number = 10): Promise<Product[]> {
try {
console.log('[getHotProducts] 开始获取热销商品...')
// 在数据库层面过滤 status获取更多数据以便手动过滤 is_hot
const response = await supa
.from('ml_products_detail_view')
.select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')
.eq('status', '1') // 使用字符串 '1'
.order('sale_count', { ascending: false })
.limit(limit * 5) // 获取更多数据以便过滤
.execute()
if (response.error != null) {
console.error('获取热销商品失败:', response.error)
return []
}
const rawData = response.data
console.log('[getHotProducts] 原始数据条数:', rawData != null ? (rawData as any[]).length : 0)
if (rawData == null) {
console.log('[getHotProducts] 数据为空')
return []
}
const products: Product[] = []
const rawList = rawData as any[]
for (let i: number = 0; i < rawList.length; i++) {
const item = rawList[i]
const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
// 手动过滤 is_hot
const rawIsHot = prodObj.get('is_hot')
let isHotBool: boolean = false
if (typeof rawIsHot == 'boolean') {
isHotBool = rawIsHot as boolean
} else if (typeof rawIsHot == 'number') {
isHotBool = (rawIsHot as number) == 1
}
if (!isHotBool) continue
products.push(parseProductFromRaw(item))
// 达到目标数量就停止
if (products.length >= limit) break
}
console.log('[getHotProducts] 最终返回商品数:', products.length)
return products
} catch (error) {
console.error('获取热销商品异常:', error)
return []
}
}
// 获取按销量排序的商品(所有商品,不限制 is_hot
async getProductsBySales(limit: number = 10): Promise<Product[]> {
try {
console.log('[getProductsBySales] 开始获取销量排序商品...')
const response = await supa
.from('ml_products_detail_view')
.select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')
.eq('status', '1')
.order('sale_count', { ascending: false })
.limit(limit)
.execute()
if (response.error != null) {
console.error('获取销量排序商品失败:', response.error)
return []
}
const rawData = response.data
if (rawData == null) {
return []
}
const products: Product[] = []
const rawList = rawData as any[]
for (let i: number = 0; i < rawList.length; i++) {
const item = rawList[i]
products.push(parseProductFromRaw(item))
}
console.log('[getProductsBySales] 返回商品数:', products.length)
return products
} catch (error) {
console.error('获取销量排序商品异常:', error)
return []
}
}
// 获取按价格排序的商品(升序:从低到高)
async getProductsByPrice(limit: number = 10, ascending: boolean = true): Promise<Product[]> {
try {
const response = await supa
.from('ml_products_detail_view')
.select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')
.eq('status', '1') // 在数据库层面过滤
.order('base_price', { ascending })
.limit(limit)
.execute()
if (response.error != null) {
console.error('获取价格排序商品失败:', response.error)
return []
}
const rawData = response.data
if (rawData == null) {
return []
}
const products: Product[] = []
const rawList = rawData as any[]
for (let i: number = 0; i < rawList.length; i++) {
const item = rawList[i]
products.push(parseProductFromRaw(item))
}
return products
} catch (error) {
console.error('获取价格排序商品异常:', error)
return []
}
}
// 获取新品(按创建时间排序,最新的在前)
async getProductsByNewest(limit: number = 10): Promise<Product[]> {
try {
console.log('[getProductsByNewest] 开始获取新品...')
const response = await supa
.from('ml_products_detail_view')
.select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')
.eq('status', '1')
.order('created_at', { ascending: false })
.limit(limit * 5)
.execute()
if (response.error != null) {
console.error('获取新品失败:', response.error)
return []
}
const rawData = response.data
if (rawData == null) {
return []
}
const products: Product[] = []
const rawList = rawData as any[]
for (let i: number = 0; i < rawList.length; i++) {
const item = rawList[i]
const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
// 手动过滤 is_new
const rawIsNew = prodObj.get('is_new')
let isNewBool: boolean = false
if (typeof rawIsNew == 'boolean') {
isNewBool = rawIsNew as boolean
} else if (typeof rawIsNew == 'number') {
isNewBool = (rawIsNew as number) == 1
}
if (!isNewBool) continue
products.push(parseProductFromRaw(item))
if (products.length >= limit) break
}
// 如果 is_new 商品不足,补充普通商品
if (products.length < limit) {
console.log('[getProductsByNewest] is_new商品不足补充普通商品')
const addedIds = new Set<string>()
for (let i = 0; i < products.length; i++) {
addedIds.add(products[i].id)
}
for (let i: number = 0; i < rawList.length; i++) {
const item = rawList[i]
const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
const rawId = prodObj.get('id')
const itemId = (typeof rawId == 'string') ? (rawId as string) : ''
if (!addedIds.has(itemId)) {
products.push(parseProductFromRaw(item))
addedIds.add(itemId)
if (products.length >= limit) break
}
}
}
console.log('[getProductsByNewest] 返回商品数:', products.length)
return products
} catch (error) {
console.error('获取新品异常:', error)
return []
}
}
// 获取推荐商品is_featured=true
async getRecommendedProducts(limit: number = 10): Promise<Product[]> {
try {
console.log('[getRecommendedProducts] 开始获取推荐商品...')
const response = await supa
.from('ml_products_detail_view')
.select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')
.eq('status', '1') // 在数据库层面过滤
.order('sale_count', { ascending: false })
.limit(limit * 5) // 获取更多数据以便过滤 is_featured
.execute()
console.log('[getRecommendedProducts] 查询完成')
if (response.error != null) {
console.error('获取推荐商品失败:', response.error)
return []
}
const rawData = response.data
if (rawData == null) {
console.log('[getRecommendedProducts] 数据为空')
return []
}
const products: Product[] = []
const rawList = rawData as any[]
console.log('[getRecommendedProducts] 数据条数:', rawList.length)
for (let i: number = 0; i < rawList.length; i++) {
const item = rawList[i]
const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
const rawFeatured = prodObj.get('is_featured')
let isFeaturedBool: boolean = false
if (typeof rawFeatured == 'boolean') {
isFeaturedBool = rawFeatured as boolean
} else if (typeof rawFeatured == 'number') {
isFeaturedBool = (rawFeatured as number) == 1
}
if (!isFeaturedBool) {
continue
}
products.push(parseProductFromRaw(item))
if (products.length >= limit) break
}
return products
} catch (error) {
console.error('获取推荐商品异常:', error)
return []
}
}
// 获取特价商品这里假设没有specific flag, just use logic or tag if exists, defaulting to hot for now or just skip
// Modify to use compatible logic if badge column doesn't exist
async getDiscountProducts(limit: number = 10): Promise<Product[]> {
return [] as Product[] // 暂无特价字段
}
// 获取当前用户的购物车商品(关联商品和店铺信息)
async getCartItems(): Promise<CartItem[]> {
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<string, any>()
if (productIds.length > 0) {
// Convert string array to any array for .in()
const productIdsAny: any[] = []
for(let i=0; i<productIds.length; i++) {
productIdsAny.push(productIds[i])
}
const productRes = await supa
.from('ml_products')
.select('*')
.in('id', productIdsAny)
.execute()
console.log('[getCartItems] 商品查询结果:', productRes.error != null ? 'error' : 'success', productRes.error)
if (productRes.error == null && productRes.data != null) {
const products = productRes.data as any[]
console.log('[getCartItems] 商品数量:', products.length)
for (let i = 0; i < products.length; i++) {
let p = products[i]
let pid: string = ''
let pname: string = ''
if (p instanceof UTSJSONObject) {
pid = p.getString('id') ?? ''
pname = p.getString('name') ?? ''
} else {
const pObj = JSON.parse(JSON.stringify(p)) as UTSJSONObject
pid = pObj.getString('id') ?? ''
pname = pObj.getString('name') ?? ''
}
console.log('[getCartItems] 商品:', pid, pname)
if (pid !== '') {
productMap.set(pid, p)
}
}
}
}
// 批量查询店铺信息
const shopMap = new Map<string, string>()
const merchantIds: string[] = []
// 遍历 productMap 获取 merchant_id
productMap.forEach((p: any, pid: string) => {
let mid: string = ''
if (p instanceof UTSJSONObject) {
mid = p.getString('merchant_id') ?? ''
} else {
const pObj = JSON.parse(JSON.stringify(p)) as UTSJSONObject
mid = pObj.getString('merchant_id') ?? ''
}
if (mid !== '' && !merchantIds.includes(mid)) {
merchantIds.push(mid)
}
})
if (merchantIds.length > 0) {
const merchantIdsAny: any[] = []
for(let i=0; i<merchantIds.length; i++) {
merchantIdsAny.push(merchantIds[i])
}
const shopRes = await supa
.from('ml_shops')
.select('merchant_id,shop_name')
.in('merchant_id', merchantIdsAny)
.execute()
if (shopRes.error == null && shopRes.data != null) {
const shops = shopRes.data as any[]
for (let i = 0; i < shops.length; i++) {
let s = shops[i]
let mid: string = ''
let sname: string = ''
if (s instanceof UTSJSONObject) {
mid = s.getString('merchant_id') ?? ''
sname = s.getString('shop_name') ?? ''
} else {
const sObj = JSON.parse(JSON.stringify(s)) as UTSJSONObject
mid = sObj.getString('merchant_id') ?? ''
sname = sObj.getString('shop_name') ?? ''
}
if (mid !== '') {
shopMap.set(mid, sname)
}
}
}
}
// 批量查询 SKU 详情
const skuMap = new Map<string, any>()
if (skuIds.length > 0) {
const skuIdsAny: any[] = []
for(let i=0; i<skuIds.length; i++) {
skuIdsAny.push(skuIds[i])
}
const skuRes = await supa
.from('ml_product_skus')
.select('*')
.in('id', skuIdsAny)
.execute()
if (skuRes.error == null && skuRes.data != null) {
const skus = skuRes.data as any[]
for (let i = 0; i < skus.length; i++) {
let s = skus[i]
let sid: string = ''
if (s instanceof UTSJSONObject) {
sid = s.getString('id') ?? ''
} else {
const sObj = JSON.parse(JSON.stringify(s)) as UTSJSONObject
sid = sObj.getString('id') ?? ''
}
if (sid !== '') {
skuMap.set(sid, s)
}
}
}
}
// 处理返回数据构建CartItem数组
const cartItems: CartItem[] = []
if ((cartData as any[]) != null) { // Simplify: cartData is already any[]
const cartArray = cartData as any[]
for (let i = 0; i < cartArray.length; i++) {
let item = cartArray[i]
let itemId: string = ''
let userIdVal: string = ''
let productId: string = ''
let skuId: string = ''
let quantity: number = 0
let selected: boolean = false
let createdAt: string = ''
let updatedAt: string = ''
let cartMerchantId: string = ''
if (item instanceof UTSJSONObject) {
itemId = item.getString('id') ?? ''
userIdVal = item.getString('user_id') ?? ''
productId = item.getString('product_id') ?? ''
skuId = item.getString('sku_id') ?? ''
quantity = item.getNumber('quantity') ?? 0
selected = item.getBoolean('selected') ?? false
createdAt = item.getString('created_at') ?? ''
updatedAt = item.getString('updated_at') ?? ''
cartMerchantId = item.getString('merchant_id') ?? ''
} else {
const iObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
itemId = iObj.getString('id') ?? ''
userIdVal = iObj.getString('user_id') ?? ''
productId = iObj.getString('product_id') ?? ''
skuId = iObj.getString('sku_id') ?? ''
quantity = iObj.getNumber('quantity') ?? 0
selected = iObj.getBoolean('selected') ?? false
createdAt = iObj.getString('created_at') ?? ''
updatedAt = iObj.getString('updated_at') ?? ''
cartMerchantId = iObj.getString('merchant_id') ?? ''
}
const product = productMap.get(productId)
const sku = (skuId !== '' && skuMap.has(skuId)) ? skuMap.get(skuId) : null
console.log('[getCartItems] 处理购物车项:', itemId, 'productId:', productId, 'product存在:', product != null, 'sku存在:', sku != null)
let merchantId: string = cartMerchantId
let productName: string = ''
let productImage: string = ''
let productPrice: number = 0
let productSpec: string = ''
let shopNameStr: string = '未知店铺'
if (product != null) {
console.log('[getCartItems] product类型:', typeof product, 'instanceof UTSJSONObject:', product instanceof UTSJSONObject)
if (product instanceof UTSJSONObject) {
// 优先使用购物车中的 merchant_id如果没有则使用商品中的
if (merchantId == '') {
merchantId = product.getString('merchant_id') ?? ''
}
productName = product.getString('name') ?? ''
productImage = product.getString('main_image_url') ?? ''
productPrice = product.getNumber('base_price') ?? 0
console.log('[getCartItems] 从UTSJSONObject获取: name=', productName, 'price=', productPrice)
} else {
const pObj = JSON.parse(JSON.stringify(product)) as UTSJSONObject
if (merchantId == '') {
merchantId = pObj.getString('merchant_id') ?? ''
}
productName = pObj.getString('name') ?? ''
productImage = pObj.getString('main_image_url') ?? ''
productPrice = pObj.getNumber('base_price') ?? 0
console.log('[getCartItems] 从普通对象获取: name=', productName, 'price=', productPrice)
}
// 从 shopMap 获取店铺名称
if (merchantId !== '' && shopMap.has(merchantId)) {
shopNameStr = shopMap.get(merchantId) ?? '未知店铺'
}
}
// 如果有SKU信息覆盖价格、图片和规格
if (sku != null) {
if (sku instanceof UTSJSONObject) {
const skuPrice = sku.getNumber('price')
if (skuPrice != null && skuPrice > 0) {
productPrice = skuPrice
}
const skuImg = sku.getString('image_url')
if (skuImg != null && skuImg !== '') {
productImage = skuImg
}
const specRaw = sku.get('specifications')
if (specRaw != null) {
if (typeof specRaw === 'string') {
productSpec = specRaw
} else if (specRaw instanceof UTSJSONObject) {
const keys = ['规格', '颜色', '尺码', '容量', '版本', '型号']
const result: string[] = []
for (let k = 0; k < keys.length; k++) {
const key = keys[k]
const val = specRaw.get(key)
if (val != null && val !== '') {
result.push(`${val}`)
}
}
if (result.length > 0) {
productSpec = result.join(' ')
} else {
const allKeys = UTSJSONObject.keys(specRaw)
const parts: string[] = []
for(let k = 0; k < allKeys.length; k++) {
let val = specRaw.get(allKeys[k])
if (val != null) {
parts.push(`${val}`)
}
}
productSpec = parts.join(' ')
}
} else {
try {
let jsonStr = JSON.stringify(specRaw)
productSpec = jsonStr.replace(/["{}]/g, '').replace(/,/g, ' ').replace(/:/g, ' ')
} catch (e) {}
}
}
} else {
const sObj = JSON.parse(JSON.stringify(sku)) as UTSJSONObject
const skuPrice = sObj.getNumber('price') ?? 0
if (skuPrice > 0) productPrice = skuPrice
const skuImg = sObj.getString('image_url') ?? ''
if (skuImg !== '') productImage = skuImg
const specRaw = sObj.get('specifications')
if (specRaw != null) {
if (typeof specRaw === 'string') {
productSpec = specRaw
} else if (specRaw instanceof UTSJSONObject) {
const keys = ['规格', '颜色', '尺码', '容量', '版本', '型号']
const result: string[] = []
for (let k = 0; k < keys.length; k++) {
const key = keys[k]
const val = specRaw.get(key)
if (val != null && val !== '') {
result.push(`${val}`)
}
}
if (result.length > 0) {
productSpec = result.join(' ')
} else {
const allKeys = UTSJSONObject.keys(specRaw)
const parts: string[] = []
for(let k = 0; k < allKeys.length; k++) {
let val = specRaw.get(allKeys[k])
if (val != null) {
parts.push(`${val}`)
}
}
productSpec = parts.join(' ')
}
} else {
try {
let jsonStr = JSON.stringify(specRaw)
productSpec = jsonStr.replace(/["{}]/g, '').replace(/,/g, ' ').replace(/:/g, ' ')
} catch (e) {}
}
}
}
}
let shopIdStr = merchantId != '' ? merchantId : 'unknown_shop'
cartItems.push({
id: itemId,
user_id: userIdVal,
product_id: productId,
sku_id: skuId,
merchant_id: merchantId,
quantity: quantity,
selected: selected,
product_name: productName,
product_image: productImage,
product_price: productPrice,
product_specification: productSpec,
shop_id: shopIdStr,
shop_name: shopNameStr,
created_at: createdAt,
updated_at: updatedAt
} as CartItem)
}
}
return cartItems
} catch (error) {
console.error('获取购物车异常:', error)
return []
}
}
// 获取用户通知 (系统、活动、订单)
async getUserNotifications(type: string | null = null): Promise<Notification[]> {
try {
console.log('[getUserNotifications] 开始获取通知')
const userId = this.getCurrentUserId()
if (userId == null) return []
let query = supa
.from('ml_notifications')
.select('*')
.eq('user_id', userId)
if (type != null) {
query = query.eq('type', type)
}
const response = await query.order('created_at', { ascending: false }).execute()
if (response.error != null) {
console.error('获取通知失败:', response.error)
return []
}
const rawData = response.data
if (rawData == null) return []
const notifications: Notification[] = []
const rawList = rawData as any[]
console.log('[getUserNotifications] 获取到通知数量:', rawList.length)
for (let i = 0; i < rawList.length; i++) {
const item = rawList[i]
const noteObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
const getSafeString = (key: string): string => {
const val = noteObj.get(key)
if (val == null) return ''
if (typeof val == 'string') return val
return ''
}
const getSafeNumber = (key: string): number => {
const val = noteObj.get(key)
if (val == null) return 0
if (typeof val == 'number') return val
return 0
}
const getSafeBoolean = (key: string): boolean => {
const val = noteObj.get(key)
if (val == null) return false
if (typeof val == 'boolean') return val
if (typeof val == 'number') return (val as number) == 1
return false
}
const note: Notification = {
id: getSafeString('id'),
user_id: getSafeString('user_id'),
type: getSafeString('type'),
title: getSafeString('title'),
content: getSafeString('content'),
is_read: getSafeBoolean('is_read'),
icon_url: getSafeString('icon_url'),
link_url: getSafeString('link_url'),
extra_data: getSafeString('extra_data'),
created_at: getSafeString('created_at')
}
notifications.push(note)
}
return notifications
} catch (e) {
console.error('获取通知异常:', e)
return []
}
}
// 获取聊天会话列表
async getChatRooms(): Promise<ChatRoom[]> {
try {
console.log('[getChatRooms] 开始获取聊天会话')
const userId = this.getCurrentUserId()
if (userId == null) return []
const response = await supa
.from('ml_chat_rooms')
.select('*')
.eq('user_id', userId)
.order('updated_at', { ascending: false })
.execute()
if (response.error != null) {
console.error('获取聊天会话失败:', response.error)
return []
}
const rawData = response.data
if (rawData == null) return []
const rooms: ChatRoom[] = []
const rawList = rawData as any[]
console.log('[getChatRooms] 获取到会话数量:', rawList.length)
for (let i = 0; i < rawList.length; i++) {
const item = rawList[i]
const roomObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
const getSafeString = (key: string): string => {
const val = roomObj.get(key)
if (val == null) return ''
if (typeof val == 'string') return val
return ''
}
const getSafeNumber = (key: string): number => {
const val = roomObj.get(key)
if (val == null) return 0
if (typeof val == 'number') return val
return 0
}
const getSafeBoolean = (key: string): boolean => {
const val = roomObj.get(key)
if (val == null) return false
if (typeof val == 'boolean') return val
if (typeof val == 'number') return (val as number) == 1
return false
}
const room: ChatRoom = {
id: getSafeString('id'),
user_id: getSafeString('user_id'),
merchant_id: getSafeString('merchant_id'),
shop_name: getSafeString('shop_name'),
shop_logo: getSafeString('shop_logo'),
last_message: getSafeString('last_message'),
last_message_at: getSafeString('last_message_at'),
unread_count: getSafeNumber('unread_count'),
is_top: getSafeBoolean('is_top'),
created_at: getSafeString('created_at'),
updated_at: getSafeString('updated_at')
}
rooms.push(room)
}
return rooms
} catch (e) {
console.error('获取聊天会话异常:', e)
return []
}
}
// 获取用户聊天消息
async getUserChatMessages(): Promise<ChatMessage[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return []
const response = await supa
.from('ml_chat_messages')
.select('*')
.or(`sender_id.eq.${userId},receiver_id.eq.${userId}`)
.order('created_at', { ascending: false })
.limit(50)
.execute()
if (response.error != null) {
console.error('获取聊天记录失败:', response.error)
return []
}
return response.data as ChatMessage[]
} catch (e) {
console.error('获取聊天记录异常:', e)
return []
}
}
// 获取与特定商家的聊天记录 (合并版本)
async getChatMessages(merchantId: string, page: number = 1, pageSize: number = 20): Promise<ChatMessage[]> {
try {
console.log('[getChatMessages] 开始获取聊天记录merchantId:', merchantId, 'page:', page)
const userId = this.getCurrentUserId()
if (userId == null) return []
const fromIndex = (page - 1) * pageSize
const toIndex = fromIndex + pageSize - 1
// 使用 or 组合精确条件查询:(我发给商家) OR (商家发给我)
const queryStr = `and(sender_id.eq.${userId},receiver_id.eq.${merchantId}),and(sender_id.eq.${merchantId},receiver_id.eq.${userId})`
const response = await supa
.from('ml_chat_messages')
.select('*')
.or(queryStr)
.order('created_at', { ascending: false }) // 最新在前
.range(fromIndex, toIndex)
.execute()
if (response.error != null) {
console.error('getChatMessages error:', response.error)
return []
}
const rawData = response.data
if (rawData == null) return []
const messages: ChatMessage[] = []
const rawList = rawData as any[]
console.log('[getChatMessages] 获取到消息数量:', rawList.length)
for (let i = 0; i < rawList.length; i++) {
const item = rawList[i]
const msgObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
const getSafeString = (key: string): string => {
const val = msgObj.get(key)
if (val == null) return ''
return val.toString()
}
const getSafeBoolean = (key: string): boolean => {
const val = msgObj.get(key)
if (val == null) return false
if (typeof val == 'boolean') return val as boolean
return (val.toString() == '1' || val.toString() == 'true')
}
const msg: ChatMessage = {
id: getSafeString('id'),
session_id: getSafeString('session_id'),
sender_id: getSafeString('sender_id'),
receiver_id: getSafeString('receiver_id'),
content: getSafeString('content'),
msg_type: getSafeString('msg_type'),
is_read: getSafeBoolean('is_read'),
is_from_user: getSafeBoolean('is_from_user'),
extra_data: getSafeString('extra_data'),
created_at: getSafeString('created_at')
}
messages.push(msg)
}
return messages
} catch (e) {
console.error('获取聊天记录异常:', e)
return []
}
}
// 发送聊天消息
async sendChatMessage(content: string, toId: string | null = null, type: string = 'text'): Promise<boolean> {
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<boolean> {
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<boolean> {
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<any>
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<boolean> {
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<boolean> {
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<boolean> {
try {
console.log('[batchUpdateCartItemSelection] 开始批量更新')
console.log('[batchUpdateCartItemSelection] cartItemIds:', JSON.stringify(cartItemIds))
console.log('[batchUpdateCartItemSelection] cartItemIds length:', cartItemIds.length)
console.log('[batchUpdateCartItemSelection] selected:', selected)
const userId = this.getCurrentUserId()
if (userId == null) {
console.error('用户未登录,无法更新购物车')
return false
}
const response = await supa
.from('ml_shopping_cart')
.update({
selected: selected,
updated_at: new Date().toISOString()
})
.eq('user_id', userId)
.in('id', cartItemIds as any[])
.execute()
console.log('[batchUpdateCartItemSelection] response.error:', response.error)
console.log('[batchUpdateCartItemSelection] response.data:', JSON.stringify(response.data))
if (response.error != null) {
console.error('批量更新购物车商品选中状态失败:', response.error)
return false
}
return true
} catch (error) {
console.error('批量更新购物车商品选中状态异常:', error)
return false
}
}
// 删除购物车商品
async deleteCartItem(cartItemId: string): Promise<boolean> {
try {
console.log('正在执行删除购物车商品ID:', cartItemId)
const userId = this.getCurrentUserId()
if (userId == null) {
console.error('用户未登录,无法删除购物车商品')
return false
}
const response = await supa
.from('ml_shopping_cart')
.eq('id', cartItemId)
.eq('user_id', userId)
.delete()
.execute()
if (response.error != null) {
console.error('删除购物车商品失败:', response.error)
return false
}
return true
} catch (error) {
console.error('删除购物车商品异常:', error)
return false
}
}
// 批量删除购物车商品
async batchDeleteCartItems(cartItemIds: string[]): Promise<boolean> {
try {
console.log('[batchDeleteCartItems] 开始删除, ids:', cartItemIds.length)
const userId = this.getCurrentUserId()
if (userId == null) {
console.error('用户未登录,无法删除购物车商品')
return false
}
console.log('[batchDeleteCartItems] userId:', userId)
const response = await supa
.from('ml_shopping_cart')
.eq('user_id', userId)
.in('id', cartItemIds as any[])
.delete()
.execute()
console.log('[batchDeleteCartItems] response.error:', response.error)
if (response.error != null) {
console.error('批量删除购物车商品失败:', response.error)
return false
}
console.log('[batchDeleteCartItems] 删除成功')
return true
} catch (error) {
console.error('批量删除购物车商品异常:', error)
return false
}
}
// 清空购物车
async clearCart(): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.error('用户未登录,无法清空购物车')
return false
}
const response = await supa
.from('ml_shopping_cart')
.eq('user_id', userId)
.delete()
.execute()
if (response.error != null) {
console.error('清空购物车失败:', response.error)
return false
}
return true
} catch (error) {
console.error('清空购物车异常:', error)
return false
}
}
// 获取当前用户的所有地址
async getAddresses(): Promise<UserAddress[]> {
const userId = this.getCurrentUserId()
if (userId == null) {
console.warn('[getAddresses] 用户未登录,无法获取地址')
return []
}
try {
console.log('[getAddresses] 查询地址, userId:', userId)
const response = await supa
.from('ml_user_addresses')
.select('*')
.eq('user_id', userId)
.order('is_default', { ascending: false })
.order('created_at', { ascending: false })
.execute()
console.log('[getAddresses] response.error:', response.error)
console.log('[getAddresses] response.data:', JSON.stringify(response.data))
if (response.error != null) {
console.error('[getAddresses] 获取地址失败:', response.error)
return []
}
const data = response.data
if (data == null) {
return []
}
const result: UserAddress[] = []
const rawData = data as any[]
for (let i = 0; i < rawData.length; i++) {
const item = rawData[i]
let itemObj: UTSJSONObject
if (item instanceof UTSJSONObject) {
itemObj = item as UTSJSONObject
} else {
itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
}
const addr: UserAddress = {
id: itemObj.getString('id') ?? '',
user_id: itemObj.getString('user_id') ?? '',
recipient_name: itemObj.getString('receiver_name') ?? itemObj.getString('recipient_name') ?? '',
phone: itemObj.getString('receiver_phone') ?? itemObj.getString('phone') ?? '',
province: itemObj.getString('province') ?? '',
city: itemObj.getString('city') ?? '',
district: itemObj.getString('district') ?? '',
detail_address: itemObj.getString('address_detail') ?? itemObj.getString('detail_address') ?? '',
is_default: itemObj.getBoolean('is_default') ?? false,
label: itemObj.getString('label') ?? '',
created_at: itemObj.getString('created_at') ?? '',
updated_at: itemObj.getString('updated_at') ?? ''
}
result.push(addr)
}
console.log('[getAddresses] 返回地址数量:', result.length)
return result
} catch (error) {
console.error('[getAddresses] 获取地址异常:', error)
return []
}
}
// 根据ID获取地址详情
async getAddressById(addressId: string): Promise<UserAddress | null> {
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<boolean> {
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<boolean> {
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<ConfirmReceiptResponse> {
console.log('[confirmReceipt] 开始确认收货, orderId:', orderId)
try {
const userId = this.getCurrentUserId()
console.log('[confirmReceipt] userId:', userId)
if (userId == null) {
return { success: false, error: '用户未登录' }
}
const updateData = new UTSJSONObject()
updateData.set('order_status', 4)
updateData.set('delivered_at', new Date().toISOString())
updateData.set('completed_at', new Date().toISOString())
updateData.set('updated_at', new Date().toISOString())
console.log('[confirmReceipt] 准备更新订单状态, updateData:', JSON.stringify(updateData))
const response = await supa
.from('ml_orders')
.update(updateData)
.eq('id', orderId)
.eq('user_id', userId)
.execute()
console.log('[confirmReceipt] response.status:', response.status)
console.log('[confirmReceipt] response.error:', response.error)
console.log('[confirmReceipt] response.data:', JSON.stringify(response.data))
// 检查 HTTP 状态码
if (response.status != null && response.status >= 400) {
// 尝试从 response.data 中提取错误信息
let errorMsg = '请求失败'
if (response.data != null) {
try {
const errorData = response.data as UTSJSONObject
const msg = errorData.getString('message')
if (msg != null) {
errorMsg = msg
}
} catch (e) {
// ignore
}
}
console.log('[confirmReceipt] HTTP错误:', response.status, errorMsg)
return { success: false, error: errorMsg }
}
if (response.error != null) {
return { success: false, error: response.error.message }
}
// 检查 response.data 是否包含错误代码
if (response.data != null) {
try {
const respData = response.data as UTSJSONObject
const errorCode = respData.getString('code')
if (errorCode != null) {
const errorMsg = respData.getString('message') ?? '数据库错误'
console.log('[confirmReceipt] 数据库错误:', errorCode, errorMsg)
return { success: false, error: errorMsg }
}
} catch (e) {
// ignore
}
}
// 检查是否有数据被更新
if (response.data == null || (Array.isArray(response.data) && response.data.length === 0)) {
console.log('[confirmReceipt] 没有数据被更新,可能订单不存在或无权限')
return { success: false, error: '订单不存在或无权限更新' }
}
console.log('[confirmReceipt] 确认收货成功')
return { success: true }
} catch (e: any) {
console.error('[confirmReceipt] 异常:', e)
return { success: false, error: e.message }
}
}
// 取消订单
async cancelOrder(orderId: string): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
return false
}
const response = await supa
.from('ml_orders')
.update({
order_status: 5,
updated_at: new Date().toISOString()
})
.eq('id', orderId)
.eq('user_id', userId)
.execute()
if (response.error != null) {
console.error('取消订单失败:', response.error)
return false
}
return true
} catch (e) {
console.error('取消订单异常:', e)
return false
}
}
// 删除订单
async deleteOrder(orderId: string): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
return false
}
const response = await supa
.from('ml_orders')
.delete()
.eq('id', orderId)
.eq('user_id', userId)
.execute()
if (response.error != null) {
console.error('删除订单失败:', response.error)
return false
}
return true
} catch (e) {
console.error('删除订单异常:', e)
return false
}
}
// 确认收货
async confirmOrderReceived(orderId: string): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
return false
}
const response = await supa
.from('ml_orders')
.update({
order_status: 4,
shipping_status: 3,
updated_at: new Date().toISOString()
})
.eq('id', orderId)
.eq('user_id', userId)
.execute()
if (response.error != null) {
console.error('确认收货失败:', response.error)
return false
}
return true
} catch (e) {
console.error('确认收货异常:', e)
return false
}
}
// 删除地址
async deleteAddress(addressId: string): Promise<boolean> {
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<void> {
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<any | null> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return null
// 联合查询 auth user 和 profile
// 由于 Supabase auth table 不可直接访问,这里查询 ml_user_profiles
// 注意:使用 limit(1) 替代 single() 以避免 Android 端类型转换错误
const response = await supa
.from('ml_user_profiles')
.select('*')
.eq('user_id', userId)
.limit(1)
.execute()
if (response.error != null) {
// 如果不存在 profile可能只有 auth user这里暂时返回空或创建默认
return null
}
const rawData = response.data
if (rawData == null) return null
// 作为数组处理
const rawList = rawData as any[]
if (rawList.length == 0) return null
return rawList[0]
} catch (e) {
return null
}
}
// 创建订单
async createOrder(orderData: CreateOrderParams): Promise<string | null> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.error('CreateOrder: User not logged in')
return null
}
const orderNo = 'ML' + Date.now() + Math.floor(Math.random() * 1000)
let merchantId = orderData.merchant_id
console.log('[CreateOrder] 原始 merchant_id:', merchantId)
if (merchantId == null || merchantId == '' || merchantId == 'unknown') {
console.warn('[CreateOrder] merchant_id 为空或无效,将使用 userId 作为 fallback')
merchantId = userId
}
console.log('[CreateOrder] 最终使用的 merchant_id:', merchantId)
let shippingAddrStr = '{}'
if (orderData.shipping_address != null) {
if (typeof orderData.shipping_address === 'string') {
shippingAddrStr = orderData.shipping_address
} else {
shippingAddrStr = JSON.stringify(orderData.shipping_address)
}
}
const orderPayload = new UTSJSONObject()
orderPayload.set('user_id', userId)
orderPayload.set('merchant_id', merchantId)
orderPayload.set('order_no', orderNo)
orderPayload.set('product_amount', orderData.product_amount)
orderPayload.set('shipping_fee', orderData.shipping_fee)
orderPayload.set('total_amount', orderData.total_amount)
orderPayload.set('paid_amount', 0)
orderPayload.set('shipping_address', shippingAddrStr)
orderPayload.set('order_status', 1)
orderPayload.set('payment_status', 1)
orderPayload.set('shipping_status', 1)
orderPayload.set('created_at', new Date().toISOString())
orderPayload.set('updated_at', new Date().toISOString())
console.log('[CreateOrder] 插入订单数据:', JSON.stringify(orderPayload))
console.log('[CreateOrder] 期望的订单号:', orderNo)
const orderResponse = await supa
.from('ml_orders')
.insert(orderPayload)
.execute()
console.log('[CreateOrder] insert 完成')
console.log('[CreateOrder] orderResponse.error:', orderResponse.error)
if (orderResponse.error != null) {
console.error('[CreateOrder] 创建订单失败:', orderResponse.error)
return null
}
console.log('[CreateOrder] 开始查询新创建的订单, order_no:', orderNo)
const queryResponse = await supa
.from('ml_orders')
.select('id, order_no')
.eq('order_no', orderNo)
.execute()
console.log('[CreateOrder] queryResponse.error:', queryResponse.error)
console.log('[CreateOrder] queryResponse.data:', JSON.stringify(queryResponse.data))
if (queryResponse.error != null) {
console.error('[CreateOrder] 查询订单失败:', queryResponse.error)
return null
}
const queryData = queryResponse.data as any
let orderId = ''
console.log('[CreateOrder] queryData 类型:', typeof queryData, '是否数组:', Array.isArray(queryData))
if (Array.isArray(queryData) && queryData.length > 0) {
console.log('[CreateOrder] queryData 长度:', queryData.length)
const firstItemRaw = queryData[0]
console.log('[CreateOrder] firstItemRaw 类型:', typeof firstItemRaw)
// 将 firstItemRaw 转换为可访问的对象
const firstItemStr = JSON.stringify(firstItemRaw)
const firstItemParsed = JSON.parse(firstItemStr)
if (firstItemParsed == null) {
console.error('[CreateOrder] 解析订单数据失败')
return null
}
const firstItem = firstItemParsed as UTSJSONObject
orderId = (firstItem.getString('id') ?? '') as string
console.log('[CreateOrder] 找到新创建的订单, id:', orderId)
} else {
console.error('[CreateOrder] 未找到新创建的订单,插入可能失败')
return null
}
console.log('[CreateOrder] 订单创建成功, orderId:', orderId)
const orderItems: UTSJSONObject[] = []
console.log('[CreateOrder] orderData.items 类型:', typeof orderData.items, '是否数组:', Array.isArray(orderData.items))
if (orderData.items == null) {
console.error('[CreateOrder] orderData.items 为 null!')
return orderId
}
const rawItems = orderData.items as any[]
console.log('[CreateOrder] rawItems 长度:', rawItems.length)
if (rawItems.length === 0) {
console.warn('[CreateOrder] rawItems 为空数组,没有商品项需要插入')
return orderId
}
for(let i = 0; i < rawItems.length; i++) {
const rawItem = rawItems[i]
const itemStr = JSON.stringify(rawItem)
const itemParsed = JSON.parse(itemStr)
if (itemParsed == null) {
console.error('[CreateOrder] 商品项解析失败')
continue
}
const item = itemParsed as UTSJSONObject
const itemJson = new UTSJSONObject()
let pId = item.get('product_id')
if (pId == null) {
pId = item.get('id')
}
const productId = (pId ?? '') as string
itemJson.set('order_id', orderId)
itemJson.set('product_id', productId)
const skuIdVal = item.get('sku_id')
if (skuIdVal != null && skuIdVal !== '') {
itemJson.set('sku_id', skuIdVal as string)
}
const productName = (item.get('product_name') ?? '') as string
itemJson.set('product_name', productName)
const sName = item.get('sku_name')
itemJson.set('sku_name', (sName ?? '') as string)
const specVal = item.get('specifications')
let skuSnapshot = '{}'
if (specVal != null) {
if (typeof specVal === 'string') {
skuSnapshot = specVal as string
} else {
skuSnapshot = JSON.stringify(specVal)
}
}
itemJson.set('sku_snapshot', skuSnapshot)
itemJson.set('specifications', skuSnapshot)
const img1 = item.get('product_image')
const img2 = item.get('image_url')
let imgUrl = ((img1 ?? img2 ?? '') as string)
while (imgUrl.indexOf('`') >= 0) {
imgUrl = imgUrl.replace('`', '')
}
itemJson.set('image_url', imgUrl)
const iPriceRaw = item.getNumber('price') ?? 0
const iMemberPrice = item.getNumber('member_price') ?? 0
// 优先使用会员价
const iPrice = (iMemberPrice > 0 && iMemberPrice < iPriceRaw) ? iMemberPrice : iPriceRaw
const iQty = item.getNumber('quantity') ?? 1
itemJson.set('price', iPrice)
itemJson.set('quantity', iQty)
itemJson.set('total_amount', iPrice * iQty)
itemJson.set('created_at', new Date().toISOString())
orderItems.push(itemJson)
}
console.log('[CreateOrder] 插入订单项数量:', orderItems.length)
for (let j: number = 0; j < orderItems.length; j++) {
console.log('[CreateOrder] 开始插入订单项', j)
const itemJson = orderItems[j]
// 将 UTSJSONObject 转换为普通对象
console.log('[CreateOrder] 序列化订单项...')
const itemObjStr = JSON.stringify(itemJson)
console.log('[CreateOrder] 订单项 JSON:', itemObjStr)
const itemObjParsed = JSON.parse(itemObjStr)
console.log('[CreateOrder] itemObjParsed 类型:', typeof itemObjParsed)
if (itemObjParsed == null) {
console.error('[CreateOrder] 订单项转换失败')
continue
}
// 使用 UTSJSONObject 而不是 Record<string, any>
const itemObj = itemObjParsed as UTSJSONObject
console.log('[CreateOrder] 执行 insert...')
const itemsResponse = await supa
.from('ml_order_items')
.insert(itemObj)
.execute()
console.log('[CreateOrder] insert 完成, error:', itemsResponse.error)
if (itemsResponse.error != null) {
console.error('[CreateOrder] 创建订单项失败:', itemsResponse.error)
}
}
console.log('[CreateOrder] 订单项创建成功')
const cartItemIds: string[] = []
for(let i = 0; i < rawItems.length; i++) {
const rawItem = rawItems[i]
const itemParsed = JSON.parse(JSON.stringify(rawItem))
if (itemParsed == null) continue
const item = itemParsed as UTSJSONObject
const iid = item.getString('id')
if (iid != null && iid.length > 10) {
cartItemIds.push(iid)
}
}
if (cartItemIds.length > 0) {
await this.batchDeleteCartItems(cartItemIds)
}
return orderId
} catch (error) {
console.error('[CreateOrder] 创建订单异常:', error)
return null
}
}
// 批量通过店铺创建订单
async createOrdersByShop(params: ShopOrderParams): Promise<ShopOrderResponse> {
try {
const orderIds: string[] = []
const groups = params.shopGroups as any[]
let grandTotal = 0.0
for(let k = 0; k < groups.length; k++) {
const g = JSON.parse(JSON.stringify(groups[k])) as UTSJSONObject
const gItemsRaw = g.get('items')
if (gItemsRaw == null) continue
const gItems = gItemsRaw as any[]
for(let gi = 0; gi < gItems.length; gi++) {
const it = JSON.parse(JSON.stringify(gItems[gi])) as UTSJSONObject
// 优先使用会员价
let itPrice = it.getNumber('price') ?? 0
const itMemberPrice = it.getNumber('member_price') ?? 0
if (itMemberPrice > 0 && itMemberPrice < itPrice) {
itPrice = itMemberPrice
}
const itQty = it.getNumber('quantity') ?? 1
grandTotal += itPrice * itQty
}
}
// 为每个店铺创建一个订单
for (let i = 0; i < groups.length; i++) {
const group = JSON.parse(JSON.stringify(groups[i])) as UTSJSONObject
const shopItemsRaw = group.get('items')
if (shopItemsRaw == null) continue
const shopItems = shopItemsRaw as any[]
let productAmount = 0.0
for(let j = 0; j < shopItems.length; j++) {
const sItem = JSON.parse(JSON.stringify(shopItems[j])) as UTSJSONObject
// 优先使用会员价
let siPrice = sItem.getNumber('price') ?? 0
const siMemberPrice = sItem.getNumber('member_price') ?? 0
if (siMemberPrice > 0 && siMemberPrice < siPrice) {
siPrice = siMemberPrice
}
const siQty = sItem.getNumber('quantity') ?? 1
productAmount += siPrice * siQty
}
// 简单平摊运费和优惠 (实际逻辑可能更复杂)
const ratio = grandTotal > 0 ? (productAmount / grandTotal) : 0
const shopShippingFee = params.deliveryFee * ratio
const shopDiscount = params.discountAmount * ratio
const shopTotal = productAmount + shopShippingFee - shopDiscount
const mId = group.getString('merchant_id')
const sId = group.getString('shopId')
const shopName = group.getString('shopName')
console.log('[createOrdersByShop] 店铺组信息:', {
merchant_id: mId,
shopId: sId,
shopName: shopName
})
const finalMerchantId = (mId != null && mId != '') ? mId : (sId ?? '')
console.log('[createOrdersByShop] 最终使用的 merchant_id:', finalMerchantId)
// 将 shopItems 转换为普通对象数组
const plainItems: any[] = []
for(let k = 0; k < shopItems.length; k++) {
const plainItemRaw = JSON.parse(JSON.stringify(shopItems[k]))
if (plainItemRaw != null) {
plainItems.push(plainItemRaw as any)
}
}
console.log('[createOrdersByShop] plainItems 数量:', plainItems.length)
const orderId = await this.createOrder({
merchant_id: finalMerchantId,
product_amount: productAmount,
shipping_fee: shopShippingFee,
total_amount: shopTotal,
shipping_address: params.shipping_address,
items: plainItems
})
if (orderId != null) {
orderIds.push(orderId)
} else {
return { success: false, orderIds, error: `店铺 ${shopName} 订单创建失败` }
}
}
return { success: true, orderIds }
} catch (e) {
console.error('批量创建订单异常:', e)
return { success: false, orderIds: [], error: '系统异常' }
}
}
// 获取订单列表
async getOrders(status: number = 0): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: any[] = []
return empty
}
// 关联查询店铺表获取店铺名称
let query = supa
.from('ml_orders')
.select('*, ml_order_items(*), ml_shops(shop_name)')
.eq('user_id', userId)
.order('created_at', { ascending: false })
if (status > 0) {
query = query.eq('order_status', status)
}
const response = await query.execute()
console.log('[getOrders] response.error:', response.error)
if (response.data != null && Array.isArray(response.data)) {
console.log('[getOrders] 订单数量:', response.data.length)
}
if (response.error != null) {
console.error('获取订单列表失败:', response.error)
const empty: any[] = []
return empty
}
const data = response.data
if (data == null) {
const empty: any[] = []
return empty
}
// 修复订单项中的图片URL
const orders = data as any[]
for (let i = 0; i < orders.length; i++) {
const order = orders[i]
const orderStr = JSON.stringify(order)
const orderObj = JSON.parse(orderStr) as UTSJSONObject
const itemsRaw = orderObj.get('ml_order_items')
if (itemsRaw != null && Array.isArray(itemsRaw)) {
const items = itemsRaw as any[]
for (let j = 0; j < items.length; j++) {
const item = items[j]
const itemStr = JSON.stringify(item)
const itemObj = JSON.parse(itemStr) as UTSJSONObject
const imgUrl = itemObj.getString('image_url')
if (imgUrl != null) {
itemObj['image_url'] = fixImageUrl(imgUrl)
}
const prodImg = itemObj.getString('product_image')
if (prodImg != null) {
itemObj['product_image'] = fixImageUrl(prodImg)
}
items[j] = itemObj
}
orderObj['ml_order_items'] = items
orders[i] = orderObj
}
}
return orders
} catch (error) {
console.error('获取订单列表异常:', error)
const empty: any[] = []
return empty
}
}
// 获取订单详情
async getOrderDetail(orderId: string): Promise<any | null> {
try {
console.log('[getOrderDetail] 开始获取订单详情orderId:', orderId)
const userId = this.getCurrentUserId()
if (userId == null) return null
const response = await supa
.from('ml_orders')
.select('*, ml_order_items(*)')
.eq('id', orderId)
.eq('user_id', userId!)
.limit(1)
.execute()
console.log('[getOrderDetail] response.error:', response.error)
console.log('[getOrderDetail] response.data:', JSON.stringify(response.data))
if (response.error != null) {
console.error('[getOrderDetail] 获取订单详情失败:', response.error)
return null
}
const rawData = response.data
if (rawData == null) {
console.log('[getOrderDetail] 数据为空')
return null
}
const rawList = rawData as any[]
if (rawList.length == 0) {
console.log('[getOrderDetail] 未找到订单')
return null
}
const orderData = rawList[0]
console.log('[getOrderDetail] 成功获取订单')
const orderObj = JSON.parse(JSON.stringify(orderData)) as UTSJSONObject
const result = new UTSJSONObject()
result.set('id', orderObj.get('id') ?? '')
result.set('order_no', orderObj.get('order_no') ?? '')
result.set('order_status', orderObj.get('order_status') ?? 1)
result.set('user_id', orderObj.get('user_id') ?? '')
result.set('merchant_id', orderObj.get('merchant_id') ?? '')
result.set('product_amount', orderObj.get('product_amount') ?? 0)
result.set('shipping_fee', orderObj.get('shipping_fee') ?? 0)
result.set('total_amount', orderObj.get('total_amount') ?? 0)
result.set('paid_amount', orderObj.get('paid_amount') ?? 0)
result.set('discount_amount', orderObj.get('discount_amount') ?? 0)
result.set('payment_method', orderObj.get('payment_method') ?? '')
result.set('payment_status', orderObj.get('payment_status') ?? 1)
result.set('shipping_status', orderObj.get('shipping_status') ?? 1)
result.set('created_at', orderObj.get('created_at') ?? '')
result.set('paid_at', orderObj.get('paid_at') ?? '')
result.set('shipped_at', orderObj.get('shipped_at') ?? '')
result.set('completed_at', orderObj.get('completed_at') ?? '')
result.set('shipping_address', orderObj.get('shipping_address'))
result.set('ml_order_items', orderObj.get('ml_order_items'))
// 添加物流信息
result.set('tracking_no', orderObj.get('tracking_no') ?? '')
result.set('carrier_name', orderObj.get('carrier_name') ?? '')
result.set('delivered_at', orderObj.get('delivered_at') ?? '')
return result
} catch (e) {
console.error('[getOrderDetail] 获取订单详情异常:', e)
return null
}
}
// 支付订单
async payOrder(orderId: string, paymentMethod: string, amount: number): Promise<boolean> {
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<UTSJSONObject | null> {
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<RefundResponse> {
try {
console.log('[createRefund] 开始处理退款申请')
const userId = this.getCurrentUserId()
if (userId == null) {
console.log('[createRefund] 用户未登录')
return { success: false, message: '请先登录' }
}
const d = JSON.parse(JSON.stringify(data)) as UTSJSONObject
const orderId = d.getString('order_id') ?? ''
const refundType = d.getNumber('refund_type')
const refundReason = d.getString('refund_reason')
const refundAmount = d.getNumber('refund_amount')
const description = d.getString('description')
const images = d.getArray('images')
console.log('[createRefund] orderId:', orderId)
console.log('[createRefund] refundType:', refundType)
console.log('[createRefund] refundReason:', refundReason)
console.log('[createRefund] refundAmount:', refundAmount)
const payload = {
user_id: userId,
order_id: orderId,
refund_no: 'REF' + Date.now() + Math.floor(Math.random() * 1000),
refund_type: refundType,
refund_reason: refundReason,
refund_amount: refundAmount,
description: description ?? '',
images: images ?? ([] as any[]),
status: 1 // Pending
}
console.log('[createRefund] 准备插入 ml_refunds')
const response = await supa
.from('ml_refunds')
.insert(payload)
.execute()
console.log('[createRefund] insert response.error:', response.error)
if (response.error != null) {
console.error('提交售后失败:', response.error)
return { success: false, message: '提交失败: ' + (response.error.message ?? '未知错误') }
}
console.log('[createRefund] 插入成功,更新订单状态')
// 更新订单状态为退款中
const updateResponse = await supa
.from('ml_orders')
.update({
order_status: 6, // 退款中
updated_at: new Date().toISOString()
})
.eq('id', orderId)
.execute()
console.log('[createRefund] update response.error:', updateResponse.error)
if (updateResponse.error != null) {
console.error('更新订单状态失败:', updateResponse.error)
// 不影响退款申请结果,只记录错误
}
console.log('[createRefund] 完成,返回成功')
return { success: true, message: '申请提交成功' }
} catch (e) {
console.error('提交售后异常:', e)
return { success: false, message: '系统异常' }
}
}
// 取消退款申请
async cancelRefund(orderId: string): Promise<RefundResponse> {
try {
console.log('[cancelRefund] 开始取消退款申请, orderId:', orderId)
const userId = this.getCurrentUserId()
if (userId == null) {
return { success: false, message: '请先登录' }
}
// 更新退款记录状态为已取消
const refundUpdateResponse = await supa
.from('ml_refunds')
.update({
status: 4, // 已取消
updated_at: new Date().toISOString()
})
.eq('order_id', orderId)
.eq('user_id', userId)
.eq('status', 1) // 只能取消待处理的退款
.execute()
if (refundUpdateResponse.error != null) {
console.error('取消退款记录失败:', refundUpdateResponse.error)
return { success: false, message: '取消失败: ' + (refundUpdateResponse.error.message ?? '未知错误') }
}
// 恢复订单状态为已完成(假设之前是已完成状态)
const orderUpdateResponse = await supa
.from('ml_orders')
.update({
order_status: 4, // 已完成
updated_at: new Date().toISOString()
})
.eq('id', orderId)
.execute()
if (orderUpdateResponse.error != null) {
console.error('恢复订单状态失败:', orderUpdateResponse.error)
// 不影响取消退款结果,只记录错误
}
return { success: true, message: '已取消退款申请' }
} catch (e) {
console.error('取消退款异常:', e)
return { success: false, message: '系统异常' }
}
}
// 再次购买
async rePurchase(order: any): Promise<boolean> {
try {
// 将 order 转换为 UTSJSONObject 以安全访问属性
const orderObj = JSON.parse(JSON.stringify(order)) as UTSJSONObject
// 尝试获取 ml_order_items 或 items
let itemsKey = 'ml_order_items'
let itemsRaw = orderObj.get(itemsKey)
if (itemsRaw == null) {
itemsKey = 'items'
itemsRaw = orderObj.get(itemsKey)
}
if (itemsRaw == null) return false
// 断言为数组
const items = itemsRaw as any[]
if (items.length === 0) return false
// 简单的循环添加,实际项目中可以优化为批量插入
for (let i = 0; i < items.length; i++) {
// 同样item 也是 UTSJSONObject 或支持访问的对象
const item = JSON.parse(JSON.stringify(items[i])) as UTSJSONObject
const productId = item.getString('product_id')
const skuId = item.getString('sku_id')
// 数量可能是数字或字符串
const quantity = item.getNumber('quantity') ?? 1
if (productId != null) {
await this.addToCart(productId, quantity, skuId ?? '', '')
}
}
return true
} catch (e) {
console.error('rePurchase error', e)
return false
}
}
// 申请售后 (Legacy/Simple update)
async applyRefund(orderId: string, reason: string): Promise<boolean> {
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<any[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: any[] = []
return empty
}
let query = supa
.from('ml_refunds')
.select(`
*,
order:ml_orders!inner (
order_no,
created_at,
ml_order_items (
product_id,
product_name,
image_url
)
)
`)
.eq('user_id', userId)
.order('created_at', { ascending: false })
if (statusList.length > 0) {
// 显式转换为 any[] 以匹配 .in 方法的参数要求
const anyList = statusList as any[]
query = query.in('status', anyList)
}
query = query.range((page - 1) * pageSize, page * pageSize - 1)
const response = await query.execute()
if (response.error != null) {
console.error('获取售后列表失败:', response.error)
const empty: any[] = []
return empty
}
const data = response.data
if (data == null) {
const empty: any[] = []
return empty
}
return data
} catch (e) {
console.error('获取售后列表异常:', e)
const empty: any[] = []
return empty
}
}
async deleteRefund(refundId: string): Promise<boolean> {
try {
const response = await supa
.from('ml_refunds')
.delete()
.eq('id', refundId)
.execute()
if (response.error != null) {
console.error('删除退款记录失败:', response.error)
return false
}
return true
} catch (e) {
console.error('删除退款记录异常:', e)
return false
}
}
async getUserBalanceNumber(): Promise<number> {
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<number> {
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<any[]> {
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<any[]> {
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<any[]> {
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<any[]> {
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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
try {
const userId = this.getCurrentUserId()
console.log(`[CheckFav] Checking for User: ${userId}, Product: ${productId}`)
if (userId == null) return false
const response = await supa
.from('ml_user_favorites')
.select('*') // Select all to verify data
.eq('user_id', userId!)
.eq('target_id', productId)
.eq('target_type', '1') // 使用字符串 '1'
.limit(1)
.execute()
// console.log(`[CheckFav] Response: ${JSON.stringify(response)}`)
if (response.error != null) {
console.error(`[CheckFav] Error: ${JSON.stringify(response.error)}`)
return false
}
const data = response.data
if (Array.isArray(data)) {
if ((data as any[]).length > 0) {
// Double check: ensure the returned item actually matches the product ID
// This guards against potential query filter failures
const item = data[0]
let targetId = ''
if (item instanceof UTSJSONObject) {
targetId = item.getString('target_id') ?? ''
} else {
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
targetId = itemObj.getString('target_id') ?? ''
}
if (targetId != '' && targetId != productId) {
console.error(`[CheckFav] ID Mismatch! Query ${productId}, Got ${targetId}`)
return false
}
return true
}
} else if (data instanceof UTSJSONObject) {
// Handle single object return case (though limit(1) usually returns array)
let targetId = data.getString('target_id') ?? ''
if (targetId !== '' && targetId !== productId) {
return false
}
return true
}
return false
} catch(e) {
console.error(`[CheckFav] Exception: ${e}`)
return false
}
}
async toggleFavorite(productId: string): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return false
console.log(`[ToggleFav] Toggling for ${productId}`)
// Check if exists
const exists = await this.checkFavorite(productId)
console.log(`[ToggleFav] Current status: ${exists}`)
if (exists) {
const response = await supa
.from('ml_user_favorites')
.eq('user_id', userId!)
.eq('target_id', productId)
.eq('target_type', '1')
.delete()
.execute()
if (response.error != null) {
console.error('取消收藏失败:', response.error)
return true // 仍然是收藏状态
}
return false // 已取消收藏
} else {
const response = await supa
.from('ml_user_favorites')
.insert({
user_id: userId,
target_id: productId,
target_type: '1',
created_at: new Date().toISOString()
})
.execute()
if (response.error != null) {
console.error('添加收藏失败:', response.error)
return false // 添加失败,仍未收藏
}
return true // 已收藏
}
} catch (e) {
console.error('切换收藏状态异常:', e)
// 发生异常时,尝试查询当前状态返回
return await this.checkFavorite(productId)
}
}
async getFavorites(): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: any[] = []
return empty
}
// 第一步:查询收藏列表
const response = await supa
.from('ml_user_favorites')
.select('*')
.eq('user_id', userId!)
.eq('target_type', '1')
.order('created_at', { ascending: false })
.execute()
if (response.error != null) {
const empty: any[] = []
return empty
}
const favorites = response.data as any[]
if (favorites == null || favorites.length === 0) {
const empty: any[] = []
return empty
}
// 第二步收集商品ID
const productIds: string[] = []
for (let i = 0; i < favorites.length; i++) {
let item: any = favorites[i]
let itemObj: UTSJSONObject
if (item instanceof UTSJSONObject) {
itemObj = item as UTSJSONObject
} else {
itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
}
// target_id 可能是 Integer 或 String 类型,需要安全转换
const targetIdRaw = itemObj.get('target_id')
let pid = ''
if (targetIdRaw != null) {
if (typeof targetIdRaw === 'string') {
pid = targetIdRaw as string
} else if (typeof targetIdRaw === 'number') {
pid = (targetIdRaw as number).toString()
}
}
if (pid !== '') productIds.push(pid)
}
if (productIds.length === 0) return []
// 第三步:批量查询商品详情
const anyProductIds = productIds as any[]
const productRes = await supa
.from('ml_products')
.select('id, name, main_image_url, base_price, sale_count')
.in('id', anyProductIds)
.execute()
if (productRes.error != null) {
const empty: any[] = []
return empty
}
const products = productRes.data as any[]
const productMap = new Map<string, any>()
for (let i = 0; i < products.length; i++) {
// 显式声明类型为 any
let p: any = products[i]
let pid = ''
if (p instanceof UTSJSONObject) {
pid = p.getString('id') ?? ''
} else {
const pObj = JSON.parse(JSON.stringify(p)) as UTSJSONObject
pid = pObj.getString('id') ?? ''
}
if (pid !== '') productMap.set(pid, p)
}
// 第四步:组合数据
const result: any[] = []
for (let i = 0; i < favorites.length; i++) {
let item: any = favorites[i]
let newItem: UTSJSONObject
if (item instanceof UTSJSONObject) {
newItem = JSON.parse(JSON.stringify(item)) as UTSJSONObject
} else {
newItem = JSON.parse(JSON.stringify(item)) as UTSJSONObject
}
// target_id 可能是 Integer 或 String 类型,需要安全转换
const targetIdRaw = newItem.get('target_id')
let targetId = ''
if (targetIdRaw != null) {
if (typeof targetIdRaw === 'string') {
targetId = targetIdRaw as string
} else if (typeof targetIdRaw === 'number') {
targetId = (targetIdRaw as number).toString()
}
}
if (targetId !== '') {
const product = productMap.get(targetId)
if (product != null) {
newItem.set('ml_products', product)
result.push(newItem)
}
}
}
return result
} catch (e) {
console.error('获取收藏列表异常:', e)
return []
}
}
// 获取足迹列表
async getFootprints(): Promise<any[]> {
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<productIds.length; i++) {
productIdsAny.push(productIds[i])
}
// 3. 批量查询商品详情
const productRes = await supa
.from('ml_products_detail_view')
.select('id, name, main_image_url, base_price, market_price, sale_count, merchant_id, shop_name')
.in('id', productIdsAny)
.execute()
// 如果视图失败,回退查基础表
let products: any[] = []
if (productRes.error == null && productRes.data != null) {
products = productRes.data as any[]
} else {
console.warn('View查询失败尝试查询基础表')
const baseRes = await supa
.from('ml_products')
.select('id, name, main_image_url, base_price, market_price, sale_count, merchant_id')
.in('id', productIdsAny)
.execute()
if (baseRes.error == null) {
products = baseRes.data as any[]
}
}
const productMap = new Map<string, any>()
for(let i=0; i<products.length; i++) {
let p = 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)
}
// 4. 组合结果
const result: any[] = []
for (let i = 0; i < footprints.length; i++) {
let fp = footprints[i]
let pid = ''
let viewTime = 0
if (fp instanceof UTSJSONObject) {
pid = fp.getString('product_id') ?? ''
const dateStr = fp.getString('updated_at')
if (dateStr != null) viewTime = new Date(dateStr).getTime()
} else {
const fpObj = JSON.parse(JSON.stringify(fp)) as UTSJSONObject
pid = fpObj.getString('product_id') ?? ''
const dateStr = fpObj.getString('updated_at')
if (dateStr != null) viewTime = new Date(dateStr).getTime()
}
const product = productMap.get(pid)
if (product != null) {
let pName = ''
let pImage = ''
let pPrice = 0
let pOriginalPrice = 0
let pSales = 0
let pShopId = ''
let pShopName = ''
if (product instanceof UTSJSONObject) {
pName = product.getString('name') ?? ''
pImage = product.getString('main_image_url') ?? ''
pPrice = product.getNumber('base_price') ?? 0
pOriginalPrice = product.getNumber('market_price') ?? 0
pSales = product.getNumber('sale_count') ?? 0
pShopId = product.getString('merchant_id') ?? ''
pShopName = product.getString('shop_name') ?? ''
} else {
const pObj = JSON.parse(JSON.stringify(product)) as UTSJSONObject
pName = pObj.getString('name') ?? ''
pImage = pObj.getString('main_image_url') ?? ''
pPrice = pObj.getNumber('base_price') ?? 0
pOriginalPrice = pObj.getNumber('market_price') ?? 0
pSales = pObj.getNumber('sale_count') ?? 0
pShopId = pObj.getString('merchant_id') ?? ''
pShopName = pObj.getString('shop_name') ?? ''
}
const fpObj = new UTSJSONObject()
fpObj.set('id', pid)
fpObj.set('name', pName)
fpObj.set('price', pPrice)
fpObj.set('original_price', pOriginalPrice)
fpObj.set('image', pImage)
fpObj.set('sales', pSales)
fpObj.set('shopId', pShopId)
fpObj.set('shopName', pShopName)
fpObj.set('merchant_id', pShopId)
fpObj.set('viewTime', viewTime)
result.push(fpObj)
}
}
return result
} catch (error) {
console.error('获取足迹异常:', error)
return []
}
}
// 添加/更新足迹
async addFootprint(productId: string): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.log('[addFootprint] 用户未登录')
return false
}
console.log('[addFootprint] 添加足迹, userId:', userId, 'productId:', productId)
// 检查是否已存在
const checkRes = await supa
.from('ml_user_footprints')
.select('id')
.eq('user_id', userId!)
.eq('product_id', productId)
.execute()
console.log('[addFootprint] 检查结果 error:', checkRes.error)
console.log('[addFootprint] 检查结果 data:', JSON.stringify(checkRes.data))
const checkData = checkRes.data as any[]
const exists = checkData != null && Array.isArray(checkData) && checkData.length > 0
if (checkRes.error == null && exists) {
console.log('[addFootprint] 足迹已存在,更新时间')
// 更新时间
const updateRes = await supa
.from('ml_user_footprints')
.update({ updated_at: new Date().toISOString() })
.eq('user_id', userId!)
.eq('product_id', productId)
.execute()
console.log('[addFootprint] 更新结果 error:', updateRes.error)
} else {
console.log('[addFootprint] 足迹不存在,插入新记录')
// 插入新记录
const insertPayload = new UTSJSONObject()
insertPayload.set('user_id', userId!)
insertPayload.set('product_id', productId)
insertPayload.set('created_at', new Date().toISOString())
insertPayload.set('updated_at', new Date().toISOString())
const insertRes = await supa
.from('ml_user_footprints')
.insert(insertPayload)
.execute()
console.log('[addFootprint] 插入结果 error:', insertRes.error)
}
return true
} catch (e) {
console.error('[addFootprint] 添加足迹异常:', e)
return false
}
}
// 删除单个足迹
async deleteFootprint(productId: string): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.log('[deleteFootprint] 用户未登录')
return false
}
const response = await supa
.from('ml_user_footprints')
.eq('user_id', userId)
.eq('product_id', productId)
.delete()
.execute()
if (response.error != null) {
console.error('[deleteFootprint] 删除足迹失败:', response.error)
return false
}
console.log('[deleteFootprint] 删除足迹成功')
return true
} catch (e) {
console.error('[deleteFootprint] 删除足迹异常:', e)
return false
}
}
// 批量删除足迹
async deleteFootprints(productIds: string[]): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.log('[deleteFootprints] 用户未登录')
return false
}
const idsAny: any[] = []
for (let i = 0; i < productIds.length; i++) {
idsAny.push(productIds[i])
}
const response = await supa
.from('ml_user_footprints')
.eq('user_id', userId)
.in('product_id', idsAny)
.delete()
.execute()
if (response.error != null) {
console.error('[deleteFootprints] 批量删除足迹失败:', response.error)
return false
}
console.log('[deleteFootprints] 批量删除足迹成功')
return true
} catch (e) {
console.error('[deleteFootprints] 批量删除足迹异常:', e)
return false
}
}
// 清空所有足迹
async clearFootprints(): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.log('[clearFootprints] 用户未登录')
return false
}
const response = await supa
.from('ml_user_footprints')
.eq('user_id', userId)
.delete()
.execute()
if (response.error != null) {
console.error('[clearFootprints] 清空足迹失败:', response.error)
return false
}
console.log('[clearFootprints] 清空足迹成功')
return true
} catch (e) {
console.error('[clearFootprints] 清空足迹异常:', e)
return false
}
}
async getAddressList(): Promise<UserAddress[]> {
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<boolean> {
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<UserCoupon[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: UserCoupon[] = []
return empty
}
// 假设有一个视图或者直接关联 ml_user_coupons 和 ml_coupon_templates
// 这里简化处理,尝试直接从 ml_user_coupons 读取,并且加入 template 信息
// 如果没有 view可能需要改为两个查询或者使用 left join
const response = await supa
.from('ml_user_coupons')
.select('*, template:ml_coupon_templates(name, amount, min_spend)')
.eq('user_id', userId!)
.eq('status', status.toString())
.order('expire_at', { ascending: true })
.execute()
if (response.error != null) {
console.error('获取优惠券失败:', response.error)
const empty: UserCoupon[] = []
return empty
}
// 安全处理返回数据 - 安卓端可能是 UTSJSONObject 或 UTSArray
const rawData: any[] = []
const respData = response.data
console.log('[getUserCoupons] 原始数据类型:', typeof respData, '是否数组:', Array.isArray(respData))
if (respData != null) {
if (Array.isArray(respData)) {
const arr = respData as any[]
console.log('[getUserCoupons] 数组长度:', arr.length)
for (let i = 0; i < arr.length; i++) {
rawData.push(arr[i])
}
} else if (respData instanceof UTSJSONObject) {
// 单个对象情况,包装成数组
console.log('[getUserCoupons] 单个对象,包装成数组')
rawData.push(respData)
} else {
// 尝试 JSON 转换
try {
const parsed = JSON.parse(JSON.stringify(respData))
console.log('[getUserCoupons] JSON转换后是否数组:', Array.isArray(parsed))
if (Array.isArray(parsed)) {
console.log('[getUserCoupons] 转换后数组长度:', parsed.length)
for (let i = 0; i < parsed.length; i++) {
rawData.push(parsed[i])
}
}
} catch (parseErr) {
console.error('解析优惠券数据异常:', parseErr)
}
}
}
console.log('[getUserCoupons] 最终rawData长度:', rawData.length)
// 映射数据,将 template 的字段展平
const coupons: UserCoupon[] = []
for (let i = 0; i < rawData.length; i++) {
const item = rawData[i]
let template: any | null = null
let itemId = ''
let itemUserId = ''
let itemTmplId = ''
let itemCode = ''
let itemStatus = 0
let itemRecv = ''
let itemExpire = ''
if (item instanceof UTSJSONObject) {
template = item.get('template') as any | null
itemId = item.getString('id') ?? ''
itemUserId = item.getString('user_id') ?? ''
itemTmplId = item.getString('template_id') ?? ''
itemCode = item.getString('coupon_code') ?? ''
itemStatus = item.getNumber('status') ?? 0
itemRecv = item.getString('received_at') ?? ''
itemExpire = item.getString('expire_at') ?? ''
} else {
const iObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
template = iObj.get('template') as any | null
itemId = iObj.getString('id') ?? ''
itemUserId = iObj.getString('user_id') ?? ''
itemTmplId = iObj.getString('template_id') ?? ''
itemCode = iObj.getString('coupon_code') ?? ''
itemStatus = iObj.getNumber('status') ?? 0
itemRecv = iObj.getString('received_at') ?? ''
itemExpire = iObj.getString('expire_at') ?? ''
}
if (template == null) template = new UTSJSONObject()
let tName = ''
let tAmount = 0
let tMin = 0
if (template instanceof UTSJSONObject) {
tName = template.getString('name') ?? '优惠券'
tAmount = template.getNumber('amount') ?? 0
tMin = template.getNumber('min_spend') ?? 0
} else {
const tObj = JSON.parse(JSON.stringify(template)) as UTSJSONObject
tName = tObj.getString('name') ?? '优惠券'
tAmount = tObj.getNumber('amount') ?? 0
tMin = tObj.getNumber('min_spend') ?? 0
}
// 创建真正的 UserCoupon 对象,而不是 UTSJSONObject
const couponItem: UserCoupon = {
id: itemId,
user_id: itemUserId,
template_id: itemTmplId,
coupon_code: itemCode,
status: itemStatus,
received_at: itemRecv,
expire_at: itemExpire,
template_name: tName,
amount: tAmount,
min_spend: tMin
}
coupons.push(couponItem)
}
return coupons
} catch (e) {
console.error('获取优惠券异常:', e)
const empty: UserCoupon[] = []
return empty
}
}
// 获取可用优惠券数量
async getUserCouponCount(): Promise<number> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return 0
const response = await supa
.from('ml_user_coupons')
.select('id', { count: 'exact' })
.eq('user_id', userId!)
.eq('status', '1')
.gt('expire_at', new Date().toISOString())
.limit(1)
.execute()
if (response.error != null) {
return 0
}
return response.total ?? 0
} catch (e) {
return 0
}
}
// 获取店铺/商品可用优惠券
async getAvailableCoupons(merchantId: string): Promise<any[]> {
return this.fetchShopCoupons(merchantId)
}
// ALIAS for Cache busting: 获取店铺优惠券
async fetchShopCoupons(merchantId: string): Promise<any[]> {
try {
console.log('[fetchShopCoupons] 开始获取优惠券merchantId:', merchantId)
// 查询该商家的优惠券 + 平台通用券 (merchant_id is null)
// 注意:这里简化逻辑,实际可能需要联合查询用户是否已领取
const response = await supa
.from('ml_coupon_templates')
.select('*')
.or(`merchant_id.eq.${merchantId},merchant_id.is.null`)
.eq('status', '1') // 使用字符串 '1'
.gt('end_time', new Date().toISOString())
.order('discount_value', { ascending: false })
.execute()
if (response.error != null) {
console.error('Fetch coupons failed:', response.error)
const empty: any[] = []
return empty
}
const data = response.data
if (data == null) {
const empty: any[] = []
return empty
}
console.log('[fetchShopCoupons] 获取到优惠券数量:', (data as any[]).length)
return data as any[]
} catch (e) {
console.error('Fetch coupons error:', e)
const empty: any[] = []
return empty
}
}
// 领取优惠券
async claimCoupon(templateId: string, userId: string): Promise<boolean> {
return this.claimShopCoupon(templateId, userId)
}
// ALIAS for Cache busting
async claimShopCoupon(templateId: string, userId: string): Promise<boolean> {
try {
console.log('Claiming coupon templateId:', templateId, 'userId:', userId)
// 1. Fetch template details to get merchant_id and validity
const tmplRes = await supa
.from('ml_coupon_templates')
.select('*')
.eq('id', templateId)
.limit(1)
.execute()
if (tmplRes.error != null) {
console.error('Claim Coupon: Template query error', tmplRes.error)
return false
}
// Null check for data
if (tmplRes.data == null) {
console.error('Claim Coupon: Template data response is null')
return false
}
const dataList = tmplRes.data as any[]
if (dataList.length === 0) {
console.error('Claim Coupon: Template not found (empty list)')
return false
}
const template = dataList[0]
// Safe property access
let validDays = 0
let endTimeStr: string | null = null
let merchantId: string | null = null
if (template instanceof UTSJSONObject) {
validDays = template.getNumber('valid_days') ?? 0
endTimeStr = template.getString('end_time')
merchantId = template.getString('merchant_id')
} else {
const tJson = JSON.parse(JSON.stringify(template)) as UTSJSONObject
validDays = tJson.getNumber('valid_days') ?? 0
endTimeStr = tJson.getString('end_time')
merchantId = tJson.getString('merchant_id')
}
// Calculate expire_at
let expireAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()
if (validDays > 0) {
expireAt = new Date(Date.now() + (validDays * 24 * 60 * 60 * 1000)).toISOString()
} else if (endTimeStr != null && endTimeStr !== '') {
expireAt = endTimeStr
}
// Handle UUID fields: Empty string is not valid UUID, must be null
if (merchantId != null && merchantId.length === 0) {
merchantId = null
}
// 2. Insert into user coupons with merchant_id
const insertData = {
user_id: userId,
template_id: templateId,
merchant_id: merchantId, // Important for shop filtering: null for platform coupons
coupon_code: 'C' + Date.now() + Math.floor(Math.random() * 1000),
status: 1,
expire_at: expireAt,
received_at: new Date().toISOString()
}
console.log('Claim Coupon Insert Payload:', JSON.stringify(insertData))
const response = await supa
.from('ml_user_coupons')
.insert(insertData)
.execute()
if (response.error != null) {
console.error('Claim Coupon: Insert failed:', JSON.stringify(response.error))
// 尝试降级:如果 merchant_id 报错,尝试不带 merchant_id (仅调试用,或兼容旧表结构)
if (JSON.stringify(response.error).includes('merchant_id')) {
console.log('Retrying without merchant_id...')
const fallbackData = {
user_id: userId,
template_id: templateId,
coupon_code: 'C' + Date.now() + Math.random().toString().substring(2,6),
status: 1,
expire_at: expireAt,
received_at: new Date().toISOString()
}
const res2 = await supa.from('ml_user_coupons').insert(fallbackData).execute()
if (res2.error == null) return true
}
return false
}
return true
} catch(e) {
console.error('Claim coupon error:', e)
return false
}
}
// ==========================================
// 聊天相关方法
// ==========================================
// 发送消息
async sendMessage(merchantId: string, content: string, msgType: string = 'text'): Promise<boolean> {
// 确保 session 有效
const userId = this.getCurrentUserId()
if (userId == null) {
console.error("sendMessage failed: user not logged in or session lost")
return false
}
try {
// Debug check
// const session = supa.getSession()
// console.log("Sending check: UserID", userId, "AuthID:", session.user?.getString('id'))
const msg = {
sender_id: userId!,
receiver_id: merchantId,
content: content,
msg_type: msgType,
is_read: false,
is_from_user: true
}
const response = await supa
.from('ml_chat_messages')
.insert(msg)
.execute()
if (response.error != null) {
console.error('sendMessage error:', response.error)
return false
}
return true
} catch (e) {
console.error('sendMessage exception:', e)
return false
}
}
// 上传聊天图片
async uploadChatImage(filePath: string): Promise<string> {
const userId = this.getCurrentUserId()
if (userId == null) {
console.error("uploadChatImage failed: user not logged in")
return ''
}
try {
// 生成唯一文件名
const timestamp = Date.now()
const randomStr = Math.random().toString(36).substring(2, 8)
const fileName = `chat_${userId}_${timestamp}_${randomStr}.jpg`
const storagePath = `chat-images/${fileName}`
console.log('[uploadChatImage] 开始上传:', filePath, '->', storagePath)
const response = await supa.storage
.from('chat')
.upload(storagePath, filePath, {})
if (response.error != null) {
console.error('[uploadChatImage] 上传失败:', response.error)
return ''
}
// 构建公开访问URL
const publicUrl = `${supa.baseUrl}/storage/v1/object/public/chat/${storagePath}`
console.log('[uploadChatImage] 上传成功:', publicUrl)
return publicUrl
} catch (e) {
console.error('[uploadChatImage] 上传异常:', e)
return ''
}
}
// 标记会话已读
async markRead(merchantId: string): Promise<boolean> {
const userId = this.getCurrentUserId()
if (userId == null) return false
try {
const response = await supa
.from('ml_chat_messages')
.update({ is_read: true })
.eq('sender_id', merchantId)
.eq('receiver_id', userId)
.eq('is_read', false)
.execute()
if (response.error != null) return false
} catch (e) { return false }
return true
}
// 提交商品评价
async submitProductReviews(reviews: Array<UTSJSONObject>): Promise<boolean> {
try {
for (let i: number = 0; i < reviews.length; i++) {
const review = reviews[i]
const response = await supa
.from('ml_product_reviews')
.insert(review)
.execute()
if (response.error != null) {
console.error('提交商品评价失败:', response.error)
return false
}
}
return true
} catch (e) {
console.error('提交商品评价失败:', e)
return false
}
}
// 提交店铺评价
async submitShopReview(review: UTSJSONObject): Promise<boolean> {
try {
const response = await supa
.from('ml_shop_reviews')
.insert(review)
.execute()
return response.error == null
} catch (e) {
console.error('提交店铺评价失败:', e)
return false
}
}
// 更新订单状态
async updateOrderStatus(orderId: string, status: number): Promise<boolean> {
try {
const updateData = new UTSJSONObject()
updateData.set('order_status', status)
const response = await supa
.from('ml_orders')
.update(updateData)
.eq('id', orderId)
.execute()
return response.error == null
} catch (e) {
console.error('更新订单状态失败:', e)
return false
}
}
// ==================== 智能推荐相关API ====================
// 获取热搜词(全站搜索频率最高的关键词)
async getHotKeywords(limit: number = 10): Promise<string[]> {
try {
const response = await supa
.from('ml_search_history')
.select('keyword')
.order('created_at', { ascending: false })
.limit(100)
.execute()
if (response.error != null || response.data == null) {
return [] as string[]
}
// 统计关键词频率
const keywordCount = new Map<string, number>()
const rawList = response.data as any[]
for (let i = 0; i < rawList.length; i++) {
const item = rawList[i]
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
const keyword = safeGetString(itemObj, 'keyword').toLowerCase().trim()
if (keyword.length > 0) {
const count = keywordCount.get(keyword) ?? 0
keywordCount.set(keyword, count + 1)
}
}
// 按频率排序并返回前N个 - UTS兼容方式
// 将Map转换为数组进行排序
type KeywordEntry = {
keyword: string
count: number
}
const entryArray: KeywordEntry[] = []
// 使用forEach遍历MapUTS支持
keywordCount.forEach((value: number, key: string) => {
entryArray.push({
keyword: key,
count: value
})
})
// 按count降序排序
entryArray.sort((a: KeywordEntry, b: KeywordEntry): number => {
return b.count - a.count
})
// 取前limit个并提取关键词
const sortedKeywords: string[] = []
const maxCount = Math.min(entryArray.length, limit)
for (let i = 0; i < maxCount; i++) {
sortedKeywords.push(entryArray[i].keyword)
}
return sortedKeywords
} catch (e) {
console.error('获取热搜词失败:', e)
return [] as string[]
}
}
// 获取用户搜索历史
async getUserSearchHistory(limit: number = 10): Promise<string[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
return [] as string[]
}
const response = await supa
.from('ml_search_history')
.select('keyword')
.order('created_at', { ascending: false })
.limit(limit * 2)
.execute()
if (response.error != null || response.data == null) {
return [] as string[]
}
const keywords: string[] = []
const rawList = response.data as any[]
const seen = new Set<string>()
for (let i = 0; i < rawList.length; i++) {
const item = rawList[i]
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
const rawUserId = itemObj.get('user_id')
const itemUserId = (typeof rawUserId == 'string') ? (rawUserId as string) : ''
// 只获取当前用户的搜索历史
if (itemUserId !== userId) continue
const keyword = safeGetString(itemObj, 'keyword').trim()
if (keyword.length > 0 && !seen.has(keyword)) {
keywords.push(keyword)
seen.add(keyword)
if (keywords.length >= limit) break
}
}
return keywords
} catch (e) {
console.error('获取用户搜索历史失败:', e)
return [] as string[]
}
}
// 获取用户浏览历史中的商品分类
async getUserBrowseCategories(limit: number = 5): Promise<string[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
return [] as string[]
}
const response = await supa
.from('ml_browse_history')
.select('product_id')
.order('created_at', { ascending: false })
.limit(20)
.execute()
if (response.error != null || response.data == null) {
return [] as string[]
}
// 获取浏览过的商品ID
const productIds: string[] = []
const rawList = response.data as any[]
for (let i = 0; i < rawList.length; i++) {
const item = rawList[i]
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
// 手动过滤 user_id
const rawUserId = itemObj.get('user_id')
const itemUserId = (typeof rawUserId == 'string') ? (rawUserId as string) : ''
if (itemUserId !== userId) continue
const productId = safeGetString(itemObj, 'product_id')
if (productId.length > 0) {
productIds.push(productId)
}
}
if (productIds.length === 0) {
return [] as string[]
}
// 查询这些商品的分类
const prodResponse = await supa
.from('ml_products')
.select('category_id')
.limit(50)
.execute()
if (prodResponse.error != null || prodResponse.data == null) {
return [] as string[]
}
const categoryIds: string[] = []
const prodList = prodResponse.data as any[]
for (let i = 0; i < prodList.length; i++) {
const prodItem = prodList[i]
const prodObj = JSON.parse(JSON.stringify(prodItem)) as UTSJSONObject
const prodId = safeGetString(prodObj, 'id')
// 只统计浏览过的商品
let found = false
for (let j = 0; j < productIds.length; j++) {
if (productIds[j] == prodId) {
found = true
break
}
}
if (!found) continue
const catId = safeGetString(prodObj, 'category_id')
if (catId.length > 0 && categoryIds.indexOf(catId) < 0) {
categoryIds.push(catId)
if (categoryIds.length >= limit) break
}
}
return categoryIds
} catch (e) {
console.error('获取用户浏览分类失败:', e)
return [] as string[]
}
}
// 智能推荐:综合用户搜索历史、浏览历史、热销商品
async getSmartRecommendations(limit: number = 10): Promise<Product[]> {
try {
console.log('[getSmartRecommendations] 开始获取智能推荐...')
const products: Product[] = []
const addedIds = new Set<string>()
// 1. 根据用户搜索历史推荐商品(权重最高)
const searchHistory = await this.getUserSearchHistory(5)
console.log('[getSmartRecommendations] 用户搜索历史:', searchHistory)
if (searchHistory.length > 0) {
// 根据搜索关键词查找商品
const keywordProducts = await this.searchProductsByKeywords(searchHistory, limit)
for (let i = 0; i < keywordProducts.length; i++) {
const prod = keywordProducts[i]
if (!addedIds.has(prod.id)) {
products.push(prod)
addedIds.add(prod.id)
}
}
}
// 2. 根据用户浏览历史推荐相似分类商品
if (products.length < limit) {
const browseCategories = await this.getUserBrowseCategories(3)
console.log('[getSmartRecommendations] 用户浏览分类:', browseCategories)
if (browseCategories.length > 0) {
const categoryProducts = await this.getProductsByCategories(browseCategories, limit - products.length)
for (let i = 0; i < categoryProducts.length; i++) {
const prod = categoryProducts[i]
if (!addedIds.has(prod.id)) {
products.push(prod)
addedIds.add(prod.id)
}
}
}
}
// 3. 补充热销商品
if (products.length < limit) {
const hotProducts = await this.getHotProducts(limit - products.length + 5)
for (let i = 0; i < hotProducts.length; i++) {
const prod = hotProducts[i]
if (!addedIds.has(prod.id)) {
products.push(prod)
addedIds.add(prod.id)
if (products.length >= limit) break
}
}
}
// 4. 如果还不够,用普通商品补充
if (products.length < limit) {
const moreProducts = await this.getProductsByPrice(limit - products.length + 5, false)
for (let i = 0; i < moreProducts.length; i++) {
const prod = moreProducts[i]
if (!addedIds.has(prod.id)) {
products.push(prod)
addedIds.add(prod.id)
if (products.length >= limit) break
}
}
}
console.log('[getSmartRecommendations] 返回商品数量:', products.length)
return products.slice(0, limit)
} catch (e) {
console.error('获取智能推荐失败:', e)
return [] as Product[]
}
}
// 根据关键词列表搜索商品
async searchProductsByKeywords(keywords: string[], limit: number): Promise<Product[]> {
try {
const response = await supa
.from('ml_products_detail_view')
.select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')
.order('sale_count', { ascending: false })
.limit(limit * 2)
.execute()
if (response.error != null || response.data == null) {
return [] as Product[]
}
const products: Product[] = []
const rawList = response.data as any[]
for (let i = 0; i < rawList.length; i++) {
const item = rawList[i]
const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
// 手动过滤 status
const rawStatus = prodObj.get('status')
let statusNum: number = 0
if (typeof rawStatus == 'number') {
statusNum = rawStatus as number
}
if (statusNum !== 1) continue
// 检查是否匹配任何关键词
const name = safeGetString(prodObj, 'name').toLowerCase()
const desc = safeGetString(prodObj, 'description').toLowerCase()
let matched = false
for (let j = 0; j < keywords.length; j++) {
const keyword = keywords[j].toLowerCase()
if (name.indexOf(keyword) >= 0 || desc.indexOf(keyword) >= 0) {
matched = true
break
}
}
if (!matched) continue
products.push(parseProductFromRaw(item))
if (products.length >= limit) break
}
return products
} catch (e) {
console.error('根据关键词搜索商品失败:', e)
return [] as Product[]
}
}
// 根据分类列表获取商品
async getProductsByCategories(categoryIds: string[], limit: number): Promise<Product[]> {
try {
const response = await supa
.from('ml_products_detail_view')
.select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')
.order('sale_count', { ascending: false })
.limit(limit * 2)
.execute()
if (response.error != null || response.data == null) {
return [] as Product[]
}
const products: Product[] = []
const rawList = response.data as any[]
for (let i = 0; i < rawList.length; i++) {
const item = rawList[i]
const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
// 手动过滤 status
const rawStatus = prodObj.get('status')
let statusNum: number = 0
if (typeof rawStatus == 'number') {
statusNum = rawStatus as number
}
if (statusNum !== 1) continue
// 手动过滤 category_id
const rawCatId = prodObj.get('category_id')
const itemCatId = (typeof rawCatId == 'string') ? (rawCatId as string) : ''
let matched = false
for (let j = 0; j < categoryIds.length; j++) {
if (itemCatId == categoryIds[j]) {
matched = true
break
}
}
if (!matched) continue
products.push(parseProductFromRaw(item))
if (products.length >= limit) break
}
return products
} catch (e) {
console.error('根据分类获取商品失败:', e)
return [] as Product[]
}
}
// 记录用户搜索行为
async recordSearch(keyword: string, resultCount: number): Promise<void> {
try {
const userId = this.getCurrentUserId()
const searchRecord = new UTSJSONObject()
searchRecord.set('keyword', keyword)
searchRecord.set('result_count', resultCount)
if (userId != null) {
searchRecord.set('user_id', userId)
}
await supa
.from('ml_search_history')
.insert(searchRecord)
.execute()
} catch (e) {
console.error('记录搜索失败:', e)
}
}
// 记录用户浏览行为
async recordBrowse(productId: string, duration: number = 0): Promise<void> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return
const browseRecord = new UTSJSONObject()
browseRecord.set('user_id', userId)
browseRecord.set('product_id', productId)
browseRecord.set('browse_duration', duration)
browseRecord.set('created_at', new Date().toISOString())
// UTS Android不支持upsert使用insert
await supa
.from('ml_browse_history')
.insert(browseRecord)
.execute()
} catch (e) {
console.error('记录浏览失败:', e)
}
}
// ==================== 签到相关API ====================
// 用户签到
async signin(): Promise<UTSJSONObject> {
const result = new UTSJSONObject()
result.set('success', false)
result.set('points', 0)
result.set('continuous_days', 0)
result.set('bonus_points', 0)
result.set('total_points', 0)
result.set('message', '')
try {
const userId = this.getCurrentUserId()
if (userId == null) {
result.set('message', '请先登录')
return result
}
const today = new Date()
const todayStr = today.toISOString().split('T')[0]
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000)
const yesterdayStr = yesterday.toISOString().split('T')[0]
// 检查今天是否已签到
const checkRes = await supa
.from('ml_signin_records')
.select('*')
.eq('user_id', userId!)
.eq('signin_date', todayStr)
.execute()
if (checkRes.error != null) {
result.set('message', '查询签到状态失败')
return result
}
const checkData = checkRes.data as any[]
if (checkData != null && checkData.length > 0) {
result.set('message', '今天已签到')
return result
}
// 查询昨天是否签到,计算连续天数
const yesterdayRes = await supa
.from('ml_signin_records')
.select('continuous_days')
.eq('user_id', userId!)
.eq('signin_date', yesterdayStr)
.execute()
let continuousDays = 1
if (yesterdayRes.error == null && yesterdayRes.data != null) {
const yData = yesterdayRes.data as any[]
if (yData.length > 0) {
const yItem = yData[0]
let yDays = 0
if (yItem instanceof UTSJSONObject) {
yDays = yItem.getNumber('continuous_days') ?? 0
} else {
const yObj = JSON.parse(JSON.stringify(yItem)) as UTSJSONObject
yDays = yObj.getNumber('continuous_days') ?? 0
}
continuousDays = yDays + 1
}
}
// 计算积分
let pointsEarned = 5 // 每日签到基础积分
let bonusPoints = 0
if (continuousDays >= 30) {
bonusPoints = 100
} else if (continuousDays >= 7) {
bonusPoints = 20
}
const totalPointsEarned = pointsEarned + bonusPoints
// 插入签到记录
const signinRecord = new UTSJSONObject()
signinRecord.set('user_id', userId!)
signinRecord.set('signin_date', todayStr)
signinRecord.set('points_earned', pointsEarned)
signinRecord.set('bonus_points', bonusPoints)
signinRecord.set('continuous_days', continuousDays)
const insertRes = await supa
.from('ml_signin_records')
.insert(signinRecord)
.execute()
if (insertRes.error != null) {
result.set('message', '签到失败')
return result
}
// 更新用户积分
await this.addPoints(userId!, totalPointsEarned, 'signin', '每日签到')
// 获取最新积分
const newPoints = await this.getUserPoints()
result.set('success', true)
result.set('points', pointsEarned)
result.set('continuous_days', continuousDays)
result.set('bonus_points', bonusPoints)
result.set('total_points', newPoints)
result.set('message', '签到成功')
return result
} catch (e) {
console.error('签到异常:', e)
result.set('message', '签到异常')
return result
}
}
// 获取签到记录(当月)
async getSigninRecords(year: number, month: number): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: any[] = []
return empty
}
const startDate = `${year}-${month.toString().padStart(2, '0')}-01`
const endDate = month === 12
? `${year + 1}-01-01`
: `${year}-${(month + 1).toString().padStart(2, '0')}-01`
const response = await supa
.from('ml_signin_records')
.select('*')
.eq('user_id', userId!)
.gte('signin_date', startDate)
.lt('signin_date', endDate)
.order('signin_date', { ascending: true })
.execute()
if (response.error != null || response.data == null) {
const empty: any[] = []
return empty
}
return response.data as any[]
} catch (e) {
console.error('获取签到记录失败:', e)
const empty: any[] = []
return empty
}
}
// 获取今日签到状态
async getTodaySigninStatus(): Promise<UTSJSONObject> {
const result = new UTSJSONObject()
result.set('signed', false)
result.set('continuous_days', 0)
try {
const userId = this.getCurrentUserId()
if (userId == null) return result
const today = new Date().toISOString().split('T')[0]
// 检查今天是否签到
const todayRes = await supa
.from('ml_signin_records')
.select('*')
.eq('user_id', userId!)
.eq('signin_date', today)
.execute()
if (todayRes.error == null && todayRes.data != null) {
const tData = todayRes.data as any[]
if (tData.length > 0) {
const tItem = tData[0]
let cDays = 0
if (tItem instanceof UTSJSONObject) {
cDays = tItem.getNumber('continuous_days') ?? 0
} else {
const tObj = JSON.parse(JSON.stringify(tItem)) as UTSJSONObject
cDays = tObj.getNumber('continuous_days') ?? 0
}
result.set('signed', true)
result.set('continuous_days', cDays)
return result
}
}
// 今天未签到,获取最近的连续签到天数
const lastRes = await supa
.from('ml_signin_records')
.select('continuous_days, signin_date')
.eq('user_id', userId!)
.order('signin_date', { ascending: false })
.limit(1)
.execute()
if (lastRes.error == null && lastRes.data != null) {
const lData = lastRes.data as any[]
if (lData.length > 0) {
const lItem = lData[0]
let lastDate = ''
let lastDays = 0
if (lItem instanceof UTSJSONObject) {
lastDate = lItem.getString('signin_date') ?? ''
lastDays = lItem.getNumber('continuous_days') ?? 0
} else {
const lObj = JSON.parse(JSON.stringify(lItem)) as UTSJSONObject
lastDate = lObj.getString('signin_date') ?? ''
lastDays = lObj.getNumber('continuous_days') ?? 0
}
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().split('T')[0]
if (lastDate === yesterday) {
result.set('continuous_days', lastDays)
}
}
}
return result
} catch (e) {
console.error('获取签到状态失败:', e)
return result
}
}
// ==================== 积分兑换相关API ====================
// 获取积分兑换商品列表
async getPointProducts(): Promise<any[]> {
try {
const response = await supa
.from('ml_point_products')
.select('*')
.eq('status', 1)
.gt('stock', 0)
.order('sort_order', { ascending: true })
.execute()
if (response.error != null || response.data == null) {
const empty: any[] = []
return empty
}
return response.data as any[]
} catch (e) {
console.error('获取积分商品失败:', e)
const empty: any[] = []
return empty
}
}
// 积分兑换
async exchangeProduct(productId: string, quantity: number, addressSnapshot: UTSJSONObject | null): Promise<UTSJSONObject> {
const result = new UTSJSONObject()
result.set('success', false)
result.set('message', '')
try {
const userId = this.getCurrentUserId()
if (userId == null) {
result.set('message', '请先登录')
return result
}
// 获取商品信息
const productRes = await supa
.from('ml_point_products')
.select('*')
.eq('id', productId)
.single()
.execute()
if (productRes.error != null || productRes.data == null) {
result.set('message', '商品不存在')
return result
}
const productRaw = productRes.data
let pointsRequired = 0
let stock = 0
let productType = ''
// 检查是否是数组,如果是则取第一个元素
let productObj: UTSJSONObject | null = null
if (Array.isArray(productRaw)) {
const arr = productRaw as any[]
if (arr.length > 0) {
const firstItem = arr[0]
if (firstItem instanceof UTSJSONObject) {
productObj = firstItem
} else {
productObj = JSON.parse(JSON.stringify(firstItem)) as UTSJSONObject
}
}
} else {
if (productRaw instanceof UTSJSONObject) {
productObj = productRaw
} else {
productObj = JSON.parse(JSON.stringify(productRaw)) as UTSJSONObject
}
}
// 使用 UTSJSONObject 方法访问属性
if (productObj != null) {
pointsRequired = productObj.getNumber('points_required') ?? 0
stock = productObj.getNumber('stock') ?? 0
productType = productObj.getString('product_type') ?? ''
}
const totalPoints = pointsRequired * quantity
// 检查库存
if (stock < quantity) {
result.set('message', '库存不足')
return result
}
// 检查积分
const userPoints = await this.getUserPoints()
if (userPoints < totalPoints) {
result.set('message', '积分不足')
return result
}
// 创建兑换记录
const exchangeRecord = new UTSJSONObject()
exchangeRecord.set('user_id', userId!)
exchangeRecord.set('product_id', productId)
exchangeRecord.set('quantity', quantity)
exchangeRecord.set('points_used', totalPoints)
exchangeRecord.set('status', 0)
if (addressSnapshot != null && productType === 'physical') {
exchangeRecord.set('address_snapshot', JSON.stringify(addressSnapshot))
}
const insertRes = await supa
.from('ml_point_exchanges')
.insert(exchangeRecord)
.execute()
if (insertRes.error != null) {
console.error('[exchangeProduct] 创建兑换记录失败:', insertRes.error)
result.set('message', '兑换失败')
return result
}
console.log('[exchangeProduct] 兑换记录创建成功')
// 扣减库存
console.log('[exchangeProduct] 准备扣减库存')
console.log('[exchangeProduct] productId 类型:', typeof productId)
console.log('[exchangeProduct] productId 值:', productId)
console.log('[exchangeProduct] 当前库存:', stock, ', 扣减数量:', quantity)
// 使用 UTSJSONObject 替代 Record<string, any>
const stockUpdateData = new UTSJSONObject()
stockUpdateData.set('stock', stock - quantity)
console.log('[exchangeProduct] stockUpdateData:', stockUpdateData)
console.log('[exchangeProduct] stockUpdateData 类型:', typeof stockUpdateData)
// 先查询确认商品存在
const checkProduct = await supa
.from('ml_point_products')
.select('id, stock')
.eq('id', productId)
.execute()
console.log('[exchangeProduct] 查询商品结果:', checkProduct.data, 'error:', checkProduct.error)
const stockUpdateRes = await supa
.from('ml_point_products')
.update(stockUpdateData)
.eq('id', productId)
.execute()
console.log('[exchangeProduct] 库存更新结果 error:', stockUpdateRes.error)
console.log('[exchangeProduct] 库存更新结果 data:', stockUpdateRes.data)
if (stockUpdateRes.error != null) {
console.error('[exchangeProduct] 扣减库存失败:', stockUpdateRes.error)
}
// 扣减积分
console.log('[exchangeProduct] 准备扣减积分, userId:', userId, ', 积分:', totalPoints)
const deductResult = await this.deductPoints(userId!, totalPoints, 'redeem', '积分兑换商品')
console.log('[exchangeProduct] 积分扣减结果:', deductResult)
if (!deductResult) {
console.error('[exchangeProduct] 扣减积分失败')
}
console.log('[exchangeProduct] 兑换流程完成')
result.set('success', true)
result.set('message', '兑换成功')
return result
} catch (e) {
console.error('积分兑换异常:', e)
result.set('message', '兑换异常')
return result
}
}
// 获取兑换记录
async getExchangeRecords(): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: any[] = []
return empty
}
const response = await supa
.from('ml_point_exchanges')
.select('*, product:ml_point_products(name, image_url, product_type)')
.eq('user_id', userId!)
.order('created_at', { ascending: false })
.execute()
if (response.error != null || response.data == null) {
const empty: any[] = []
return empty
}
return response.data as any[]
} catch (e) {
console.error('获取兑换记录失败:', e)
const empty: any[] = []
return empty
}
}
// ==================== 评价相关API ====================
// 获取商品评价列表
async getProductReviews(productId: string, page: number = 1, limit: number = 10, rating: number = 0, hasImage: boolean = false): Promise<UTSJSONObject> {
const result = new UTSJSONObject()
result.set('total', 0)
result.set('page', page)
result.set('limit', limit)
result.set('data', [] as any[])
try {
const userId = this.getCurrentUserId()
let query = supa
.from('ml_product_reviews')
.select('*, user:auth.users!ml_product_reviews_user_id_fkey(raw_user_meta_data)', { count: 'exact' })
.eq('product_id', productId)
if (rating > 0) {
query = query.eq('rating', rating)
}
if (hasImage) {
query = query.neq('images', '[]')
}
const offset = (page - 1) * limit
const response = await query
.order('created_at', { ascending: false })
.range(offset, offset + limit - 1)
.execute()
if (response.error != null) {
console.error('获取评价列表失败:', response.error)
return result
}
const total = response.total ?? 0
const reviews = response.data as any[]
// 处理评价数据
const processedReviews: any[] = []
for (let i = 0; i < reviews.length; i++) {
const review = reviews[i]
const processed = JSON.parse(JSON.stringify(review)) as UTSJSONObject
// 处理用户信息
const userRaw = processed.get('user')
let userName = '匿名用户'
let userAvatar = ''
if (userRaw != null) {
let userData: UTSJSONObject
if (userRaw instanceof UTSJSONObject) {
userData = userRaw as UTSJSONObject
} else {
userData = JSON.parse(JSON.stringify(userRaw)) as UTSJSONObject
}
const metaData = userData.get('raw_user_meta_data')
if (metaData != null) {
let metaObj: UTSJSONObject
if (metaData instanceof UTSJSONObject) {
metaObj = metaData as UTSJSONObject
} else {
metaObj = JSON.parse(JSON.stringify(metaData)) as UTSJSONObject
}
userName = metaObj.getString('nickname') ?? metaObj.getString('name') ?? '匿名用户'
userAvatar = metaObj.getString('avatar_url') ?? ''
}
}
// 检查是否匿名
const isAnonymous = processed.getBoolean('is_anonymous') ?? false
if (isAnonymous) {
userName = '匿名用户'
userAvatar = ''
}
processed.set('user_name', userName)
processed.set('user_avatar', userAvatar)
// 检查当前用户是否点赞
let isLiked = false
if (userId != null) {
const likeRes = await supa
.from('ml_review_likes')
.select('id')
.eq('review_id', processed.getString('id') ?? '')
.eq('user_id', userId!)
.limit(1)
.execute()
if (likeRes.error == null && likeRes.data != null) {
const likeData = likeRes.data as any[]
isLiked = likeData.length > 0
}
}
processed.set('is_liked', isLiked)
processedReviews.push(processed)
}
result.set('total', total)
result.set('data', processedReviews)
return result
} catch (e) {
console.error('获取评价列表异常:', e)
return result
}
}
// 获取商品评价统计
async getReviewStats(productId: string): Promise<UTSJSONObject> {
const result = new UTSJSONObject()
result.set('total_count', 0)
result.set('avg_rating', 0)
result.set('good_rate', 0)
result.set('rating_distribution', new UTSJSONObject())
result.set('tags', [] as any[])
try {
const response = await supa
.from('ml_product_reviews')
.select('rating')
.eq('product_id', productId)
.execute()
if (response.error != null || response.data == null) {
return result
}
const reviews = response.data as any[]
const totalCount = reviews.length
if (totalCount === 0) return result
let totalRating = 0
let goodCount = 0
const distribution: Map<number, number> = new Map()
distribution.set(1, 0)
distribution.set(2, 0)
distribution.set(3, 0)
distribution.set(4, 0)
distribution.set(5, 0)
for (let i = 0; i < reviews.length; i++) {
const review = reviews[i]
let rating = 0
if (review instanceof UTSJSONObject) {
rating = review.getNumber('rating') ?? 0
} else {
const rObj = JSON.parse(JSON.stringify(review)) as UTSJSONObject
rating = rObj.getNumber('rating') ?? 0
}
totalRating += rating
if (rating >= 4) goodCount++
const currentCount = distribution.get(rating) ?? 0
distribution.set(rating, currentCount + 1)
}
const avgRating = Math.round((totalRating / totalCount) * 10) / 10
const goodRate = Math.round((goodCount / totalCount) * 100)
const distObj = new UTSJSONObject()
distribution.forEach((value: number, key: number) => {
distObj.set(key.toString(), value)
})
result.set('total_count', totalCount)
result.set('avg_rating', avgRating)
result.set('good_rate', goodRate)
result.set('rating_distribution', distObj)
return result
} catch (e) {
console.error('获取评价统计异常:', e)
return result
}
}
// 评价点赞
async toggleReviewLike(reviewId: string): Promise<UTSJSONObject> {
const result = new UTSJSONObject()
result.set('success', false)
result.set('is_liked', false)
result.set('like_count', 0)
try {
const userId = this.getCurrentUserId()
if (userId == null) {
return result
}
// 检查是否已点赞
const checkRes = await supa
.from('ml_review_likes')
.select('id')
.eq('review_id', reviewId)
.eq('user_id', userId!)
.limit(1)
.execute()
let isLiked = false
if (checkRes.error == null && checkRes.data != null) {
const checkData = checkRes.data as any[]
isLiked = checkData.length > 0
}
if (isLiked) {
// 取消点赞
await supa
.from('ml_review_likes')
.eq('review_id', reviewId)
.eq('user_id', userId!)
.delete()
.execute()
// 更新点赞数 - 直接查询并更新
const currentCountRes = await supa
.from('ml_product_reviews')
.select('like_count')
.eq('id', reviewId)
.single()
.execute()
if (currentCountRes.error == null && currentCountRes.data != null) {
let currentCount = 0
if (currentCountRes.data instanceof UTSJSONObject) {
currentCount = currentCountRes.data.getNumber('like_count') ?? 0
} else {
const countObj = JSON.parse(JSON.stringify(currentCountRes.data)) as UTSJSONObject
currentCount = countObj.getNumber('like_count') ?? 0
}
const updateData = new UTSJSONObject()
updateData.set('like_count', Math.max(0, currentCount - 1))
await supa
.from('ml_product_reviews')
.update(updateData)
.eq('id', reviewId)
.execute()
}
result.set('is_liked', false)
} else {
// 添加点赞
const likeRecord = new UTSJSONObject()
likeRecord.set('review_id', reviewId)
likeRecord.set('user_id', userId!)
await supa
.from('ml_review_likes')
.insert(likeRecord)
.execute()
// 更新点赞数 - 直接查询并更新
const currentCountRes = await supa
.from('ml_product_reviews')
.select('like_count')
.eq('id', reviewId)
.single()
.execute()
if (currentCountRes.error == null && currentCountRes.data != null) {
let currentCount = 0
if (currentCountRes.data instanceof UTSJSONObject) {
currentCount = currentCountRes.data.getNumber('like_count') ?? 0
} else {
const countObj = JSON.parse(JSON.stringify(currentCountRes.data)) as UTSJSONObject
currentCount = countObj.getNumber('like_count') ?? 0
}
const updateData = new UTSJSONObject()
updateData.set('like_count', currentCount + 1)
await supa
.from('ml_product_reviews')
.update(updateData)
.eq('id', reviewId)
.execute()
}
result.set('is_liked', true)
}
// 获取最新点赞数
const reviewRes = await supa
.from('ml_product_reviews')
.select('like_count')
.eq('id', reviewId)
.single()
.execute()
if (reviewRes.error == null && reviewRes.data != null) {
let likeCount = 0
if (reviewRes.data instanceof UTSJSONObject) {
likeCount = reviewRes.data.getNumber('like_count') ?? 0
} else {
const rObj = JSON.parse(JSON.stringify(reviewRes.data)) as UTSJSONObject
likeCount = rObj.getNumber('like_count') ?? 0
}
result.set('like_count', likeCount)
}
result.set('success', true)
return result
} catch (e) {
console.error('评价点赞异常:', e)
return result
}
}
// 获取我的评价列表
async getMyReviews(): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: any[] = []
return empty
}
const response = await supa
.from('ml_product_reviews')
.select(`
*,
product:ml_products!ml_product_reviews_product_id_fkey(name, main_image_url)
`)
.eq('user_id', userId!)
.order('created_at', { ascending: false })
.execute()
if (response.error != null || response.data == null) {
const empty: any[] = []
return empty
}
const reviews = response.data as any[]
const result: any[] = []
for (let i = 0; i < reviews.length; i++) {
const review = reviews[i]
const processed = JSON.parse(JSON.stringify(review)) as UTSJSONObject
// 处理商品信息
const productRaw = processed.get('product')
let productName = ''
let productImage = ''
if (productRaw != null) {
let productObj: UTSJSONObject
if (productRaw instanceof UTSJSONObject) {
productObj = productRaw as UTSJSONObject
} else {
productObj = JSON.parse(JSON.stringify(productRaw)) as UTSJSONObject
}
productName = productObj.getString('name') ?? ''
productImage = productObj.getString('main_image_url') ?? ''
}
processed.set('product_name', productName)
processed.set('product_image', productImage)
// 计算是否可追加评价7天内
const createdAt = processed.getString('created_at') ?? ''
const createdTime = new Date(createdAt).getTime()
const now = Date.now()
const sevenDays = 7 * 24 * 60 * 60 * 1000
const canAppend = (now - createdTime) < sevenDays && (processed.getString('append_content') ?? '') === ''
processed.set('can_append', canAppend)
// 计算是否可编辑24小时内
const oneDay = 24 * 60 * 60 * 1000
const canEdit = (now - createdTime) < oneDay
processed.set('can_edit', canEdit)
result.push(processed)
}
return result
} catch (e) {
console.error('获取我的评价失败:', e)
const empty: any[] = []
return empty
}
}
// 追加评价
async appendReview(reviewId: string, content: string, images: string[]): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return false
const updateData = new UTSJSONObject()
updateData.set('append_content', content)
updateData.set('append_images', JSON.stringify(images))
updateData.set('append_at', new Date().toISOString())
const response = await supa
.from('ml_product_reviews')
.update(updateData)
.eq('id', reviewId)
.eq('user_id', userId!)
.execute()
return response.error == null
} catch (e) {
console.error('追加评价失败:', e)
return false
}
}
// 删除评价
async deleteReview(reviewId: string): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return false
const response = await supa
.from('ml_product_reviews')
.delete()
.eq('id', reviewId)
.eq('user_id', userId!)
.execute()
return response.error == null
} catch (e) {
console.error('删除评价失败:', e)
return false
}
}
// ==================== 积分辅助方法 ====================
// 增加积分
private async addPoints(userId: string, points: number, type: string, description: string): Promise<boolean> {
try {
// 获取当前积分
const currentPoints = await this.getUserPoints()
const newPoints = currentPoints + points
const totalEarned = await this.getTotalEarned()
// 检查用户积分记录是否存在
const checkRes = await supa
.from('ml_user_points')
.select('user_id')
.eq('user_id', userId)
.limit(1)
.execute()
const exists = checkRes.error == null && checkRes.data != null && (checkRes.data as any[]).length > 0
if (exists) {
// 更新现有记录
const updateData = new UTSJSONObject()
updateData.set('points', newPoints)
updateData.set('total_earned', totalEarned + points)
updateData.set('updated_at', new Date().toISOString())
await supa
.from('ml_user_points')
.update(updateData)
.eq('user_id', userId)
.execute()
} else {
// 插入新记录
const insertData = new UTSJSONObject()
insertData.set('user_id', userId)
insertData.set('points', newPoints)
insertData.set('total_earned', points)
insertData.set('updated_at', new Date().toISOString())
await supa
.from('ml_user_points')
.insert(insertData)
.execute()
}
// 记录积分变动
const record = new UTSJSONObject()
record.set('user_id', userId)
record.set('points', points)
record.set('type', type)
record.set('description', description)
await supa
.from('ml_point_records')
.insert(record)
.execute()
return true
} catch (e) {
console.error('增加积分失败:', e)
return false
}
}
// 扣减积分
private async deductPoints(userId: string, points: number, type: string, description: string): Promise<boolean> {
try {
const currentPoints = await this.getUserPoints()
const newPoints = currentPoints - points
if (newPoints < 0) return false
const updateData = new UTSJSONObject()
updateData.set('points', newPoints)
updateData.set('updated_at', new Date().toISOString())
await supa
.from('ml_user_points')
.update(updateData)
.eq('user_id', userId)
.execute()
const record = new UTSJSONObject()
record.set('user_id', userId)
record.set('points', -points)
record.set('type', type)
record.set('description', description)
await supa
.from('ml_point_records')
.insert(record)
.execute()
return true
} catch (e) {
console.error('扣减积分失败:', e)
return false
}
}
// 获取历史累计积分
private async getTotalEarned(): Promise<number> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return 0
const res = await supa
.from('ml_user_points')
.select('total_earned')
.eq('user_id', userId!)
.single()
.execute()
if (res.error == null && res.data != null) {
if (res.data instanceof UTSJSONObject) {
return res.data.getNumber('total_earned') ?? 0
} else {
const obj = JSON.parse(JSON.stringify(res.data)) as UTSJSONObject
return obj.getNumber('total_earned') ?? 0
}
}
return 0
} catch (e) {
return 0
}
}
// ==================== 积分过期相关API ====================
// 获取即将过期积分
async getExpiringPoints(): Promise<UTSJSONObject> {
const result = new UTSJSONObject()
result.set('expiring_points', 0)
result.set('expiring_date', null)
result.set('details', [] as any[])
try {
const userId = this.getCurrentUserId()
if (userId == null) return result
// 查询30天内即将过期的积分记录
const now = new Date()
const thirtyDaysLater = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000)
const nowStr = now.toISOString()
const laterStr = thirtyDaysLater.toISOString()
const res = await supa
.from('ml_point_records')
.select('points, description, expires_at, created_at')
.eq('user_id', userId!)
.gt('points', 0)
.eq('is_expired', false)
.not('expires_at', 'is', null)
.gte('expires_at', nowStr)
.lte('expires_at', laterStr)
.order('expires_at', { ascending: true })
.execute()
if (res.error != null) {
console.error('获取即将过期积分失败:', res.error)
return result
}
if (res.data != null && Array.isArray(res.data)) {
const records = res.data as any[]
let totalExpiring = 0
let earliestDate: string | null = null
const details: any[] = []
for (let i = 0; i < records.length; i++) {
const record = records[i]
let recordObj: UTSJSONObject
if (record instanceof UTSJSONObject) {
recordObj = record
} else {
recordObj = JSON.parse(JSON.stringify(record)) as UTSJSONObject
}
const points = recordObj.getNumber('points') ?? 0
const expiresAt = recordObj.getString('expires_at') ?? ''
totalExpiring += points
if (earliestDate == null || expiresAt < earliestDate) {
earliestDate = expiresAt
}
details.push({
points: points,
description: recordObj.getString('description'),
expires_at: expiresAt,
created_at: recordObj.getString('created_at') ?? ''
})
}
result.set('expiring_points', totalExpiring)
result.set('expiring_date', earliestDate != null ? earliestDate.split('T')[0] : null)
result.set('details', details)
}
return result
} catch (e) {
console.error('获取即将过期积分异常:', e)
return result
}
}
// 获取积分概览(包含即将过期积分)
async getPointsOverview(): Promise<UTSJSONObject> {
const result = new UTSJSONObject()
result.set('current_points', 0)
result.set('total_earned', 0)
result.set('expiring_points', 0)
result.set('expiring_date', null)
try {
const userId = this.getCurrentUserId()
if (userId == null) return result
const res = await supa
.from('ml_user_points')
.select('points, total_earned, expiring_points, expiring_date')
.eq('user_id', userId!)
.single()
.execute()
if (res.error == null && res.data != null) {
let data: UTSJSONObject
if (res.data instanceof UTSJSONObject) {
data = res.data as UTSJSONObject
} else {
data = JSON.parse(JSON.stringify(res.data)) as UTSJSONObject
}
result.set('current_points', data.getNumber('points') ?? 0)
result.set('total_earned', data.getNumber('total_earned') ?? 0)
result.set('expiring_points', data.getNumber('expiring_points') ?? 0)
result.set('expiring_date', data.getString('expiring_date'))
}
return result
} catch (e) {
console.error('获取积分概览异常:', e)
return result
}
}
// 获取过期提醒通知列表
async getExpiryNotifications(): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: any[] = []
return empty
}
const res = await supa
.from('ml_point_expiry_notifications')
.select('*')
.eq('user_id', userId!)
.eq('is_sent', false)
.order('expiry_date', { ascending: true })
.execute()
if (res.error != null || res.data == null) {
const empty: any[] = []
return empty
}
return res.data as any[]
} catch (e) {
console.error('获取过期提醒失败:', e)
const empty: any[] = []
return empty
}
}
// 标记过期提醒为已读
async markNotificationRead(notificationId: string): Promise<boolean> {
try {
const res = await supa
.from('ml_point_expiry_notifications')
.update({ is_sent: true, sent_at: new Date().toISOString() })
.eq('id', notificationId)
.execute()
return res.error == null
} catch (e) {
console.error('标记通知失败:', e)
return false
}
}
// 手动触发积分维护任务(管理员功能)
// 注意UTS不支持rpc此功能需要在Supabase后台手动执行或通过其他方式触发
async triggerPointsMaintenance(): Promise<boolean> {
console.warn('triggerPointsMaintenance: UTS不支持rpc调用请在Supabase后台手动执行 daily_points_maintenance()')
return false
}
// ==================== 推销模式 - 商家配置API ====================
// 获取商家推销配置
async getMerchantPromotionConfig(merchantId: string): Promise<UTSJSONObject> {
const result = new UTSJSONObject()
result.set('promotion_enabled', false)
result.set('share_free_enabled', false)
result.set('distribution_enabled', false)
result.set('required_count', 4)
result.set('reward_type', 'product_price')
result.set('fixed_reward_amount', 0)
try {
const res = await supa
.from('ml_merchant_promotion_config')
.select('*')
.eq('merchant_id', merchantId)
.limit(1)
.execute()
if (res.error == null && res.data != null && Array.isArray(res.data)) {
const arr = res.data as any[]
if (arr.length > 0) {
const item = arr[0]
const itemAny = item as any
if (itemAny instanceof UTSJSONObject) {
result.set('promotion_enabled', itemAny.getBoolean('promotion_enabled') ?? false)
result.set('share_free_enabled', itemAny.getBoolean('share_free_enabled') ?? false)
result.set('distribution_enabled', itemAny.getBoolean('distribution_enabled') ?? false)
result.set('required_count', itemAny.getNumber('required_count') ?? 4)
result.set('reward_type', itemAny.getString('reward_type') ?? 'product_price')
result.set('fixed_reward_amount', itemAny.getNumber('fixed_reward_amount') ?? 0)
}
}
}
} catch (e) {
console.error('获取商家推销配置失败:', e)
}
return result
}
// 检查商家是否开启分享免单
async isShareFreeEnabled(merchantId: string): Promise<boolean> {
try {
const config = await this.getMerchantPromotionConfig(merchantId)
const promotionEnabled = config.get('promotion_enabled')
const shareFreeEnabled = config.get('share_free_enabled')
return (promotionEnabled === true || promotionEnabled === 'true') &&
(shareFreeEnabled === true || shareFreeEnabled === 'true')
} catch (e) {
console.error('检查分享免单状态失败:', e)
return false
}
}
// ==================== 推销模式 - 余额相关API ====================
// 获取用户余额
async getUserBalance(): Promise<UTSJSONObject> {
const result = new UTSJSONObject()
result.set('balance', 0)
result.set('frozen_balance', 0)
result.set('total_earned', 0)
result.set('total_withdrawn', 0)
try {
const userId = this.getCurrentUserId()
if (userId == null) return result
const res = await supa
.from('ml_user_balance')
.select('*')
.eq('user_id', userId!)
.limit(1)
.execute()
if (res.error == null && res.data != null && Array.isArray(res.data)) {
const arr = res.data as any[]
if (arr.length > 0) {
const item = arr[0]
const itemAny = item as any
if (itemAny instanceof UTSJSONObject) {
result.set('balance', itemAny.getNumber('balance') ?? 0)
result.set('frozen_balance', itemAny.getNumber('frozen_balance') ?? 0)
result.set('total_earned', itemAny.getNumber('total_earned') ?? 0)
result.set('total_withdrawn', itemAny.getNumber('total_withdrawn') ?? 0)
}
}
}
return result
} catch (e) {
console.error('获取用户余额失败:', e)
return result
}
}
// 获取余额变动记录
async getBalanceRecords(page: number = 1, limit: number = 20): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: any[] = []
return empty
}
const offset = (page - 1) * limit
const res = await supa
.from('ml_balance_records')
.select('*')
.eq('user_id', userId!)
.order('created_at', { ascending: false })
.range(offset, offset + limit - 1)
.execute()
if (res.error != null || res.data == null) {
const empty: any[] = []
return empty
}
return res.data as any[]
} catch (e) {
console.error('获取余额记录失败:', e)
const empty: any[] = []
return empty
}
}
// ==================== 推销模式 - 分享免单相关API ====================
// 创建分享记录
async createShareRecord(productId: string, orderId: string, orderItemId: string | null, productName: string, productImage: string | null, productPrice: number): Promise<UTSJSONObject> {
const result = new UTSJSONObject()
result.set('success', false)
result.set('share_code', '')
result.set('message', '')
try {
const userId = this.getCurrentUserId()
if (userId == null) {
result.set('message', '请先登录')
return result
}
// 生成分享码
const shareCode = this.generateShareCode()
const insertData = new UTSJSONObject()
insertData.set('user_id', userId)
insertData.set('product_id', productId)
insertData.set('order_id', orderId)
insertData.set('order_item_id', orderItemId)
insertData.set('share_code', shareCode)
insertData.set('product_name', productName)
insertData.set('product_image', productImage)
insertData.set('product_price', productPrice)
insertData.set('required_count', 4)
insertData.set('current_count', 0)
insertData.set('status', 0)
const res = await supa
.from('ml_share_records')
.insert(insertData)
.execute()
if (res.error != null) {
console.error('[createShareRecord] 插入失败:', res.error)
console.error('[createShareRecord] 插入数据:', JSON.stringify(insertData))
result.set('message', '创建分享记录失败: ' + (res.error.message ?? '未知错误'))
return result
}
// 获取插入记录的id
let insertedId = ''
if (res.data != null && Array.isArray(res.data) && res.data.length > 0) {
const inserted = res.data[0]
let insertedObj: UTSJSONObject | null = null
if (inserted instanceof UTSJSONObject) {
insertedObj = inserted
} else {
insertedObj = JSON.parse(JSON.stringify(inserted)) as UTSJSONObject
}
insertedId = insertedObj.getString('id') ?? ''
}
result.set('success', true)
result.set('id', insertedId)
result.set('share_code', shareCode)
result.set('message', '分享创建成功')
return result
} catch (e) {
console.error('创建分享记录失败:', e)
result.set('message', '创建分享记录异常')
return result
}
}
// 生成分享码
private generateShareCode(): string {
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'
let result = ''
for (let i = 0; i < 8; i++) {
const randomIndex = Math.floor(Math.random() * chars.length)
result += chars.charAt(randomIndex)
}
return result
}
// 验证分享码
async validateShareCode(shareCode: string): Promise<UTSJSONObject> {
const result = new UTSJSONObject()
result.set('valid', false)
result.set('share_record', null)
try {
const res = await supa
.from('ml_share_records')
.select('*')
.eq('share_code', shareCode)
.eq('status', 0)
.limit(1)
.execute()
if (res.error == null && res.data != null && Array.isArray(res.data)) {
const arr = res.data as any[]
if (arr.length > 0) {
result.set('valid', true)
result.set('share_record', arr[0])
}
}
return result
} catch (e) {
console.error('验证分享码失败:', e)
return result
}
}
// 获取我的分享记录
async getMyShareRecords(): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: any[] = []
return empty
}
const res = await supa
.from('ml_share_records')
.select('*')
.eq('user_id', userId!)
.order('created_at', { ascending: false })
.execute()
if (res.error != null || res.data == null) {
const empty: any[] = []
return empty
}
return res.data as any[]
} catch (e) {
console.error('获取分享记录失败:', e)
const empty: any[] = []
return empty
}
}
// 获取分享详情
async getShareDetail(shareId: string): Promise<UTSJSONObject> {
const result = new UTSJSONObject()
result.set('share_record', null)
result.set('secondary_purchases', [] as any[])
try {
const res = await supa
.from('ml_share_records')
.select('*')
.eq('id', shareId)
.limit(1)
.execute()
if (res.error == null && res.data != null && Array.isArray(res.data)) {
const arr = res.data as any[]
if (arr.length > 0) {
result.set('share_record', arr[0])
}
}
// 获取二级购买记录
const purchasesRes = await supa
.from('ml_secondary_purchases')
.select('*')
.eq('share_record_id', shareId)
.order('created_at', { ascending: false })
.execute()
if (purchasesRes.error == null && purchasesRes.data != null) {
result.set('secondary_purchases', purchasesRes.data)
}
return result
} catch (e) {
console.error('获取分享详情失败:', e)
return result
}
}
// 获取免单奖励记录
async getFreeOrderRewards(): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: any[] = []
return empty
}
const res = await supa
.from('ml_free_order_rewards')
.select('*')
.eq('user_id', userId!)
.order('created_at', { ascending: false })
.execute()
if (res.error != null || res.data == null) {
const empty: any[] = []
return empty
}
return res.data as any[]
} catch (e) {
console.error('获取免单奖励记录失败:', e)
const empty: any[] = []
return empty
}
}
// ==================== 推销模式 - 会员等级相关API ====================
// 获取会员等级列表
async getMemberLevels(): Promise<any[]> {
try {
const res = await supa
.from('ml_member_levels')
.select('*')
.eq('is_active', true)
.order('level_rank', { ascending: true } as OrderOptions)
.execute()
if (res.error != null || res.data == null) {
const empty: any[] = []
return empty
}
return res.data as any[]
} catch (e) {
console.error('获取会员等级列表失败:', e)
const empty: any[] = []
return empty
}
}
// 获取用户会员信息
async getUserMemberInfo(): Promise<UTSJSONObject> {
const result = new UTSJSONObject()
result.set('member_level', 0)
result.set('level_name', '普通会员')
result.set('discount', 1.0)
result.set('total_spent', 0)
result.set('next_level', null)
result.set('progress_percent', 0)
result.set('manual_level', false)
try {
const userId = this.getCurrentUserId()
if (userId == null) return result
// 获取用户信息(包括 tier_id
const userRes = await supa
.from('ml_user_profiles')
.select('tier_id, total_spent, manual_level')
.eq('user_id', userId!)
.limit(1)
.execute()
let tierId: string = ''
let totalSpent = 0
let manualLevel = false
if (userRes.error == null && userRes.data != null && Array.isArray(userRes.data)) {
const arr = userRes.data as any[]
if (arr.length > 0) {
const item = arr[0]
const itemAny = item as any
if (itemAny instanceof UTSJSONObject) {
tierId = itemAny.getString('tier_id') ?? ''
totalSpent = itemAny.getNumber('total_spent') ?? 0
manualLevel = itemAny.getBoolean('manual_level') ?? false
}
}
}
// 获取等级信息
const levels = await this.getMemberLevels()
let levelName = '普通会员'
let discount = 1.0
let nextLevel: UTSJSONObject | null = null
let progressPercent = 0
let currentLevelRank = 0
// 通过 tier_id 匹配等级
for (let i = 0; i < levels.length; i++) {
const level = levels[i]
const levelAny = level as any
let levelId = ''
let levelNameStr = ''
let levelRank = 0
let levelDiscount = 1.0
if (levelAny instanceof UTSJSONObject) {
levelId = levelAny.getString('id') ?? ''
levelNameStr = levelAny.getString('name') ?? ''
levelRank = levelAny.getNumber('level_rank') ?? 0
levelDiscount = levelAny.getNumber('discount_rate') ?? 1.0
}
// 通过 tier_id 匹配当前等级
if (levelId == tierId) {
levelName = levelNameStr
discount = levelDiscount
currentLevelRank = levelRank
}
}
// 找下一等级level_rank 更大的第一个等级)
for (let i = 0; i < levels.length; i++) {
const level = levels[i]
const levelAny = level as any
let levelRank = 0
let levelNameStr = ''
let levelMinAmount = 0
if (levelAny instanceof UTSJSONObject) {
levelRank = levelAny.getNumber('level_rank') ?? 0
levelNameStr = levelAny.getString('name') ?? ''
levelMinAmount = levelAny.getNumber('min_amount') ?? 0
}
if (levelRank > currentLevelRank && nextLevel == null) {
const nextLevelObj = new UTSJSONObject()
const levelObj = level as UTSJSONObject
nextLevelObj.set('id', levelObj.getString('id') ?? '')
nextLevelObj.set('name', levelNameStr)
nextLevelObj.set('min_amount', levelMinAmount)
nextLevel = nextLevelObj
}
}
result.set('member_level', currentLevelRank)
result.set('level_name', levelName)
result.set('discount', discount)
result.set('total_spent', totalSpent)
result.set('next_level', nextLevel)
result.set('progress_percent', progressPercent)
result.set('manual_level', manualLevel)
return result
} catch (e) {
console.error('获取用户会员信息失败:', e)
return result
}
}
// 获取当前等级的最低消费金额
private getCurrentLevelMinAmount(levels: any[], currentLevel: number): number {
for (let i = 0; i < levels.length; i++) {
const level = levels[i]
const levelAny = level as any
if (levelAny instanceof UTSJSONObject) {
const levelId = levelAny.getNumber('id') ?? 0
if (levelId === currentLevel) {
return levelAny.getNumber('min_amount') ?? 0
}
}
}
return 0
}
// 获取会员等级变更记录
async getMemberLevelLogs(): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: any[] = []
return empty
}
const res = await supa
.from('ml_member_level_logs')
.select('*')
.eq('user_id', userId!)
.order('created_at', { ascending: false })
.execute()
if (res.error != null || res.data == null) {
const empty: any[] = []
return empty
}
return res.data as any[]
} catch (e) {
console.error('获取会员等级变更记录失败:', e)
const empty: any[] = []
return empty
}
}
}
// 导出单例实例
export const supabaseService = new SupabaseService()
// 默认导出
export default supabaseService