Files
medical-mall/mall/utils/supabaseService.uts

2867 lines
86 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'
// 使用单例 Supabase 客户端
// const supa = createClient(SUPA_URL, SUPA_KEY)
// 类型定义
export interface Brand {
id: string
name: string
logo_url: string
description: string
}
export interface Category {
id: string
name: string
icon: string
description: string
color: string
created_at?: string
}
export interface Product {
id: string
category_id: string
merchant_id: string
name: string
subtitle?: string
description?: string
base_price: number
market_price?: number
cost_price?: number
main_image_url?: string
image_urls?: string // JSON string array
video_urls?: string // JSON string array
sale_count?: number
view_count?: number
total_stock?: number
available_stock?: number
is_hot?: boolean
is_new?: boolean
is_featured?: boolean
status?: number
rating_avg?: number
rating_count?: number
tags?: string // array string in DB
attributes?: string // JSON string
created_at?: string
updated_at?: string
// Alias fields for compatibility
price?: number
original_price?: number
stock?: number
sales?: number
images?: string
cover?: string
// View fields
brand_name?: string
category_name?: string
shop_name?: string
merchant_name?: string
}
export interface 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 interface 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 interface 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
created_at?: string
updated_at?: string
}
export interface 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 interface 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 interface 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 interface PaginatedResponse<T> {
data: T[]
total: number
page: number
limit: number
hasmore: boolean
}
export interface 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
}
class SupabaseService {
// 获取当前用户ID
public getCurrentUserId(): string | null {
try {
// 优先从 Supabase 会话获取
const session = supa.getSession()
if (session && session.user) {
return session.user.getString('id')
}
// 后备:尝试从本地存储获取 (兼容旧逻辑)
const userId = uni.getStorageSync('user_id')
return userId ? userId as string : null
} catch (e) {
console.error('获取用户ID失败:', e)
return null
}
}
// 获取所有分类
async getCategories(): Promise<Category[]> {
try {
const response = await supa
.from('ml_categories')
.select('*')
.order('name', { ascending: true })
.execute()
if (response.error) {
console.error('获取分类失败:', response.error)
return []
}
return response.data as Category[]
} catch (error) {
console.error('获取分类异常:', error)
return []
}
}
// 获取所有品牌
async getBrands(): Promise<Brand[]> {
try {
const response = await supa
.from('ml_brands')
.select('*')
.eq('is_active', true)
.order('name', { ascending: true })
.execute()
if (response.error) {
console.error('获取品牌失败:', response.error)
return []
}
return response.data as Brand[]
} catch (error) {
console.error('获取品牌异常:', error)
return []
}
}
// 获取指定分类的商品
async getProductsByCategory(
categoryId: string,
page: number = 1,
limit: number = 20
): Promise<PaginatedResponse<Product>> {
try {
const response = await supa
.from('ml_products_detail_view')
.select('*', { count: 'exact' })
.eq('category_id', categoryId)
.eq('status', 1)
.order('sale_count', { ascending: false })
.page(page)
.limit(limit)
.execute()
if (response.error) {
console.error('获取商品失败:', response.error)
return {
data: [],
total: 0,
page,
limit,
hasmore: false
}
}
return {
data: response.data as Product[],
total: response.total || 0,
page,
limit,
hasmore: response.hasmore || false
}
} catch (error) {
console.error('获取商品异常:', error)
return {
data: [],
total: 0,
page,
limit,
hasmore: false
}
}
}
// 根据商品ID获取SKU列表
async getProductSkus(productId: string): Promise<ProductSku[]> {
try {
const response = await supa
.from('ml_product_skus')
.select('*')
.eq('product_id', productId)
.eq('status', 1)
.execute()
if (response.error) {
console.error('获取商品SKU失败:', response.error)
return []
}
return response.data as ProductSku[]
} catch (error) {
console.error('获取商品SKU异常:', error)
return []
}
}
// 搜索商品
async searchProducts(
keyword: string,
page: number = 1,
limit: number = 20,
sortBy: string = 'sales',
ascending: boolean = false
): Promise<PaginatedResponse<Product>> {
try {
let query = supa
.from('ml_products_detail_view')
.select('*', { count: 'exact' })
.eq('status', 1)
.or(`name.ilike.%${keyword}%,description.ilike.%${keyword}%,subtitle.ilike.%${keyword}%,brand_name.ilike.%${keyword}%`)
// 根据sortBy和ascending设置排序
if (sortBy === 'price') {
query = query.order('base_price', { ascending })
} else if (sortBy === 'sales' || sortBy === 'sale_count') {
query = query.order('sale_count', { ascending: false }) // 销量总是降序
} else {
// 默认按销量降序
query = query.order('sale_count', { ascending: false })
}
const response = await query
.page(page)
.limit(limit)
.execute()
if (response.error) {
console.error('搜索商品失败:', response.error)
return {
data: [],
total: 0,
page,
limit,
hasmore: false
}
}
return {
data: response.data as Product[],
total: response.total || 0,
page,
limit,
hasmore: response.hasmore || false
}
} catch (error) {
console.error('搜索商品异常:', error)
return {
data: [],
total: 0,
page,
limit,
hasmore: false
}
}
}
// 获取单个商品详情
async getProductById(productId: string): Promise<Product | null> {
try {
const response = await supa
.from('ml_products_detail_view')
.select('*')
.eq('id', productId)
.single()
.executeAs<Product>()
if (response.error) {
console.error('获取商品详情失败:', response.error)
return null
}
return response.data as Product
} catch (error) {
console.error('获取商品详情异常:', error)
return null
}
}
// 根据商户ID获取店铺信息
async getShopByMerchantId(merchantId: string): Promise<Shop | null> {
try {
const response = await supa
.from('ml_shops')
.select('*')
.eq('merchant_id', merchantId)
.single()
.executeAs<Shop>()
if (response.error) {
console.error('获取店铺信息失败:', response.error)
return null
}
const data = response.data
if (Array.isArray(data)) {
if (data.length > 0) return data[0] as Shop
return null
}
return data as Shop
} catch (error) {
console.error('获取店铺信息异常:', error)
return null
}
}
// 根据商户ID获取商品列表
async getProductsByMerchantId(merchantId: string, page: number = 1, limit: number = 20): Promise<PaginatedResponse<Product>> {
try {
const response = await supa
.from('ml_products')
.select('*', { count: 'exact' })
.eq('merchant_id', merchantId)
.order('created_at', { ascending: false })
.page(page)
.limit(limit)
.execute()
if (response.error) {
console.error('获取商户商品失败:', response.error)
return {
data: [],
total: 0,
page,
limit,
hasmore: false
}
}
return {
data: response.data as Product[],
total: response.total || 0,
page,
limit,
hasmore: response.hasmore || false
}
} catch (error) {
console.error('获取商户商品异常:', error)
return {
data: [],
total: 0,
page,
limit,
hasmore: false
}
}
}
// 获取热销商品(按销量排序)
async getHotProducts(limit: number = 10): Promise<Product[]> {
try {
const response = await supa
.from('ml_products_detail_view')
.select('*')
.eq('is_hot', true)
.eq('status', 1)
.order('sale_count', { ascending: false })
.limit(limit)
.execute()
if (response.error) {
console.error('获取热销商品失败:', response.error)
return []
}
return response.data as Product[]
} catch (error) {
console.error('获取热销商品异常:', error)
return []
}
}
// 获取按价格排序的商品(升序:从低到高)
async getProductsByPrice(limit: number = 10, ascending: boolean = true): Promise<Product[]> {
try {
const response = await supa
.from('ml_products_detail_view')
.select('*')
.eq('status', 1)
.order('base_price', { ascending })
.limit(limit)
.execute()
if (response.error) {
console.error('获取价格排序商品失败:', response.error)
return []
}
return response.data as Product[]
} catch (error) {
console.error('获取价格排序商品异常:', error)
return []
}
}
// 获取新品(按创建时间排序,最新的在前)
async getProductsByNewest(limit: number = 10): Promise<Product[]> {
try {
const response = await supa
.from('ml_products_detail_view')
.select('*')
.eq('is_new', true)
.eq('status', 1)
.order('published_at', { ascending: false }) // Use published_at for newest
.limit(limit)
.execute()
if (response.error) {
console.error('获取新品失败:', response.error)
return []
}
return response.data as Product[]
} catch (error) {
console.error('获取新品异常:', error)
return []
}
}
// 获取推荐商品is_featured=true
async getRecommendedProducts(limit: number = 10): Promise<Product[]> {
try {
// 查询 is_featured = true 的商品
const response = await supa
.from('ml_products_detail_view')
.select('*')
.eq('is_featured', true)
.eq('status', 1)
.order('sale_count', { ascending: false })
.limit(limit)
.execute()
if (response.error) {
console.error('获取推荐商品失败:', response.error)
return []
}
console.log('推荐商品查询结果条数:', response.data?.length || 0)
return response.data as Product[] || []
} 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 [] // 暂无特价字段
}
// 获取当前用户的购物车商品(关联商品和店铺信息)
async getCartItems(): Promise<CartItem[]> {
try {
const userId = this.getCurrentUserId()
if (!userId) {
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) {
console.error('获取购物车失败:', response.error)
return []
}
const cartData = response.data as any[]
// console.log('Raw Cart Data:', JSON.stringify(cartData))
if (!cartData || 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 {
pid = (item['product_id'] as string) || ''
sid = (item['sku_id'] as string) || ''
}
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) {
const productRes = await supa
.from('ml_products_detail_view')
.select('id,name,main_image_url,base_price,attributes,merchant_id,shop_name')
.in('id', productIds)
.execute()
if (!productRes.error && productRes.data != null) {
const products = productRes.data as any[]
for (let i = 0; i < products.length; i++) {
let p = products[i]
let pid: string = ''
if (p instanceof UTSJSONObject) {
pid = p.getString('id') || ''
} else {
pid = (p['id'] as string) || ''
}
if (pid !== '') {
productMap.set(pid, p)
}
}
}
}
// 批量查询 SKU 详情
const skuMap = new Map<string, any>()
if (skuIds.length > 0) {
const skuRes = await supa
.from('ml_product_skus')
.select('id, specifications, price, image_url')
.in('id', skuIds)
.execute()
if (!skuRes.error && 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 {
sid = (s['id'] as string) || ''
}
if (sid !== '') {
skuMap.set(sid, s)
}
}
}
}
// 处理返回数据构建CartItem数组
const cartItems: CartItem[] = []
if (cartData && Array.isArray(cartData)) {
for (let i = 0; i < cartData.length; i++) {
let item = cartData[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 = ''
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') || ''
} else {
itemId = (item['id'] as string) || ''
userIdVal = (item['user_id'] as string) || ''
productId = (item['product_id'] as string) || ''
skuId = (item['sku_id'] as string) || ''
quantity = (item['quantity'] as number) || 0
selected = (item['selected'] as boolean) || false
createdAt = (item['created_at'] as string) || ''
updatedAt = (item['updated_at'] as string) || ''
}
const product = productMap.get(productId)
const sku = (skuId !== '' && skuMap.has(skuId)) ? skuMap.get(skuId) : null
let merchantId: string = ''
let productName: string = ''
let productImage: string = ''
let productPrice: number = 0
let productSpec: string = ''
let shopNameStr: string = '未知店铺'
if (product != null) {
if (product instanceof UTSJSONObject) {
merchantId = product.getString('merchant_id') || ''
productName = product.getString('name') || ''
productImage = product.getString('main_image_url') || ''
productPrice = product.getNumber('base_price') || 0
shopNameStr = product.getString('shop_name') || '未知店铺'
// 只有当没有sku信息时才尝试使用商品的attributes作为降级显示
// 或者如果业务需求是属性和规格都显示,可以修改这里
// 但通常SKU规格更具体。如果sku为空再显示product的attributes
if (sku == null) {
const specRaw = product.get('attributes')
if (specRaw != null) {
if (typeof specRaw === 'string') {
productSpec = specRaw
} else if (specRaw instanceof UTSJSONObject) {
const keys = UTSJSONObject.keys(specRaw)
const parts: string[] = []
for(let k = 0; k < keys.length; k++) {
let val = specRaw.get(keys[k])
if (val != null) {
parts.push(`${keys[k]}: ${val}`)
}
}
productSpec = parts.join('; ')
} else {
try {
let jsonStr = JSON.stringify(specRaw)
productSpec = jsonStr.replace(/["{}]/g, '').replace(/,/g, '; ')
} catch (e) {}
}
}
}
} else {
merchantId = (product['merchant_id'] as string) || ''
productName = (product['name'] as string) || ''
productImage = (product['main_image_url'] as string) || ''
productPrice = (product['base_price'] as number) || 0
shopNameStr = (product['shop_name'] as string) || '未知店铺'
if (sku == null) {
const specRaw = product['attributes']
if (specRaw != null) {
if (typeof specRaw === 'string') {
productSpec = specRaw
} else if (specRaw instanceof UTSJSONObject) {
const keys = UTSJSONObject.keys(specRaw)
const parts: string[] = []
for(let k = 0; k < keys.length; k++) {
let val = specRaw.get(keys[k])
if (val != null) {
parts.push(`${keys[k]}: ${val}`)
}
}
productSpec = parts.join('; ')
} else {
try {
let jsonStr = JSON.stringify(specRaw)
productSpec = jsonStr.replace(/["{}]/g, '').replace(/,/g, '; ')
} catch (e) {}
}
}
}
}
}
// 如果有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) {
// 优先使用SKU的规格
if (typeof specRaw === 'string') {
productSpec = specRaw
} else if (specRaw instanceof UTSJSONObject) {
const keys = UTSJSONObject.keys(specRaw)
const parts: string[] = []
for(let k = 0; k < keys.length; k++) {
let val = specRaw.get(keys[k])
if (val != null) {
parts.push(`${keys[k]}: ${val}`)
}
}
productSpec = parts.join('; ')
} else {
try {
let jsonStr = JSON.stringify(specRaw)
productSpec = jsonStr.replace(/["{}]/g, '').replace(/,/g, '; ')
} catch (e) {}
}
}
} else {
const skuPrice = sku['price'] as number
if (skuPrice > 0) productPrice = skuPrice
const skuImg = sku['image_url'] as string
if (skuImg && skuImg !== '') productImage = skuImg
const specRaw = sku['specifications']
if (specRaw != null) {
// 优先使用SKU的规格
if (typeof specRaw === 'string') {
productSpec = specRaw
} else if (specRaw instanceof UTSJSONObject) {
const keys = UTSJSONObject.keys(specRaw)
const parts: string[] = []
for(let k = 0; k < keys.length; k++) {
let val = specRaw.get(keys[k])
if (val != null) {
parts.push(`${keys[k]}: ${val}`)
}
}
productSpec = parts.join('; ')
} else {
try {
let jsonStr = JSON.stringify(specRaw)
productSpec = jsonStr.replace(/["{}]/g, '').replace(/,/g, '; ')
} catch (e) {}
}
}
}
}
let shopIdStr = merchantId || '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
})
}
}
return cartItems
} catch (error) {
console.error('获取购物车异常:', error)
return []
}
}
// 获取用户通知 (系统、活动、订单)
async getUserNotifications(type: string | null = null): Promise<Notification[]> {
try {
const userId = this.getCurrentUserId()
if (!userId) return []
let query = supa
.from('ml_notifications')
.select('*')
.eq('user_id', userId)
if (type) {
query = query.eq('type', type)
}
const response = await query.order('created_at', { ascending: false }).execute()
if (response.error) {
console.error('获取通知失败:', response.error)
return []
}
return response.data as Notification[]
} catch (e) {
console.error('获取通知异常:', e)
return []
}
}
// 获取用户聊天消息
async getUserChatMessages(): Promise<ChatMessage[]> {
try {
const userId = this.getCurrentUserId()
if (!userId) 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) {
console.error('获取聊天记录失败:', response.error)
return []
}
return response.data as ChatMessage[]
} catch (e) {
console.error('获取聊天记录异常:', e)
return []
}
}
// 获取与特定商家的聊天记录
async getChatMessages(merchantId: string): Promise<ChatMessage[]> {
try {
const userId = this.getCurrentUserId()
if (!userId) return []
const response = await supa
.from('ml_chat_messages')
.select('*')
.or(`and(sender_id.eq.${userId},receiver_id.eq.${merchantId}),and(sender_id.eq.${merchantId},receiver_id.eq.${userId})`)
.order('created_at', { ascending: false })
.limit(50)
.execute()
if (response.error) {
console.error('获取聊天记录失败:', response.error)
return []
}
return response.data as ChatMessage[]
} catch (e) {
console.error('获取聊天记录异常:', e)
return []
}
}
// 发送聊天消息
async sendChatMessage(content: string, toId: string | null = null, type: string = 'text'): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (!userId) return false
const payload : any = {
sender_id: userId,
content: content,
msg_type: type,
is_from_user: true,
created_at: new Date().toISOString()
}
if (toId != null) {
payload['receiver_id'] = toId
}
const response = await supa
.from('ml_chat_messages')
.insert(payload)
.execute()
if (response.error) {
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) 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
} catch (e) {
return false
}
}
// 添加商品到购物车
async addToCart(productId: string, quantity: number = 1, skuId?: string): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (!userId) {
console.error('用户未登录,无法添加商品到购物车')
return false
}
// 检查商品是否已在购物车中
// 注意:必须处理 sku_id 为空的情况,使用 is.null 过滤器
let query = supa
.from('ml_shopping_cart')
.select('*')
.eq('user_id', userId)
.eq('product_id', productId)
if (skuId && skuId.length > 0) {
query = query.eq('sku_id', skuId)
} 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
if (existingItem != null) {
// 商品已存在,更新数量
console.log('Found existing cart item:', JSON.stringify(existingItem))
// 确保 existingItem.id 存在
const itemId = existingItem['id']
const itemQty = existingItem['quantity']
if (itemId != null) {
const currentQty = typeof itemQty === 'number' ? itemQty : parseInt(String(itemQty || 0))
const newQty = currentQty + quantity
response = await supa
.from('ml_shopping_cart')
.update({
quantity: newQty,
updated_at: new Date().toISOString()
})
.eq('id', itemId)
.execute()
} else {
console.error('购物车已有商品但缺少ID无法更新. Data:', JSON.stringify(existingItem))
return false
}
} else {
// 商品不存在,添加新记录
response = await supa
.from('ml_shopping_cart')
.insert({
user_id: userId,
product_id: productId,
sku_id: skuId || null,
quantity: quantity,
selected: true,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
})
.execute()
}
if (response.error) {
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) {
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) {
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) {
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) {
console.error('更新购物车商品选中状态失败:', response.error)
return false
}
return true
} catch (error) {
console.error('更新购物车商品选中状态异常:', error)
return false
}
}
// 批量更新购物车商品选中状态
async batchUpdateCartItemSelection(cartItemIds: string[], selected: boolean): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (!userId) {
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)
.execute()
if (response.error) {
console.error('批量更新购物车商品选中状态失败:', response.error)
return false
}
return true
} catch (error) {
console.error('批量更新购物车商品选中状态异常:', error)
return false
}
}
// 删除购物车商品
async deleteCartItem(cartItemId: string): Promise<boolean> {
return true
/*
try {
console.log('正在执行删除购物车商品ID:', cartItemId)
const userId = this.getCurrentUserId()
if (!userId) {
console.error('用户未登录,无法删除购物车商品')
return false
}
const response = await supa
.from('ml_shopping_cart')
.eq('id', cartItemId)
.eq('user_id', userId)
.delete()
.execute()
if (response.error) {
console.error('删除购物车商品失败:', response.error)
return false
}
return true
} catch (error) {
console.error('删除购物车商品异常:', error)
return false
}
*/
}
// 批量删除购物车商品
async batchDeleteCartItems(cartItemIds: string[]): Promise<boolean> {
return true
/*
try {
const userId = this.getCurrentUserId()
if (!userId) {
console.error('用户未登录,无法删除购物车商品')
return false
}
const response = await supa
.from('ml_shopping_cart')
.eq('user_id', userId)
.in('id', cartItemIds)
.delete()
.execute()
if (response.error) {
console.error('批量删除购物车商品失败:', response.error)
return false
}
return true
} catch (error) {
console.error('批量删除购物车商品异常:', error)
return false
}
*/
}
// 清空购物车
async clearCart(): Promise<boolean> {
return true
/*
try {
const userId = this.getCurrentUserId()
if (!userId) {
console.error('用户未登录,无法清空购物车')
return false
}
const response = await supa
.from('ml_shopping_cart')
.eq('user_id', userId)
.delete()
.execute()
if (response.error) {
console.error('清空购物车失败:', response.error)
return false
}
return true
} catch (error) {
console.error('清空购物车异常:', error)
return false
}
*/
}
// 获取当前用户的所有地址
async getAddresses(): Promise<UserAddress[]> {
const userId = this.getCurrentUserId()
if (userId == null) {
console.warn('用户未登录,无法获取地址')
return []
}
try {
const query = 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 })
const response = await query.execute()
if (response.error != null) {
console.error('获取地址失败:', response.error)
return []
}
const data = response.data
if (data == null) {
return []
}
return data as UserAddress[]
} catch (error) {
console.error('获取地址异常:', 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: {
recipient_name: string
phone: string
province: string
city: string
district: string
detail_address: string
postal_code?: string
is_default?: boolean
}): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (!userId) {
console.error('用户未登录,无法添加地址')
return false
}
// 如果设置为默认地址,需要先取消其他默认地址
if (address.is_default) {
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) {
console.error('添加地址失败:', response.error)
return false
}
return true
} catch (error) {
console.error('添加地址异常:', error)
return false
}
}
// 更新地址
async updateAddress(addressId: string, address: {
recipient_name?: string
phone?: string
province?: string
city?: string
district?: string
detail_address?: string
postal_code?: string
is_default?: boolean
}): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (!userId) {
console.error('用户未登录,无法更新地址')
return false
}
// 如果设置为默认地址,需要先取消其他默认地址
if (address.is_default) {
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
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) {
console.error('更新地址失败:', response.error)
return false
}
return true
} catch (error) {
console.error('更新地址异常:', error)
return false
}
}
// 确认收货
async confirmReceipt(orderId: string): Promise<{ success: boolean, error?: string }> {
try {
const userId = this.getCurrentUserId()
if (!userId) {
return { success: false, error: '用户未登录' }
}
const response = await supa
.from('ml_orders')
.update({
order_status: 4, // 4: 已完成
delivered_at: new Date().toISOString(),
completed_at: new Date().toISOString(), // 也更新完成时间
updated_at: new Date().toISOString()
})
.eq('id', orderId)
.eq('user_id', userId)
.execute()
if (response.error) {
return { success: false, error: response.error.message }
}
return { success: true }
} catch (e: any) {
return { success: false, error: e.message }
}
}
// 删除地址
async deleteAddress(addressId: string): Promise<boolean> {
try {
console.log('正在执行删除地址ID:', addressId)
const userId = this.getCurrentUserId()
if (!userId) {
console.error('用户未登录,无法删除地址')
return false
}
const response = await supa
.from('ml_user_addresses')
.eq('id', addressId)
.eq('user_id', userId)
.delete()
.execute()
if (response.error) {
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) return null
// 联合查询 auth user 和 profile
// 由于 Supabase auth table 不可直接访问,这里查询 ml_user_profiles
const response = await supa
.from('ml_user_profiles')
.select('*')
.eq('user_id', userId)
.single()
.execute()
if (response.error) {
// 如果不存在 profile可能只有 auth user这里暂时返回空或创建默认
return null
}
return response.data
} catch (e) {
return null
}
}
// 创建订单
async createOrder(orderData: {
merchant_id: string,
product_amount: number,
shipping_fee: number,
total_amount: number,
shipping_address: any,
items: any[]
}): Promise<string | null> {
try {
const userId = this.getCurrentUserId()
if (!userId) {
console.error('CreateOrder: User not logged in')
return null
}
// 生成订单号
const orderNo = 'ML' + Date.now() + Math.floor(Math.random() * 1000)
// 1. 创建主订单
const orderResponse = await supa
.from('ml_orders')
.insert({
user_id: userId,
merchant_id: orderData.merchant_id,
order_no: orderNo,
product_amount: orderData.product_amount,
shipping_fee: orderData.shipping_fee,
total_amount: orderData.total_amount,
paid_amount: 0,
shipping_address: typeof orderData.shipping_address === 'string' ? orderData.shipping_address : JSON.stringify(orderData.shipping_address),
order_status: 1, // 待付款
payment_status: 1, // 未支付
shipping_status: 1, // 未发货
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
})
.select()
.single()
.execute()
if (orderResponse.error) {
console.error('创建订单失败:', orderResponse.error)
return null
}
const orderId = orderResponse.data['id'] as string
// 2. 创建订单项
const orderItems = orderData.items.map((item: any) => ({
order_id: orderId,
product_id: item.product_id,
sku_id: item.sku_id || null,
product_name: item.product_name,
sku_name: item.sku_name || '',
specifications: item.specifications ? (typeof item.specifications === 'string' ? item.specifications : JSON.stringify(item.specifications)) : '{}',
image_url: item.product_image || item.image_url,
price: item.price,
quantity: item.quantity,
total_amount: item.price * item.quantity,
created_at: new Date().toISOString()
}))
const itemsResponse = await supa
.from('ml_order_items')
.insert(orderItems)
.execute()
if (itemsResponse.error) {
console.error('创建订单项失败:', itemsResponse.error)
return null
}
// 3. 清除购物车中已购买的商品
const cartItemIds = orderData.items
.filter((item: any) => item.id && item.id.length > 10) // 假设UUID长度大于10区分临时ID
.map((item: any) => item.id as string)
if (cartItemIds.length > 0) {
await this.batchDeleteCartItems(cartItemIds)
}
return orderId
} catch (error) {
console.error('创建订单异常:', error)
return null
}
}
// 批量通过店铺创建订单
async createOrdersByShop(params: {
shipping_address: any,
shopGroups: any[],
deliveryFee: number,
discountAmount: number
}): Promise<{ success: boolean, orderIds: string[], error?: string }> {
try {
const orderIds: string[] = []
// 为每个店铺创建一个订单
for (const group of params.shopGroups) {
const shopItems = group.items as any[]
const productAmount = shopItems.reduce((sum, item) => sum + (item.price * item.quantity), 0)
// 简单平摊运费和优惠 (实际逻辑可能更复杂)
const ratio = productAmount / params.shopGroups.reduce((sum, g) => sum + g.items.reduce((s, i) => s + (i.price * i.quantity), 0), 0)
const shopShippingFee = params.deliveryFee * ratio
const shopDiscount = params.discountAmount * ratio
const shopTotal = productAmount + shopShippingFee - shopDiscount
const orderId = await this.createOrder({
merchant_id: group.merchant_id || group.shopId, // 兼容旧字段
product_amount: productAmount,
shipping_fee: shopShippingFee,
total_amount: shopTotal,
shipping_address: params.shipping_address,
items: shopItems
})
if (orderId) {
orderIds.push(orderId)
} else {
return { success: false, orderIds, error: `店铺 ${group.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) return []
let query = supa
.from('ml_orders')
.select(`
*,
ml_order_items (*)
`)
.eq('user_id', userId)
.order('created_at', { ascending: false })
if (status > 0) {
query = query.eq('order_status', status)
}
const response = await query.execute()
if (response.error) {
console.error('获取订单列表失败:', response.error)
return []
}
return response.data || []
} catch (error) {
console.error('获取订单列表异常:', error)
return []
}
}
// 获取订单详情
async getOrderDetail(orderId: string): Promise<any | null> {
try {
const userId = this.getCurrentUserId()
if (!userId) return null
const response = await supa
.from('ml_orders')
.select(`
*,
ml_order_items (*)
`)
.eq('id', orderId)
.eq('user_id', userId)
.single()
.execute()
if (response.error) {
return null
}
return response.data
} catch (e) {
return null
}
}
// 支付订单
async payOrder(orderId: string, paymentMethod: string, amount: number): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (!userId) return false
// 1. Update order status
const response = await supa
.from('ml_orders')
.update({
order_status: 2, // 待发货(已支付)
payment_status: 1, // Pay Success
payment_method: paymentMethod, // e.g. 'wechat', 'alipay'
payment_time: new Date().toISOString(),
updated_at: new Date().toISOString()
})
.eq('id', orderId)
.eq('user_id', userId)
.execute()
if (response.error) {
console.error('Pay order failed', response.error)
return false
}
// 2. Handle specific payment methods (e.g., deduct balance)
if (paymentMethod === 'balance') {
// await this.deductBalance(userId, amount)
}
return true
} catch (e) {
console.error('Pay order error', e)
return false
}
}
// 提交售后申请
async createRefund(data: any): Promise<{ success: boolean, message: string }> {
try {
const userId = this.getCurrentUserId()
if (!userId) return { success: false, message: '请先登录' }
const payload = {
user_id: userId,
order_id: data.order_id,
refund_no: 'REF' + Date.now() + Math.floor(Math.random() * 1000),
refund_type: data.refund_type,
refund_reason: data.refund_reason,
refund_amount: data.refund_amount,
description: data.description || '',
images: data.images || [],
status: 1 // Pending
}
const response = await supa
.from('ml_refunds')
.insert(payload)
.execute()
if (response.error) {
console.error('提交售后失败:', response.error)
return { success: false, message: '提交失败: ' + (response.error.message || '未知错误') }
}
return { success: true, message: '申请提交成功' }
} catch (e) {
console.error('提交售后异常:', e)
return { success: false, message: '系统异常' }
}
}
// 再次购买
async rePurchase(order: any): Promise<boolean> {
try {
const items = order.ml_order_items || order.items || []
if (items.length === 0) return false
// 简单的循环添加,实际项目中可以优化为批量插入
for (const item of items) {
const productId = item.product_id
const skuId = item.sku_id
const quantity = item.quantity
await this.addToCart(productId, quantity, skuId)
}
return true
} catch (e) {
return false
}
}
// 申请售后 (Legacy/Simple update)
async applyRefund(orderId: string, reason: string): Promise<boolean> {
try {
// 更新订单状态为 退款中 (6)
const { error } = await supa
.from('ml_orders')
.update({
order_status: 6,
cancel_reason: reason,
updated_at: new Date().toISOString()
})
.eq('id', orderId)
return 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) return []
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) {
query = query.in('status', statusList)
}
query = query.range((page - 1) * pageSize, page * pageSize - 1)
const response = await query.execute()
if (response.error) {
console.error('获取售后列表失败:', response.error)
return []
}
return response.data || []
} catch (e) {
console.error('获取售后列表异常:', e)
return []
}
}
// 获取用户钱包余额
async getUserBalance(): Promise<number> {
try {
const userId = this.getCurrentUserId()
console.log('[Supabase] getUserBalance userId:', userId)
if (!userId) 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 && 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 = (data instanceof UTSJSONObject) ? data : new UTSJSONObject(data)
val = jsonObj.getNumber('balance') || 0
if (val === 0 && jsonObj.getString('balance') != null) {
val = parseFloat(jsonObj.getString('balance')!)
}
if (val !== 0) return val
// 最后的尝试:直接属性访问
const raw = data['balance']
if (typeof raw === 'number') {
return raw as number
} else if (typeof raw === 'string') {
return parseFloat(raw as string)
}
return 0
}
}
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 {
return (profile['balance'] as number) || 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) 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 && 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 = (data instanceof UTSJSONObject) ? data : new UTSJSONObject(data)
const val = jsonObj.getNumber('points')
if (val != null) return val
// 直接属性访问兜底
const raw = data['points']
if (typeof raw === 'number') {
return raw as number
}
return (data['points'] as number) || 0
}
}
// Fallback check profile if needed
const profile = await this.getUserProfile()
if (profile != null) {
if (profile instanceof UTSJSONObject) {
return profile.getNumber('points') || 0
} else {
return (profile['points'] as number) || 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) return []
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) {
console.error('获取交易记录失败:', response.error)
return []
}
return response.data as any[]
} catch (e) {
console.error('获取交易记录异常:', e)
return []
}
}
// 获取积分记录
async getPointRecords(): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (!userId) return []
const res = await supa
.from('ml_point_records')
.select('*')
.eq('user_id', userId)
.order('created_at', { ascending: false })
.execute()
if (res.error) return []
return res.data || []
} catch (e) {
return []
}
}
// 获取用户红包
async getUserRedPackets(): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (!userId) return []
const res = await supa
.from('ml_user_red_packets')
.select('*')
.eq('user_id', userId)
.order('created_at', { ascending: false })
.execute()
if (res.error) {
console.error('获取红包失败:', res.error)
return []
}
return res.data as any[]
} catch (e) {
console.error('获取红包异常:', e)
return []
}
}
// 获取用户银行卡
async getUserBankCards(): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (!userId) return []
const res = await supa
.from('ml_user_bank_cards')
.select('*')
.eq('user_id', userId)
.order('created_at', { ascending: false })
.execute()
if (res.error) {
console.error('获取银行卡失败:', res.error)
return []
}
return res.data as any[]
} catch (e) {
console.error('获取银行卡异常:', e)
return []
}
}
// 余额充值 (调用 RPC)
async rechargeBalance(amount: number): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (!userId) return false
const res = await supa.rpc('recharge_wallet', {
p_user_id: userId,
p_amount: amount
})
if (res.error) {
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) return false
const res = await supa.rpc('withdraw_wallet', {
p_user_id: userId,
p_amount: amount
})
if (res.error) {
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) return false
// 补全 user_id
card.set('user_id', userId)
const res = await supa
.from('ml_user_bank_cards')
.insert(card)
.execute()
if (res.error) {
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) return false
const res = await supa
.from('ml_user_bank_cards')
.delete()
.eq('id', cardId)
.eq('user_id', userId)
.execute()
if (res.error) {
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) return false
const response = await supa
.from('ml_user_favorites')
.select('*') // Select all to verify data
.eq('user_id', userId)
.eq('target_id', productId)
.eq('target_type', 1) // 1 for product
.limit(1)
.execute()
// console.log(`[CheckFav] Response: ${JSON.stringify(response)}`)
if (response.error) {
console.error(`[CheckFav] Error: ${JSON.stringify(response.error)}`)
return false
}
const data = response.data
if (Array.isArray(data)) {
if (data.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 {
targetId = (item['target_id'] as string) || ''
}
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) return false
console.log(`[ToggleFav] Toggling for ${productId}`)
// Check if exists
const exists = await this.checkFavorite(productId)
console.log(`[ToggleFav] Current status: ${exists}`)
if (exists) {
// Delete
const response = await supa
.from('ml_user_favorites')
.delete()
.eq('user_id', userId)
.eq('target_id', productId)
.eq('target_type', 1)
.execute()
if (response.error) {
console.error('取消收藏失败:', response.error)
return true // 仍然是收藏状态
}
return false // 已取消收藏
} else {
// Add
const response = await supa
.from('ml_user_favorites')
.insert({
user_id: userId,
target_id: productId,
target_type: 1,
created_at: new Date().toISOString()
})
.execute()
if (response.error) {
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) return []
// 第一步:查询收藏列表
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) return []
const favorites = response.data as any[]
if (!favorites || favorites.length === 0) return []
// 第二步收集商品ID
const productIds: string[] = []
for (let i = 0; i < favorites.length; i++) {
let item = favorites[i]
let pid = ''
if (item instanceof UTSJSONObject) {
pid = item.getString('target_id') || ''
} else {
pid = (item['target_id'] as string) || ''
}
if (pid !== '') productIds.push(pid)
}
if (productIds.length === 0) return []
// 第三步:批量查询商品详情
const productRes = await supa
.from('ml_products')
.select('id, name, main_image_url, base_price, sale_count')
.in('id', productIds)
.execute()
if (productRes.error) return []
const products = productRes.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 {
pid = (p['id'] as string) || ''
}
if (pid !== '') productMap.set(pid, p)
}
// 第四步:组合数据
const result: any[] = []
for (let i = 0; i < favorites.length; i++) {
let item = favorites[i]
// 深拷贝或重新构造 item避免修改原引用
let newItem: any
if (item instanceof UTSJSONObject) {
newItem = item.toMap()
} else {
newItem = { ...item }
}
let targetId = ''
if (item instanceof UTSJSONObject) {
targetId = item.getString('target_id') || ''
} else {
targetId = (item['target_id'] as string) || ''
}
const product = productMap.get(targetId)
if (product) {
// 将商品信息挂载到 ml_products 字段上,保持与前端代码兼容
newItem['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) return []
// 1. 获取足迹记录
const response = await supa
.from('ml_user_footprints')
.select('*')
.eq('user_id', userId)
.order('updated_at', { ascending: false })
.limit(50) // 限制最近50条
.execute()
if (response.error) {
console.error('获取足迹失败:', response.error)
return []
}
const footprints = response.data as any[]
if (!footprints || footprints.length === 0) return []
// 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 {
pid = (item['product_id'] as string) || ''
}
if (pid !== '' && !productIds.includes(pid)) productIds.push(pid)
}
if (productIds.length === 0) return []
// 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', productIds)
.execute()
// 如果视图失败,回退查基础表
let products: any[] = []
if (!productRes.error && 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', productIds)
.execute()
if (!baseRes.error) {
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 {
pid = (p['id'] as string) || ''
}
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) viewTime = new Date(dateStr).getTime()
} else {
pid = (fp['product_id'] as string) || ''
const dateStr = fp['updated_at'] as string
if (dateStr) viewTime = new Date(dateStr).getTime()
}
const product = productMap.get(pid)
if (product) {
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 {
pName = (product['name'] as string) || ''
pImage = (product['main_image_url'] as string) || ''
pPrice = (product['base_price'] as number) || 0
pOriginalPrice = (product['market_price'] as number) || 0
pSales = (product['sale_count'] as number) || 0
pShopId = (product['merchant_id'] as string) || ''
pShopName = (product['shop_name'] as string) || ''
}
result.push({
id: pid,
name: pName,
price: pPrice,
original_price: pOriginalPrice,
image: pImage,
sales: pSales,
shopId: pShopId,
shopName: pShopName,
viewTime: viewTime
})
}
}
return result
} catch (error) {
console.error('获取足迹异常:', error)
return []
}
}
// 添加/更新足迹
async addFootprint(productId: string): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (!userId) return false
// 检查是否已存在
const checkRes = await supa
.from('ml_user_footprints')
.select('id')
.eq('user_id', userId)
.eq('product_id', productId)
.single()
.execute()
if (!checkRes.error && checkRes.data != null) {
// 更新时间
await supa
.from('ml_user_footprints')
.update({ updated_at: new Date().toISOString() })
.eq('user_id', userId)
.eq('product_id', productId)
.execute()
} else {
// 插入新记录
await supa
.from('ml_user_footprints')
.insert({
user_id: userId,
product_id: productId,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
})
.execute()
}
return true
} catch (e) {
console.error('添加足迹异常:', e)
return false
}
}
async getAddressList(): Promise<UserAddress[]> {
try {
const userId = this.getCurrentUserId()
if (!userId) return []
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) {
console.error('获取地址列表失败:', response.error)
return []
}
return response.data as UserAddress[]
} catch (e) {
console.error('获取地址列表异常:', e)
return []
}
}
// 设置默认地址
async setDefaultAddress(addressId: string): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (!userId) {
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) {
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) return []
// 假设有一个视图或者直接关联 ml_user_coupons 和 ml_coupon_templates
// 这里简化处理,尝试直接从 ml_user_coupons 读取,并且加入 template 信息
// 如果没有 view可能需要改为两个查询或者使用 left join
const response = await supa
.from('ml_user_coupons')
.select(`
*,
template:ml_coupon_templates(name, amount, min_spend)
`)
.eq('user_id', userId)
.eq('status', status)
.order('expire_at', { ascending: true })
.execute()
if (response.error) {
console.error('获取优惠券失败:', response.error)
return []
}
// 映射数据,将 template 的字段展平
const coupons = response.data.map((item: any) => {
const template = item.template || {}
return {
id: item.id,
user_id: item.user_id,
template_id: item.template_id,
coupon_code: item.coupon_code,
status: item.status,
received_at: item.received_at,
expire_at: item.expire_at,
template_name: template.name || '优惠券',
amount: template.amount || 0,
min_spend: template.min_spend || 0
} as UserCoupon
})
return coupons
} catch (e) {
console.error('获取优惠券异常:', e)
return []
}
}
// 获取可用优惠券数量
async getUserCouponCount(): Promise<number> {
try {
const userId = this.getCurrentUserId()
if (!userId) return 0
const response = await supa
.from('ml_user_coupons')
.select('id', { count: 'exact', head: true })
.eq('user_id', userId)
.eq('status', 1) // 1: unused
.gt('expire_at', new Date().toISOString()) // 未过期
.execute()
if (response.error) {
return 0
}
return response.count || 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 {
// 查询该商家的优惠券 + 平台通用券 (merchant_id is null)
// 注意:这里简化逻辑,实际可能需要联合查询用户是否已领取
const response = await supa
.from('ml_coupon_templates')
.select('*')
.or(`merchant_id.eq.${merchantId},merchant_id.is.null`)
.eq('status', 1)
.gt('end_time', new Date().toISOString())
.order('discount_value', { ascending: false })
.execute()
if (response.error) {
console.error('Fetch coupons failed:', response.error)
return []
}
return response.data
} catch (e) {
console.error('Fetch coupons error:', e)
return []
}
}
// 领取优惠券
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 {
// 1. Fetch template details to get merchant_id and validity
const tmplRes = await supa
.from('ml_coupon_templates')
.select('*')
.eq('id', templateId)
.single()
if (tmplRes.error) {
console.error('Claim Coupon: Template not found', tmplRes.error)
return false
}
const template = tmplRes.data as any
// Calculate expire_at
let expireAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()
if (template['valid_days'] && (template['valid_days'] as number) > 0) {
expireAt = new Date(Date.now() + (template['valid_days'] as number) * 24 * 60 * 60 * 1000).toISOString()
} else if (template['end_time']) {
expireAt = template['end_time'] as string
}
// 2. Insert into user coupons with merchant_id
const insertData = {
user_id: userId,
template_id: templateId,
merchant_id: template['merchant_id'], // Important for shop filtering
coupon_code: 'C' + Date.now() + Math.floor(Math.random() * 1000),
status: 1,
expire_at: expireAt,
received_at: new Date().toISOString()
}
const response = await supa
.from('ml_user_coupons')
.insert(insertData)
.execute()
if (response.error) {
console.error('Claim Coupon: Insert failed', response.error)
return false
}
return true
} catch(e) {
console.error('Claim coupon error:', e)
return false
}
}
}
// 导出单例实例
export const supabaseService = new SupabaseService()
// 默认导出
export default supabaseService