1406 lines
38 KiB
Plaintext
1406 lines
38 KiB
Plaintext
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 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
|
||
description?: string
|
||
specification?: string
|
||
price: number
|
||
base_price?: number
|
||
original_price?: number
|
||
market_price?: number
|
||
image?: string
|
||
main_image_url?: string
|
||
image_urls?: string // JSON string
|
||
manufacturer?: string
|
||
sales?: number
|
||
sale_count?: number
|
||
stock?: number
|
||
available_stock?: number
|
||
badge?: string
|
||
shop_id?: string
|
||
shop_name?: string
|
||
attributes?: string // JSON string
|
||
created_at?: string
|
||
expiry_date?: string
|
||
approval_number?: string
|
||
usage?: string
|
||
side_effects?: 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
|
||
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 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')
|
||
}
|
||
|
||
// 移除基于 storage 的后备获取,严格只认当前 Tab 独立 session
|
||
return 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 getProductsByCategory(
|
||
categoryId: string,
|
||
page: number = 1,
|
||
limit: number = 20
|
||
): Promise<PaginatedResponse<Product>> {
|
||
try {
|
||
const response = await supa
|
||
.from('ml_products')
|
||
.select('*', { count: 'exact' })
|
||
.eq('category_id', categoryId)
|
||
.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')
|
||
.select('*', { count: 'exact' })
|
||
.or(`name.ilike.%${keyword}%,manufacturer.ilike.%${keyword}%,specification.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')
|
||
.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')
|
||
.select('*')
|
||
.order('sales', { 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')
|
||
.select('*')
|
||
.order('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')
|
||
.select('*')
|
||
.order('created_at', { ascending: false })
|
||
.limit(limit)
|
||
.execute()
|
||
|
||
if (response.error) {
|
||
console.error('获取新品失败:', response.error)
|
||
return []
|
||
}
|
||
|
||
return response.data as Product[]
|
||
} catch (error) {
|
||
console.error('获取新品异常:', error)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 获取推荐商品(带badge的商品)
|
||
async getRecommendedProducts(limit: number = 10): Promise<Product[]> {
|
||
try {
|
||
// 直接使用 neq 空字符串查询,忽略 null 值(null 表示没有 badge,不应被推荐)
|
||
const response = await supa
|
||
.from('ml_products')
|
||
.select('*')
|
||
.neq('badge', '')
|
||
.order('sales', { 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 []
|
||
}
|
||
}
|
||
|
||
// 获取特价商品(badge为'特价')
|
||
async getDiscountProducts(limit: number = 10): Promise<Product[]> {
|
||
try {
|
||
const response = await supa
|
||
.from('ml_products')
|
||
.select('*')
|
||
.eq('badge', '特价')
|
||
.order('sales', { 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 []
|
||
}
|
||
}
|
||
|
||
// 获取当前用户的购物车商品(关联商品和店铺信息)
|
||
async getCartItems(): Promise<CartItem[]> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (!userId) {
|
||
console.warn('用户未登录,无法获取购物车')
|
||
return []
|
||
}
|
||
|
||
// 查询购物车表,并关联商品表(使用内联关联)
|
||
const response = await supa
|
||
.from('ml_shopping_cart')
|
||
.select(`
|
||
id,
|
||
user_id,
|
||
product_id,
|
||
sku_id,
|
||
quantity,
|
||
selected,
|
||
created_at,
|
||
updated_at,
|
||
ml_products!inner (
|
||
id,
|
||
name,
|
||
image,
|
||
price,
|
||
specification,
|
||
merchant_id
|
||
)
|
||
`)
|
||
.eq('user_id', userId)
|
||
.order('created_at', { ascending: false })
|
||
.execute()
|
||
|
||
if (response.error) {
|
||
console.error('获取购物车失败:', response.error)
|
||
return []
|
||
}
|
||
|
||
const cartData = response.data as any[]
|
||
|
||
// 调试日子:打印购物车数据第一条结构,确认产品字段名
|
||
if (cartData && Array.isArray(cartData) && cartData.length > 0) {
|
||
console.log('Cart Item Structure:', JSON.stringify(cartData[0]))
|
||
}
|
||
|
||
const merchantIds: string[] = []
|
||
|
||
if (cartData && Array.isArray(cartData)) {
|
||
for (const item of cartData) {
|
||
// PostgREST 返回的关联字段通常与表名一致
|
||
// 尝试获取ml_products,如果为空则尝试products
|
||
let product = item['ml_products'] as any
|
||
if (!product) {
|
||
product = item['products'] as any
|
||
}
|
||
|
||
if (product && product.merchant_id && !merchantIds.includes(product.merchant_id)) {
|
||
merchantIds.push(product.merchant_id as string)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 查询店铺信息
|
||
const shopMap = new Map<string, any>()
|
||
if (merchantIds.length > 0) {
|
||
const shopRes = await supa
|
||
.from('ml_shops')
|
||
.select('id, merchant_id, shop_name')
|
||
.in('merchant_id', merchantIds)
|
||
.execute()
|
||
|
||
if (!shopRes.error && shopRes.data != null) {
|
||
const shops = shopRes.data as any[]
|
||
for (const shop of shops) {
|
||
shopMap.set(shop.merchant_id as string, shop)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 处理返回数据,构建CartItem数组
|
||
const cartItems: CartItem[] = []
|
||
if (cartData && Array.isArray(cartData)) {
|
||
for (const item of cartData) {
|
||
let product = item['ml_products'] as any
|
||
if (!product) {
|
||
product = item['products'] as any
|
||
}
|
||
|
||
const merchantId = product?.merchant_id as string
|
||
const shopInfo = shopMap.get(merchantId)
|
||
|
||
cartItems.push({
|
||
id: item.id as string,
|
||
user_id: item.user_id as string,
|
||
product_id: item.product_id as string,
|
||
sku_id: item.sku_id as string,
|
||
quantity: item.quantity as number,
|
||
selected: item.selected as boolean,
|
||
product_name: product?.name as string,
|
||
product_image: product?.image as string,
|
||
product_price: product?.price as number,
|
||
product_specification: product?.specification as string,
|
||
shop_id: shopInfo ? (shopInfo['id'] as string) : (merchantId || 'unknown_shop'),
|
||
shop_name: shopInfo ? (shopInfo['shop_name'] as string) : '未知店铺',
|
||
created_at: item.created_at as string,
|
||
updated_at: item.updated_at as string
|
||
})
|
||
}
|
||
}
|
||
|
||
return cartItems
|
||
} catch (error) {
|
||
console.error('获取购物车异常:', error)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 添加商品到购物车
|
||
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> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (!userId) {
|
||
console.error('用户未登录,无法删除购物车商品')
|
||
return false
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_shopping_cart')
|
||
.delete()
|
||
.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 batchDeleteCartItems(cartItemIds: string[]): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (!userId) {
|
||
console.error('用户未登录,无法删除购物车商品')
|
||
return false
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_shopping_cart')
|
||
.delete()
|
||
.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 clearCart(): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (!userId) {
|
||
console.error('用户未登录,无法清空购物车')
|
||
return false
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_shopping_cart')
|
||
.delete()
|
||
.eq('user_id', userId)
|
||
.execute()
|
||
|
||
if (response.error) {
|
||
console.error('清空购物车失败:', response.error)
|
||
return false
|
||
}
|
||
|
||
return true
|
||
} catch (error) {
|
||
console.error('清空购物车异常:', error)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 获取当前用户的所有地址
|
||
async getAddresses(): Promise<UserAddress[]> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (!userId) {
|
||
console.warn('用户未登录,无法获取地址')
|
||
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 (error) {
|
||
console.error('获取地址异常:', error)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 根据ID获取地址详情
|
||
async getAddressById(addressId: string): Promise<UserAddress | null> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (!userId) {
|
||
console.warn('用户未登录,无法获取地址')
|
||
return null
|
||
}
|
||
|
||
const response = await 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()
|
||
.execute()
|
||
|
||
if (response.error) {
|
||
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 deleteAddress(addressId: string): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (!userId) {
|
||
console.error('用户未登录,无法删除地址')
|
||
return false
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_user_addresses')
|
||
.delete()
|
||
.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
|
||
}
|
||
}
|
||
|
||
// 清除默认地址(内部使用)
|
||
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) return null
|
||
|
||
// 生成订单号
|
||
const orderNo = 'ORD' + 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: JSON.stringify(orderData.shipping_address),
|
||
order_status: 1, // 待付款
|
||
payment_status: 1, // 未支付
|
||
shipping_status: 1, // 未发货
|
||
created_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 ? JSON.stringify(item.specifications) : '{}',
|
||
image_url: 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. 清除购物车中已购买的商品(如果是从购物车购买)
|
||
// 这一步通常在前端调用 removeCartItem 或在此处根据参数处理
|
||
|
||
return orderId
|
||
} catch (error) {
|
||
console.error('创建订单异常:', error)
|
||
return null
|
||
}
|
||
}
|
||
|
||
// 获取订单列表
|
||
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 (*),
|
||
ml_shops (shop_name, id)
|
||
`)
|
||
.eq('id', orderId)
|
||
.eq('user_id', userId)
|
||
.single()
|
||
.execute()
|
||
|
||
if (response.error) {
|
||
return null
|
||
}
|
||
return response.data
|
||
} catch (e) {
|
||
return null
|
||
}
|
||
}
|
||
|
||
// 收藏相关
|
||
async checkFavorite(productId: string): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (!userId) return false
|
||
|
||
const response = await supa
|
||
.from('ml_user_favorites')
|
||
.select('id')
|
||
.eq('user_id', userId)
|
||
.eq('target_id', productId)
|
||
.eq('target_type', 1) // 1 for product
|
||
.single()
|
||
.execute()
|
||
|
||
return !!response.data
|
||
} catch(e) {
|
||
return false
|
||
}
|
||
}
|
||
|
||
async toggleFavorite(productId: string): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (!userId) return false
|
||
|
||
// Check if exists
|
||
const exists = await this.checkFavorite(productId)
|
||
|
||
if (exists) {
|
||
// Delete
|
||
await supa
|
||
.from('ml_user_favorites')
|
||
.delete()
|
||
.eq('user_id', userId)
|
||
.eq('target_id', productId)
|
||
.eq('target_type', 1)
|
||
.execute()
|
||
return false // Now not favorite
|
||
} else {
|
||
// Add
|
||
await supa
|
||
.from('ml_user_favorites')
|
||
.insert({
|
||
user_id: userId,
|
||
target_id: productId,
|
||
target_type: 1,
|
||
created_at: new Date().toISOString()
|
||
})
|
||
.execute()
|
||
return true // Now favorite
|
||
}
|
||
} catch (e) {
|
||
return false
|
||
}
|
||
}
|
||
|
||
async getFavorites(): Promise<any[]> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (!userId) return []
|
||
|
||
// 需要关联查询商品信息
|
||
const response = await supa
|
||
.from('ml_user_favorites')
|
||
.select(`
|
||
id,
|
||
target_id,
|
||
created_at,
|
||
ml_products!target_id (
|
||
id, name, image_urls, main_image_url, price, sales
|
||
)
|
||
`)
|
||
.eq('user_id', userId)
|
||
.eq('target_type', 1)
|
||
.order('created_at', { ascending: false })
|
||
.execute()
|
||
|
||
if (response.error) return []
|
||
return response.data || []
|
||
} catch (e) {
|
||
return []
|
||
}
|
||
}
|
||
|
||
|
||
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
|
||
}
|
||
}
|
||
}
|
||
|
||
// 导出单例实例
|
||
export const supabaseService = new SupabaseService()
|
||
|
||
// 默认导出
|
||
export default supabaseService
|