2131 lines
66 KiB
Plaintext
2131 lines
66 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
|
||
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
|
||
// 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 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 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 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 {
|
||
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> {
|
||
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> {
|
||
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[]> {
|
||
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 {
|
||
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 getUserBalance(): Promise<number> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (!userId) return 0
|
||
|
||
// Check if wallet table exists or uses profile
|
||
// Assume ml_user_wallets or field in profile
|
||
// For now returning mock high balance or check profile
|
||
const profile = await this.getUserProfile()
|
||
if (profile && typeof profile.balance === 'number') {
|
||
return profile.balance
|
||
}
|
||
return 0
|
||
} catch(e) {
|
||
return 0
|
||
}
|
||
}
|
||
|
||
// 收藏相关
|
||
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')
|
||
.eq('user_id', userId)
|
||
.eq('target_id', productId)
|
||
.eq('target_type', 1)
|
||
.delete()
|
||
.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 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)
|
||
.limit(1)
|
||
.execute()
|
||
|
||
if (checkRes.error) {
|
||
console.error('检查足迹失败', checkRes.error)
|
||
// 可能是表不存在,但我们还是尝试继续或返回
|
||
// 这里假设如果检查失败可能是网络问题或权限
|
||
// 如果表不存在,下面的 insert 也会失败
|
||
}
|
||
|
||
const data = checkRes.data
|
||
let exists = false
|
||
if (Array.isArray(data) && data.length > 0) exists = true
|
||
else if (data instanceof UTSJSONObject) exists = true // single object case
|
||
|
||
if (exists) {
|
||
// 更新时间
|
||
await supa
|
||
.from('ml_user_footprints')
|
||
.update({
|
||
updated_at: new Date().toISOString(),
|
||
created_at: new Date().toISOString() // 更新 created_at 以便排序(可选,视需求而定,通常足迹按访问时间倒序)
|
||
})
|
||
.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 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(100) // 限制数量
|
||
.execute()
|
||
|
||
if (response.error) {
|
||
console.error('获取足迹列表失败:', response.error)
|
||
return []
|
||
}
|
||
const footprints = response.data as any[]
|
||
console.log('原始足迹数据条数:', footprints.length)
|
||
if (footprints.length > 0) {
|
||
console.log('第一条足迹:', JSON.stringify(footprints[0]))
|
||
}
|
||
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.push(pid)
|
||
}
|
||
|
||
console.log('收集到的商品ID:', JSON.stringify(productIds))
|
||
if (productIds.length === 0) return []
|
||
|
||
// 3. 批量获取商品信息
|
||
let products: any[] = []
|
||
|
||
// 优先尝试查询 ml_products 表(基础表),因为它最可靠
|
||
const simpleRes = await supa
|
||
.from('ml_products')
|
||
.select('id, name, main_image_url, base_price, sale_count, merchant_id')
|
||
.in('id', productIds)
|
||
.execute()
|
||
|
||
if (!simpleRes.error && simpleRes.data != null) {
|
||
products = simpleRes.data as any[]
|
||
console.log('从 ml_products 获取到商品条数:', products.length)
|
||
} else {
|
||
console.error('从 ml_products 获取商品失败:', simpleRes.error)
|
||
}
|
||
|
||
// 如果需要更详细的店铺信息,可以在这里补充查询店铺,或者简化处理直接显示“未知店铺”
|
||
// 为了保证显示,我们先确保有商品基本信息
|
||
|
||
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 item = footprints[i]
|
||
let pid = ''
|
||
let viewTimeStr = ''
|
||
if (item instanceof UTSJSONObject) {
|
||
pid = item.getString('product_id') || ''
|
||
viewTimeStr = item.getString('updated_at') || item.getString('created_at') || ''
|
||
} else {
|
||
pid = (item['product_id'] as string) || ''
|
||
viewTimeStr = (item['updated_at'] as string) || (item['created_at'] as string) || ''
|
||
}
|
||
|
||
const product = productMap.get(pid)
|
||
if (product) {
|
||
// 构造前端需要的格式
|
||
let name = '', image = '', shopName = '自营店铺', shopId = ''
|
||
let price = 0, original_price = 0, sales = 0
|
||
|
||
if (product instanceof UTSJSONObject) {
|
||
name = product.getString('name') || '未知商品'
|
||
image = product.getString('main_image_url') || '/static/default-product.png'
|
||
price = product.getNumber('base_price') || 0
|
||
sales = product.getNumber('sale_count') || 0
|
||
shopId = product.getString('merchant_id') || ''
|
||
// 尝试从 view 获取 shopName,或者这里暂时给默认值
|
||
} else {
|
||
name = (product['name'] as string) || '未知商品'
|
||
image = (product['main_image_url'] as string) || '/static/default-product.png'
|
||
price = (product['base_price'] as number) || 0
|
||
sales = (product['sale_count'] as number) || 0
|
||
shopId = (product['merchant_id'] as string) || ''
|
||
}
|
||
|
||
result.push({
|
||
id: pid,
|
||
name: name,
|
||
price: price,
|
||
original_price: original_price,
|
||
image: image,
|
||
sales: sales,
|
||
shopId: shopId,
|
||
shopName: shopName,
|
||
viewTime: new Date(viewTimeStr).getTime()
|
||
})
|
||
} else {
|
||
// console.warn('未找到商品信息 ID:', pid)
|
||
}
|
||
}
|
||
|
||
// console.log('最终返回足迹数量:', result.length)
|
||
return result
|
||
} catch (e) {
|
||
console.error('获取足迹异常:', e)
|
||
return []
|
||
}
|
||
}
|
||
|
||
async deleteFootprint(productId: string): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (!userId) return false
|
||
|
||
await supa
|
||
.from('ml_user_footprints')
|
||
.eq('user_id', userId)
|
||
.eq('product_id', productId)
|
||
.delete()
|
||
.execute()
|
||
|
||
return true
|
||
} catch (e) {
|
||
return false
|
||
}
|
||
}
|
||
|
||
async clearFootprints(): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (!userId) return false
|
||
|
||
await supa
|
||
.from('ml_user_footprints')
|
||
.eq('user_id', userId)
|
||
.delete()
|
||
.execute()
|
||
|
||
return true
|
||
} catch (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
|
||
}
|
||
}
|
||
}
|
||
|
||
// 导出单例实例
|
||
export const supabaseService = new SupabaseService()
|
||
|
||
// 默认导出
|
||
export default supabaseService
|