Files
medical-mall/utils/supabaseService.uts
2026-02-27 16:51:56 +08:00

4541 lines
148 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 type Brand = {
id: string
name: string
logo_url: string
description: string
}
export type Category = {
id: string
name: string
icon: string
description: string
color: string
parent_id?: string
level?: number
slug?: string
created_at?: string
}
export type Product = {
id: string
category_id: string
merchant_id: string
name: string
subtitle?: string
description?: string
base_price?: number
market_price?: number
cost_price?: number
main_image_url?: string
image_url?: string
image_urls?: string
video_urls?: string
images?: string[]
sale_count?: number
view_count?: number
total_stock?: number
available_stock?: number
is_hot?: boolean
is_new?: boolean
is_featured?: boolean
status?: number
rating_avg?: number
rating_count?: number
rating?: number
review_count?: number
brand_id?: string
shop_id?: string
tags?: string
attributes?: string
specification?: string
created_at?: string
updated_at?: string
price?: number
original_price?: number
stock?: number
sales?: number
cover?: string
brand_name?: string
category_name?: string
shop_name?: string
merchant_name?: string
}
export type Shop = {
id: string
merchant_id: string
shop_name: string
shop_logo?: string
shop_banner?: string
description?: string
contact_name?: string
contact_phone?: string
rating_avg?: number
total_sales?: number
product_count?: number
total_sales_count?: number
created_at?: string
}
export type CartItem = {
id: string
user_id: string
product_id: string
sku_id?: string
merchant_id?: string
quantity: number
selected: boolean
product_name?: string
product_image?: string
product_price?: number
product_specification?: string
shop_id?: string
shop_name?: string
created_at?: string
updated_at?: string
}
export type UserAddress = {
id: string
user_id: string
recipient_name: string
phone: string
province: string
city: string
district: string
detail_address: string
postal_code?: string
is_default: boolean
label?: string
created_at?: string
updated_at?: string
}
export type UserCoupon = {
id: string
user_id: string
template_id: string
coupon_code: string
status: number // 1: unused, 2: used, 3: expired
received_at: string
expire_at: string
used_at?: string
// join fields from template or view
template_name?: string
amount?: number
min_spend?: number
name?: string
title?: string
}
export type ChatRoom = {
id: string
user_id: string
merchant_id: string
shop_name: string
shop_logo?: string
last_message?: string
last_message_at?: string
unread_count: number
is_top: boolean
created_at?: string
updated_at?: string
}
export type Notification = {
id: string
user_id: string
type: string
title: string
content: string
icon_url?: string
link_url?: string
is_read: boolean
extra_data?: string
created_at?: string
}
export type ChatMessage = {
id: string
session_id?: string
sender_id?: string
receiver_id?: string
content: string
msg_type: string
is_read: boolean
is_from_user: boolean
extra_data?: string
created_at?: string
}
export type PaginatedResponse<T> = {
data: T[]
total: number
page: number
limit: number
hasmore: boolean
}
export type ProductSku = {
id: string
product_id: string
sku_code: string
specifications: string // JSON string
price: number
market_price?: number
cost_price?: number
stock?: number
warning_stock?: number
image_url?: string
weight?: number
status?: number
created_at?: string
}
export type AddAddressParams = {
recipient_name: string
phone: string
province: string
city: string
district: string
detail_address: string
postal_code?: string
is_default?: boolean
label?: string
}
export type UpdateAddressParams = {
recipient_name?: string
phone?: string
province?: string
city?: string
district?: string
detail_address?: string
postal_code?: string
is_default?: boolean
label?: string
}
export type CreateOrderParams = {
merchant_id: string
product_amount: number
shipping_fee: number
total_amount: number
shipping_address: any
items: any[]
}
export type ShopOrderParams = {
shipping_address: any
shopGroups: any[]
deliveryFee: number
discountAmount: number
}
export type ShopOrderResponse = {
success: boolean
orderIds: string[]
error?: string
}
export type RefundResponse = {
success: boolean
message: string
}
export type ConfirmReceiptResponse = {
success: boolean
error?: string
}
class SupabaseService {
// 获取当前用户ID
public getCurrentUserId(): string | null {
try {
// 1. 优先从 Supabase 会话获取
const session = supa.getSession()
if (session != null && session.user != null) {
return session.user.getString('id')
}
// 2. 尝试从 Storage 恢复 Session (针对 App 重启后内存丢失的情况)
// 注意:这里无法异步调用 hydrate所以只能依赖 UI 层或 init 层的预加载
// 但我们可以返回本地存储 ID 作为 fallback前提是 Token 有效
// 后备:尝试从本地存储获取
const userId = uni.getStorageSync('user_id')
return userId != null ? userId as string : null
} catch (e) {
console.error('获取用户ID失败:', e)
return null
}
}
// 确保会话有效 (异步)
async ensureSession(): Promise<string | null> {
let session = supa.getSession()
if (session.user == null) {
console.log('Session user is null, attempting to hydrate from storage...')
await supa.hydrateSessionFromStorage()
session = supa.getSession()
}
if (session.user != null) {
// 同步 user_id 到 storage 保持一致
const uid = session.user!!.getString('id')
if (uid != null) {
uni.setStorageSync('user_id', uid)
return uid
}
}
return this.getCurrentUserId()
}
// 获取所有分类
async getCategories(): Promise<Category[]> {
try {
const response = await supa
.from('ml_categories')
.select('*')
.order('name', { ascending: true })
.execute()
if (response.error != null) {
console.error('获取分类失败:', response.error)
return []
}
const rawData = response.data
if (rawData == null) {
return []
}
const categories: Category[] = []
const rawList = rawData as any[]
for (let i: number = 0; i < rawList.length; i++) {
const item = rawList[i]
const catObj = item as UTSJSONObject
const idVal = catObj.get('id')
const nameVal = catObj.get('name')
const iconVal = catObj.get('icon')
const iconUrlVal = catObj.get('icon_url')
const descVal = catObj.get('description')
const colorVal = catObj.get('color')
const parentIdVal = catObj.get('parent_id')
const levelVal = catObj.get('level')
const cat: Category = {
id: (typeof idVal == 'string') ? (idVal as string) : '',
name: (typeof nameVal == 'string') ? (nameVal as string) : '',
icon: (typeof iconVal == 'string') ? (iconVal as string) : ((typeof iconUrlVal == 'string') ? (iconUrlVal as string) : ''),
description: (typeof descVal == 'string') ? (descVal as string) : '',
color: (typeof colorVal == 'string') ? (colorVal as string) : '#4CAF50',
parent_id: (typeof parentIdVal == 'string') ? (parentIdVal as string) : null,
level: (typeof levelVal == 'number') ? (levelVal as number) : 0
} as Category
categories.push(cat)
}
return categories
} catch (error) {
console.error('获取分类异常:', error)
return []
}
}
// 获取一级分类
async getParentCategories(): Promise<Category[]> {
try {
const response = await supa
.from('ml_categories')
.select('*')
.is('parent_id', null)
.order('sort_order', { ascending: true })
.execute()
if (response.error != null) {
console.error('获取一级分类失败:', response.error)
return []
}
const rawData = response.data
if (rawData == null) {
return []
}
const categories: Category[] = []
const rawList = rawData as Array<UTSJSONObject>
for (let i = 0; i < rawList.length; i++) {
const item = rawList[i]
const icon = this.getCategoryIcon(item)
// 安全获取属性
const idVal = item['id']
const nameVal = item['name']
const descVal = item['description']
const colorVal = item['color']
const slugVal = item['slug']
const cat: Category = {
id: (typeof idVal == 'string') ? (idVal as string) : '',
name: (typeof nameVal == 'string') ? (nameVal as string) : '',
icon: icon,
description: (typeof descVal == 'string') ? (descVal as string) : '',
color: (typeof colorVal == 'string') ? (colorVal as string) : '#4CAF50',
level: 1,
slug: (typeof slugVal == 'string') ? (slugVal as string) : ''
}
categories.push(cat)
}
return categories
} catch (error) {
console.error('获取一级分类异常:', error)
return []
}
}
// 获取子分类
async getSubCategories(parentId: string): Promise<Category[]> {
try {
const response = await supa
.from('ml_categories')
.select('*')
.eq('parent_id', parentId)
.order('sort_order', { ascending: true })
.execute()
if (response.error != null) {
console.error('获取子分类失败:', response.error)
return []
}
const rawData = response.data
if (rawData == null) {
return []
}
const categories: Category[] = []
const rawList = rawData as Array<UTSJSONObject>
for (let i = 0; i < rawList.length; i++) {
const item = rawList[i]
const icon = this.getCategoryIcon(item)
const cat: Category = {
id: item['id'] as string,
name: item['name'] as string,
icon: icon,
description: (item['description'] as string) ?? '',
color: (item['color'] as string) ?? '#4CAF50',
level: 2,
parent_id: item['parent_id'] as string,
slug: item['slug'] as string
}
categories.push(cat)
}
return categories
} catch (error) {
console.error('获取子分类异常:', error)
return []
}
}
// 获取分类图标的辅助方法
getCategoryIcon(item: UTSJSONObject): string {
const icon = item['icon'] as string | null
if (icon != null && icon.length > 0) {
return icon
}
const iconUrl = item['icon_url'] as string | null
if (iconUrl != null && iconUrl.length > 0) {
return iconUrl
}
// 根据分类名称返回默认图标
const name = (item['name'] as string) ?? ''
if (name.includes('数码') || name.includes('电器') || name.includes('手机')) return '📱'
if (name.includes('服装') || name.includes('衣服') || name.includes('鞋')) return '👕'
if (name.includes('食品') || name.includes('水果') || name.includes('零食')) return '🍎'
if (name.includes('美妆') || name.includes('护肤') || name.includes('化妆')) return '💄'
if (name.includes('母婴') || name.includes('婴儿') || name.includes('儿童')) return '👶'
if (name.includes('家居') || name.includes('家具') || name.includes('装饰')) return '🏠'
if (name.includes('图书') || name.includes('文具')) return '📚'
if (name.includes('运动') || name.includes('户外') || name.includes('健身')) return '⚽'
if (name.includes('医药') || name.includes('保健') || name.includes('健康')) return '💊'
return '📦'
}
// 获取所有品牌
async getBrands(): Promise<Brand[]> {
try {
console.log('[getBrands] 开始获取品牌数据...')
const response = await supa
.from('ml_brands')
.select('*')
.eq('is_active', true)
.order('name', { ascending: true })
.execute()
if (response.error != null) {
console.error('获取品牌失败:', response.error)
return []
}
const rawData = response.data
console.log('[getBrands] 原始数据:', rawData)
if (rawData == null) {
console.log('[getBrands] 数据为空')
return []
}
const brands: Brand[] = []
const rawList = rawData as any[]
console.log('[getBrands] 数据条数:', rawList.length)
for (let i: number = 0; i < rawList.length; i++) {
const item = rawList[i]
const brandObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
const idVal = brandObj.get('id')
const nameVal = brandObj.get('name')
const logoVal = brandObj.get('logo_url')
const descVal = brandObj.get('description')
const brand: Brand = {
id: (idVal != null && typeof idVal == 'string') ? (idVal as string) : '',
name: (nameVal != null && typeof nameVal == 'string') ? (nameVal as string) : '',
logo_url: (logoVal != null && typeof logoVal == 'string') ? (logoVal as string) : '',
description: (descVal != null && typeof descVal == 'string') ? (descVal as string) : ''
} as Brand
brands.push(brand)
}
console.log('[getBrands] 返回品牌数量:', brands.length)
return brands
} catch (error) {
console.error('获取品牌异常:', error)
return []
}
}
// 获取指定分类的商品
async getProductsByCategory(
categoryId: string,
page: number = 1,
limit: number = 20
): Promise<PaginatedResponse<Product>> {
try {
const response = await supa
.from('ml_products_detail_view')
.select('*', { count: 'exact' })
.eq('category_id', categoryId)
.eq('status', 1)
.order('sale_count', { ascending: false })
.page(page)
.limit(limit)
.execute()
if (response.error != null) {
console.error('获取商品失败:', response.error)
return {
data: [] as Product[],
total: 0,
page,
limit,
hasmore: false
}
}
return {
data: response.data as Product[],
total: response.total ?? 0,
page,
limit,
hasmore: response.hasmore ?? false
}
} catch (error) {
console.error('获取商品异常:', error)
return {
data: [] as Product[],
total: 0,
page,
limit,
hasmore: false
}
}
}
// 根据商品ID获取SKU列表
async getProductSkus(productId: string): Promise<ProductSku[]> {
try {
const response = await supa
.from('ml_product_skus')
.select('*')
.eq('product_id', productId)
.eq('status', 1)
.execute()
if (response.error != null) {
console.error('获取商品SKU失败:', response.error)
return []
}
return response.data as ProductSku[]
} catch (error) {
console.error('获取商品SKU异常:', error)
return []
}
}
// 搜索商品
async searchProducts(
keyword: string,
page: number = 1,
limit: number = 20,
sortBy: string = 'sales',
ascending: boolean = false
): Promise<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 != null) {
console.error('搜索商品失败:', response.error)
return {
data: [] as Product[],
total: 0,
page,
limit,
hasmore: false
}
}
return {
data: response.data as Product[],
total: response.total ?? 0,
page,
limit,
hasmore: response.hasmore ?? false
}
} catch (error) {
console.error('搜索商品异常:', error)
return {
data: [] as Product[],
total: 0,
page,
limit,
hasmore: false
}
}
}
// 搜索店铺
async searchShops(
keyword: string,
page: number = 1,
limit: number = 20
): Promise<PaginatedResponse<Shop>> {
try {
const response = await supa
.from('ml_shops')
.select('*', { count: 'exact' })
.eq('status', 1)
.ilike('shop_name', `%${keyword}%`)
.order('product_count', { ascending: false })
.page(page)
.limit(limit)
.execute()
if (response.error != null) {
console.error('搜索店铺失败:', response.error)
return { data: [] as Shop[], total: 0, page, limit, hasmore: false }
}
// 映射数据确保类型安全
const shops: Shop[] = []
const dataList = response.data as any[]
for (let i = 0; i < dataList.length; i++) {
shops.push(dataList[i] as Shop)
}
return {
data: shops,
total: response.total ?? 0,
page,
limit,
hasmore: response.hasmore ?? false
}
} catch (error) {
console.error('搜索店铺异常:', error)
return { data: [] as Shop[], total: 0, page, limit, hasmore: false }
}
}
// 获取单个商品详情
async getProductById(productId: string): Promise<Product | null> {
try {
const response = await supa
.from('ml_products_detail_view')
.select('*')
.eq('id', productId)
.single()
.executeAs<Product>()
if (response.error != null) {
console.error('获取商品详情失败:', response.error)
return null
}
return response.data as Product
} catch (error) {
console.error('获取商品详情异常:', error)
return null
}
}
// --- 关注店铺相关 ---
// 检查是否已关注店铺
async isShopFollowed(shopId: string, userId: string): Promise<boolean> {
try {
const res = await supa
.from('ml_shop_follows')
.select('id', { count: 'exact' })
.eq('shop_id', shopId)
.eq('user_id', userId)
.limit(1)
.execute()
return (res.total != null && res.total! > 0)
} catch (e) {
console.error('Check follow error:', e)
return false
}
}
// 关注店铺
async followShop(shopId: string, userId: string): Promise<boolean> {
try {
const res = await supa
.from('ml_shop_follows')
.insert({
user_id: userId,
shop_id: shopId
})
.execute()
return res.error == null
} catch (e) {
console.error('Follow shop error:', e)
return false
}
}
// 取消关注
async unfollowShop(shopId: string, userId: string): Promise<boolean> {
try {
const res = await supa
.from('ml_shop_follows')
.eq('shop_id', shopId)
.eq('user_id', userId)
.delete()
.execute()
return res.error == null
} catch (e) {
console.error('Unfollow shop error:', e)
return false
}
}
// 获取我关注的店铺列表
async getFollowedShops(userId: string): Promise<any[]> {
try {
// 关联查询店铺信息
const res = await supa
.from('ml_shop_follows')
.select('*, ml_shops(*)')
.eq('user_id', userId)
.order('created_at', { ascending: false })
.execute()
if (res.error != null) {
console.error('getFollowedShops error:', res.error)
return []
}
return res.data as any[]
} catch (e) {
console.error('getFollowedShops exception:', e)
return []
}
}
// 根据商户ID获取店铺信息
async getShopByMerchantId(merchantId: string): Promise<Shop | null> {
try {
// 1. Try querying by merchant_id
let response = await supa
.from('ml_shops')
.select('*')
.eq('merchant_id', merchantId)
.limit(1)
.execute()
if (response.error == null && response.data != null && (response.data as any[]).length > 0) {
return (response.data as any[])[0] as Shop
}
// 2. Fallback: Try querying by id (Maybe the passed ID is the Shop ID?)
console.log('getShopByMerchantId: merchant_id not found, trying id...', merchantId)
response = await supa
.from('ml_shops')
.select('*')
.eq('id', merchantId)
.limit(1)
.execute()
if (response.error == null && response.data != null && (response.data as any[]).length > 0) {
console.log('Found shop by ID instead of MerchantID')
// Fix the merchant_id reference if we found it by ID
const shop = (response.data as any[])[0] as Shop
return shop
}
if (response.error != null) {
console.error('获取店铺信息失败:', response.error)
}
return null
} catch (error) {
console.error('获取店铺信息异常:', error)
return null
}
}
// 根据商户ID获取商品列表
async getProductsByMerchantId(merchantId: string, page: number = 1, limit: number = 20): Promise<PaginatedResponse<Product>> {
try {
console.log('getProductsByMerchantId querying for:', merchantId)
// 1. Try fetching from view strictly first
let query = supa
.from('ml_products_detail_view')
.select('*', { count: 'exact' })
.eq('merchant_id', merchantId)
// .eq('status', 1) // Temporarily disabled status check to see if products exist at all
.order('created_at', { ascending: false })
.page(page)
.limit(limit)
const response = await query.execute()
// 检查视图结果:如果有错误 OR 数据为空,都尝试去查原始表
if (response.error != null || (response.data != null && (response.data as any[]).length === 0)) {
if (response.error != null) {
console.error('获取商户商品失败 (View):', response.error)
} else {
console.log('View returned 0 products, trying raw table fallback...')
}
// Fallback: Try raw table
console.log('Falling back to raw ml_products table...')
const query2 = supa
.from('ml_products')
.select('*', { count: 'exact' })
.eq('merchant_id', merchantId)
// .eq('status', 1) // Also disabled here
.order('created_at', { ascending: false })
.page(page)
.limit(limit)
const res2 = await query2.execute()
if (res2.error != null) {
console.error('获取商户商品失败 (Raw):', res2.error)
return {data:[] as Product[], total:0, page, limit, hasmore:false}
}
console.log(`Fallback (Raw) found: ${(res2.data as any[]).length} products`)
const mappedData: Product[] = []
const rawData = res2.data as any[]
for(let i = 0; i < rawData.length; i++) {
const item = rawData[i] as UTSJSONObject
const images: string[] = []
const mainImageUrl = item.getString('main_image_url')
if (mainImageUrl != null && mainImageUrl !== '') {
images.push(mainImageUrl)
}
const imageUrlsRaw = item.get('image_urls')
if (imageUrlsRaw != null) {
try {
if (Array.isArray(imageUrlsRaw)) {
const arr = imageUrlsRaw as string[]
if (arr.length > 0 && images.length === 0) {
for (let j = 0; j < arr.length; j++) {
images.push(arr[j])
}
}
} else {
const rawUrlStr = imageUrlsRaw as string
if (rawUrlStr.startsWith('[')) {
const parsed = JSON.parse(rawUrlStr)
if (Array.isArray(parsed) && images.length === 0) {
for (let j = 0; j < parsed.length; j++) {
images.push(parsed[j] as string)
}
}
} else {
if (images.indexOf(rawUrlStr) === -1) images.push(rawUrlStr)
}
}
} catch(e) {
console.error('解析图片数组失败:', e)
}
}
if (images.length === 0) {
images.push('/static/default-product.png')
}
let safePrice = item.getNumber('base_price')
if (safePrice == null) {
const p = item.getNumber('price')
safePrice = p != null ? p : 0
}
let safeOriginalPrice = item.getNumber('market_price')
if (safeOriginalPrice == null) {
const op = item.getNumber('original_price')
safeOriginalPrice = op != null ? op : safePrice
}
let safeStock = item.getNumber('total_stock')
if (safeStock == null) {
let as_ = item.getNumber('available_stock')
if (as_ == null) {
const s = item.getNumber('stock')
safeStock = s != null ? s : 0
} else {
safeStock = as_
}
}
let safeSales = item.getNumber('sale_count')
if (safeSales == null) {
const s = item.getNumber('sales')
safeSales = s != null ? s : 0
}
const product: Product = {
id: item.getString('id') ?? '',
category_id: item.getString('category_id') ?? '',
merchant_id: item.getString('merchant_id') ?? '',
name: item.getString('name') ?? '',
description: item.getString('description') ?? '',
images: images,
price: safePrice,
original_price: safeOriginalPrice,
stock: safeStock,
sales: safeSales,
status: item.getNumber('status') ?? 1,
created_at: item.getString('created_at') ?? '',
base_price: safePrice,
market_price: safeOriginalPrice,
main_image_url: images.length > 0 ? images[0] : '',
sale_count: safeSales,
total_stock: safeStock
} as Product
mappedData.push(product)
}
return {
data: mappedData,
total: res2.total ?? 0,
page,
limit,
hasmore: res2.hasmore ?? false
}
}
console.log(`Merchant products found: ${(response.data as any[]).length}`)
return {
data: response.data as Product[],
total: response.total ?? 0,
page,
limit,
hasmore: response.hasmore ?? false
}
} catch (error) {
console.error('获取商户商品异常:', error)
return {
data: [] as Product[],
total: 0,
page,
limit,
hasmore: false
}
}
}
// 根据店铺ID获取商品列表新增
async getProductsByShopId(shopId: string, page: number = 1, limit: number = 20): Promise<PaginatedResponse<Product>> {
try {
console.log('getProductsByShopId querying for:', shopId)
// 1. Try fetching from view with shop_id
let query = supa
.from('ml_products_detail_view')
.select('*', { count: 'exact' })
.eq('shop_id', shopId)
.order('created_at', { ascending: false })
.page(page)
.limit(limit)
const response = await query.execute()
// 检查视图结果:如果有错误 OR 数据为空,都尝试去查原始表
if (response.error != null || (response.data != null && (response.data as any[]).length === 0)) {
if (response.error != null) {
console.error('获取店铺商品失败 (View):', response.error)
} else {
console.log('View returned 0 products, trying raw table fallback...')
}
// Fallback: Try raw table with shop_id
console.log('Falling back to raw ml_products table with shop_id...')
const query2 = supa
.from('ml_products')
.select('*', { count: 'exact' })
.eq('shop_id', shopId)
.order('created_at', { ascending: false })
.page(page)
.limit(limit)
const res2 = await query2.execute()
if (res2.error != null) {
console.error('获取店铺商品失败 (Raw):', res2.error)
return {data:[] as Product[], total:0, page, limit, hasmore:false}
}
console.log(`Fallback (Raw) found: ${(res2.data as any[]).length} products`)
const mappedData: Product[] = []
const rawData = res2.data as any[]
for(let i = 0; i < rawData.length; i++) {
const item = rawData[i] as UTSJSONObject
const images: string[] = []
const mainImageUrl = item.getString('main_image_url')
if (mainImageUrl != null && mainImageUrl !== '') {
images.push(mainImageUrl)
}
const imageUrlsRaw = item.get('image_urls')
if (imageUrlsRaw != null) {
try {
if (Array.isArray(imageUrlsRaw)) {
const arr = imageUrlsRaw as string[]
if (arr.length > 0 && images.length === 0) {
for (let j = 0; j < arr.length; j++) {
images.push(arr[j])
}
}
} else {
const rawUrlStr = imageUrlsRaw as string
if (rawUrlStr.startsWith('[')) {
const parsed = JSON.parse(rawUrlStr)
if (Array.isArray(parsed) && images.length === 0) {
for (let j = 0; j < parsed.length; j++) {
images.push(parsed[j] as string)
}
}
} else {
if (images.indexOf(rawUrlStr) === -1) images.push(rawUrlStr)
}
}
} catch(e) {
console.error('解析图片数组失败:', e)
}
}
if (images.length === 0) {
images.push('/static/default-product.png')
}
let safePrice = item.getNumber('base_price')
if (safePrice == null) {
const p = item.getNumber('price')
safePrice = p != null ? p : 0
}
let safeOriginalPrice = item.getNumber('market_price')
if (safeOriginalPrice == null) {
const op = item.getNumber('original_price')
safeOriginalPrice = op != null ? op : safePrice
}
let safeStock = item.getNumber('total_stock')
if (safeStock == null) {
let as_ = item.getNumber('available_stock')
if (as_ == null) {
const s = item.getNumber('stock')
safeStock = s != null ? s : 0
} else {
safeStock = as_
}
}
let safeSales = item.getNumber('sale_count')
if (safeSales == null) {
const s = item.getNumber('sales')
safeSales = s != null ? s : 0
}
const product: Product = {
id: item.getString('id') ?? '',
category_id: item.getString('category_id') ?? '',
merchant_id: item.getString('merchant_id') ?? '',
name: item.getString('name') ?? '',
description: item.getString('description') ?? '',
images: images,
price: safePrice,
original_price: safeOriginalPrice,
stock: safeStock,
sales: safeSales,
status: item.getNumber('status') ?? 1,
created_at: item.getString('created_at') ?? '',
base_price: safePrice,
market_price: safeOriginalPrice,
main_image_url: images.length > 0 ? images[0] : '',
sale_count: safeSales,
total_stock: safeStock
} as Product
mappedData.push(product)
}
return {
data: mappedData,
total: res2.total ?? 0,
page,
limit,
hasmore: res2.hasmore ?? false
}
}
console.log(`Shop products found: ${(response.data as any[]).length}`)
return {
data: response.data as Product[],
total: response.total ?? 0,
page,
limit,
hasmore: response.hasmore ?? false
}
} catch (error) {
console.error('获取店铺商品异常:', error)
return {
data: [] as Product[],
total: 0,
page,
limit,
hasmore: false
}
}
}
// 获取热销商品(按销量排序)
async getHotProducts(limit: number = 10): Promise<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 != null) {
console.error('获取热销商品失败:', response.error)
return []
}
const rawData = response.data
if (rawData == null) {
return []
}
const products: Product[] = []
const rawList = rawData as any[]
for (let i: number = 0; i < rawList.length; i++) {
const item = rawList[i]
const prodObj = item as UTSJSONObject
const imagesRaw = prodObj.getArray('images')
let safePrice = prodObj.getNumber('base_price')
if (safePrice == null) {
const p = prodObj.getNumber('price')
safePrice = p != null ? p : 0
}
let safeOriginalPrice = prodObj.getNumber('market_price')
if (safeOriginalPrice == null) {
const op = prodObj.getNumber('original_price')
safeOriginalPrice = op != null ? op : safePrice
}
const product: Product = {
id: prodObj.getString('id') ?? '',
name: prodObj.getString('name') ?? '',
description: prodObj.getString('description') ?? '',
base_price: safePrice,
price: safePrice,
original_price: safeOriginalPrice,
market_price: safeOriginalPrice,
main_image_url: prodObj.getString('main_image_url') ?? prodObj.getString('image_url') ?? '',
image_url: prodObj.getString('image_url') ?? '',
images: imagesRaw != null ? (imagesRaw as string[]) : [] as string[],
category_id: prodObj.getString('category_id') ?? '',
brand_id: prodObj.getString('brand_id') ?? '',
shop_id: prodObj.getString('shop_id') ?? '',
stock: prodObj.getNumber('stock') ?? 0,
sale_count: prodObj.getNumber('sale_count') ?? 0,
status: prodObj.getNumber('status') ?? 1,
is_featured: prodObj.getBoolean('is_featured') ?? false,
is_new: prodObj.getBoolean('is_new') ?? false,
rating: prodObj.getNumber('rating') ?? 0,
review_count: prodObj.getNumber('review_count') ?? 0,
merchant_id: prodObj.getString('merchant_id') ?? ''
} as Product
products.push(product)
}
return products
} catch (error) {
console.error('获取热销商品异常:', error)
return []
}
}
// 获取按价格排序的商品(升序:从低到高)
async getProductsByPrice(limit: number = 10, ascending: boolean = true): Promise<Product[]> {
try {
const response = await supa
.from('ml_products_detail_view')
.select('*')
.eq('status', 1)
.order('base_price', { ascending })
.limit(limit)
.execute()
if (response.error != null) {
console.error('获取价格排序商品失败:', response.error)
return []
}
const rawData = response.data
if (rawData == null) {
return []
}
const products: Product[] = []
const rawList = rawData as any[]
for (let i: number = 0; i < rawList.length; i++) {
const item = rawList[i]
const prodObj = item as UTSJSONObject
const imagesRaw = prodObj.getArray('images')
let safePrice = prodObj.getNumber('base_price')
if (safePrice == null) {
const p = prodObj.getNumber('price')
safePrice = p != null ? p : 0
}
let safeOriginalPrice = prodObj.getNumber('market_price')
if (safeOriginalPrice == null) {
const op = prodObj.getNumber('original_price')
safeOriginalPrice = op != null ? op : safePrice
}
const product: Product = {
id: prodObj.getString('id') ?? '',
name: prodObj.getString('name') ?? '',
description: prodObj.getString('description') ?? '',
base_price: safePrice,
price: safePrice,
original_price: safeOriginalPrice,
market_price: safeOriginalPrice,
main_image_url: prodObj.getString('main_image_url') ?? prodObj.getString('image_url') ?? '',
image_url: prodObj.getString('image_url') ?? '',
images: imagesRaw != null ? (imagesRaw as string[]) : [] as string[],
category_id: prodObj.getString('category_id') ?? '',
brand_id: prodObj.getString('brand_id') ?? '',
shop_id: prodObj.getString('shop_id') ?? '',
stock: prodObj.getNumber('stock') ?? 0,
sale_count: prodObj.getNumber('sale_count') ?? 0,
status: prodObj.getNumber('status') ?? 1,
is_featured: prodObj.getBoolean('is_featured') ?? false,
is_new: prodObj.getBoolean('is_new') ?? false,
rating: prodObj.getNumber('rating') ?? 0,
review_count: prodObj.getNumber('review_count') ?? 0,
merchant_id: prodObj.getString('merchant_id') ?? ''
} as Product
products.push(product)
}
return products
} 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 })
.limit(limit)
.execute()
if (response.error != null) {
console.error('获取新品失败:', response.error)
return []
}
const rawData = response.data
if (rawData == null) {
return []
}
const products: Product[] = []
const rawList = rawData as any[]
for (let i: number = 0; i < rawList.length; i++) {
const item = rawList[i]
const prodObj = item as UTSJSONObject
const imagesRaw = prodObj.getArray('images')
let safePrice = prodObj.getNumber('base_price')
if (safePrice == null) {
const p = prodObj.getNumber('price')
safePrice = p != null ? p : 0
}
let safeOriginalPrice = prodObj.getNumber('market_price')
if (safeOriginalPrice == null) {
const op = prodObj.getNumber('original_price')
safeOriginalPrice = op != null ? op : safePrice
}
const product: Product = {
id: prodObj.getString('id') ?? '',
name: prodObj.getString('name') ?? '',
description: prodObj.getString('description') ?? '',
base_price: safePrice,
price: safePrice,
original_price: safeOriginalPrice,
market_price: safeOriginalPrice,
main_image_url: prodObj.getString('main_image_url') ?? prodObj.getString('image_url') ?? '',
image_url: prodObj.getString('image_url') ?? '',
images: imagesRaw != null ? (imagesRaw as string[]) : [] as string[],
category_id: prodObj.getString('category_id') ?? '',
brand_id: prodObj.getString('brand_id') ?? '',
shop_id: prodObj.getString('shop_id') ?? '',
stock: prodObj.getNumber('stock') ?? 0,
sale_count: prodObj.getNumber('sale_count') ?? 0,
status: prodObj.getNumber('status') ?? 1,
is_featured: prodObj.getBoolean('is_featured') ?? false,
is_new: prodObj.getBoolean('is_new') ?? false,
rating: prodObj.getNumber('rating') ?? 0,
review_count: prodObj.getNumber('review_count') ?? 0,
merchant_id: prodObj.getString('merchant_id') ?? ''
} as Product
products.push(product)
}
return products
} 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 != null) {
console.error('获取推荐商品失败:', response.error)
return []
}
const rawData = response.data
if (rawData == null) {
return []
}
const products: Product[] = []
const rawList = rawData as any[]
for (let i: number = 0; i < rawList.length; i++) {
const item = rawList[i]
const prodObj = item as UTSJSONObject
const imagesRaw = prodObj.getArray('images')
let safePrice = prodObj.getNumber('base_price')
if (safePrice == null) {
const p = prodObj.getNumber('price')
safePrice = p != null ? p : 0
}
let safeOriginalPrice = prodObj.getNumber('market_price')
if (safeOriginalPrice == null) {
const op = prodObj.getNumber('original_price')
safeOriginalPrice = op != null ? op : safePrice
}
const product: Product = {
id: prodObj.getString('id') ?? '',
name: prodObj.getString('name') ?? '',
description: prodObj.getString('description') ?? '',
base_price: safePrice,
price: safePrice,
original_price: safeOriginalPrice,
market_price: safeOriginalPrice,
main_image_url: prodObj.getString('main_image_url') ?? prodObj.getString('image_url') ?? '',
image_url: prodObj.getString('image_url') ?? '',
images: imagesRaw != null ? (imagesRaw as string[]) : [] as string[],
category_id: prodObj.getString('category_id') ?? '',
brand_id: prodObj.getString('brand_id') ?? '',
shop_id: prodObj.getString('shop_id') ?? '',
stock: prodObj.getNumber('stock') ?? 0,
sale_count: prodObj.getNumber('sale_count') ?? 0,
status: prodObj.getNumber('status') ?? 1,
is_featured: prodObj.getBoolean('is_featured') ?? false,
is_new: prodObj.getBoolean('is_new') ?? false,
rating: prodObj.getNumber('rating') ?? 0,
review_count: prodObj.getNumber('review_count') ?? 0,
merchant_id: prodObj.getString('merchant_id') ?? ''
} as Product
products.push(product)
}
return products
} catch (error) {
console.error('获取推荐商品异常:', error)
return []
}
}
// 获取特价商品这里假设没有specific flag, just use logic or tag if exists, defaulting to hot for now or just skip
// Modify to use compatible logic if badge column doesn't exist
async getDiscountProducts(limit: number = 10): Promise<Product[]> {
return [] as Product[] // 暂无特价字段
}
// 获取当前用户的购物车商品(关联商品和店铺信息)
async getCartItems(): Promise<CartItem[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.warn('用户未登录,无法获取购物车')
return []
}
// 查询购物车表,并关联商品表(使用内联关联)
// 注意:使用 !inner 进行内连接,或者 left join (默认)
// 修改查询语法以符合 PostgREST 规范
// 尝试简化查询,先只查购物车,再查商品,避免复杂的嵌套查询导致 400 错误
const response = await supa
.from('ml_shopping_cart')
.select('*')
.eq('user_id', userId)
.order('created_at', { ascending: false })
.execute()
if (response.error != null) {
console.error('获取购物车失败:', response.error)
return []
}
const cartData = response.data as any[]
// console.log('Raw Cart Data:', JSON.stringify(cartData))
if (cartData == null || cartData.length === 0) {
return []
}
// 收集所有 product_id 和 sku_id
const productIds: string[] = []
const skuIds: string[] = []
for (let i = 0; i < cartData.length; i++) {
let item = cartData[i]
let pid: string = ''
let sid: string = ''
if (item instanceof UTSJSONObject) {
pid = item.getString('product_id') ?? ''
sid = item.getString('sku_id') ?? ''
} else {
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
pid = itemObj.getString('product_id') ?? ''
sid = itemObj.getString('sku_id') ?? ''
}
if (pid !== '' && !productIds.includes(pid)) {
productIds.push(pid)
}
if (sid !== '' && !skuIds.includes(sid)) {
skuIds.push(sid)
}
}
// 批量查询商品详情 (使用视图关联店铺信息,修复字段名 specification -> attributes)
const productMap = new Map<string, any>()
if (productIds.length > 0) {
// Convert string array to any array for .in()
const productIdsAny: any[] = []
for(let i=0; i<productIds.length; i++) {
productIdsAny.push(productIds[i])
}
const productRes = await supa
.from('ml_products_detail_view')
.select('id,name,main_image_url,base_price,attributes,merchant_id,shop_name')
.in('id', productIdsAny)
.execute()
if (productRes.error == null && 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 {
const pObj = JSON.parse(JSON.stringify(p)) as UTSJSONObject
pid = pObj.getString('id') ?? ''
}
if (pid !== '') {
productMap.set(pid, p)
}
}
}
}
// 批量查询 SKU 详情
const skuMap = new Map<string, any>()
if (skuIds.length > 0) {
const skuIdsAny: any[] = []
for(let i=0; i<skuIds.length; i++) {
skuIdsAny.push(skuIds[i])
}
const skuRes = await supa
.from('ml_product_skus')
.select('id, specifications, price, image_url')
.in('id', skuIdsAny)
.execute()
if (skuRes.error == null && skuRes.data != null) {
const skus = skuRes.data as any[]
for (let i = 0; i < skus.length; i++) {
let s = skus[i]
let sid: string = ''
if (s instanceof UTSJSONObject) {
sid = s.getString('id') ?? ''
} else {
const sObj = JSON.parse(JSON.stringify(s)) as UTSJSONObject
sid = sObj.getString('id') ?? ''
}
if (sid !== '') {
skuMap.set(sid, s)
}
}
}
}
// 处理返回数据构建CartItem数组
const cartItems: CartItem[] = []
if ((cartData as any[]) != null) { // Simplify: cartData is already any[]
const cartArray = cartData as any[]
for (let i = 0; i < cartArray.length; i++) {
let item = cartArray[i]
let itemId: string = ''
let userIdVal: string = ''
let productId: string = ''
let skuId: string = ''
let quantity: number = 0
let selected: boolean = false
let createdAt: string = ''
let updatedAt: string = ''
let cartMerchantId: string = ''
if (item instanceof UTSJSONObject) {
itemId = item.getString('id') ?? ''
userIdVal = item.getString('user_id') ?? ''
productId = item.getString('product_id') ?? ''
skuId = item.getString('sku_id') ?? ''
quantity = item.getNumber('quantity') ?? 0
selected = item.getBoolean('selected') ?? false
createdAt = item.getString('created_at') ?? ''
updatedAt = item.getString('updated_at') ?? ''
cartMerchantId = item.getString('merchant_id') ?? ''
} else {
const iObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
itemId = iObj.getString('id') ?? ''
userIdVal = iObj.getString('user_id') ?? ''
productId = iObj.getString('product_id') ?? ''
skuId = iObj.getString('sku_id') ?? ''
quantity = iObj.getNumber('quantity') ?? 0
selected = iObj.getBoolean('selected') ?? false
createdAt = iObj.getString('created_at') ?? ''
updatedAt = iObj.getString('updated_at') ?? ''
cartMerchantId = iObj.getString('merchant_id') ?? ''
}
const product = productMap.get(productId)
const sku = (skuId !== '' && skuMap.has(skuId)) ? skuMap.get(skuId) : null
let merchantId: string = cartMerchantId
let productName: string = ''
let productImage: string = ''
let productPrice: number = 0
let productSpec: string = ''
let shopNameStr: string = '未知店铺'
if (product != null) {
if (product instanceof UTSJSONObject) {
// 优先使用购物车中的 merchant_id如果没有则使用商品中的
if (merchantId == '') {
merchantId = product.getString('merchant_id') ?? ''
}
productName = product.getString('name') ?? ''
productImage = product.getString('main_image_url') ?? ''
productPrice = product.getNumber('base_price') ?? 0
shopNameStr = product.getString('shop_name') ?? '未知店铺'
// 只有当没有sku信息时才尝试使用商品的attributes作为降级显示
// 修改策略:不使用 product.attributes 作为规格显示,因为那通常包含品牌、风格等静态属性,非用户选择的规格
// 如果 sku 为空,则让前端显示默认"标准规格"
/*
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 {
const pObj = JSON.parse(JSON.stringify(product)) as UTSJSONObject
merchantId = pObj.getString('merchant_id') ?? ''
productName = pObj.getString('name') ?? ''
productImage = pObj.getString('main_image_url') ?? ''
productPrice = pObj.getNumber('base_price') ?? 0
shopNameStr = pObj.getString('shop_name') ?? '未知店铺'
// Same here: disable fallback to attributes
/*
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 sObj = JSON.parse(JSON.stringify(sku)) as UTSJSONObject
const skuPrice = sObj.getNumber('price') ?? 0
if (skuPrice > 0) productPrice = skuPrice
const skuImg = sObj.getString('image_url') ?? ''
if (skuImg !== '') productImage = skuImg
const specRaw = sObj.get('specifications')
if (specRaw != null) {
// 优先使用SKU的规格
if (typeof specRaw === 'string') {
productSpec = specRaw
} else if (specRaw instanceof UTSJSONObject) {
const keys = UTSJSONObject.keys(specRaw)
const parts: string[] = []
for(let k = 0; k < keys.length; k++) {
let val = specRaw.get(keys[k])
if (val != null) {
parts.push(`${keys[k]}: ${val}`)
}
}
productSpec = parts.join('; ')
} else {
try {
let jsonStr = JSON.stringify(specRaw)
productSpec = jsonStr.replace(/["{}]/g, '').replace(/,/g, '; ')
} catch (e) {}
}
}
}
}
let shopIdStr = merchantId != '' ? merchantId : 'unknown_shop'
cartItems.push({
id: itemId,
user_id: userIdVal,
product_id: productId,
sku_id: skuId,
merchant_id: merchantId,
quantity: quantity,
selected: selected,
product_name: productName,
product_image: productImage,
product_price: productPrice,
product_specification: productSpec,
shop_id: shopIdStr,
shop_name: shopNameStr,
created_at: createdAt,
updated_at: updatedAt
} as CartItem)
}
}
return cartItems
} catch (error) {
console.error('获取购物车异常:', error)
return []
}
}
// 获取用户通知 (系统、活动、订单)
async getUserNotifications(type: string | null = null): Promise<Notification[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return []
let query = supa
.from('ml_notifications')
.select('*')
.eq('user_id', userId)
if (type != null) {
query = query.eq('type', type)
}
const response = await query.order('created_at', { ascending: false }).execute()
if (response.error != null) {
console.error('获取通知失败:', response.error)
return []
}
return response.data as Notification[]
} catch (e) {
console.error('获取通知异常:', e)
return []
}
}
// 获取聊天会话列表
async getChatRooms(): Promise<ChatRoom[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return []
const response = await supa
.from('ml_chat_rooms')
.select('*')
.eq('user_id', userId)
.order('updated_at', { ascending: false })
.execute()
if (response.error != null) {
console.error('获取聊天会话失败:', response.error)
return []
}
return response.data as ChatRoom[]
} catch (e) {
console.error('获取聊天会话异常:', e)
return []
}
}
// 获取用户聊天消息
async getUserChatMessages(): Promise<ChatMessage[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return []
const response = await supa
.from('ml_chat_messages')
.select('*')
.or(`sender_id.eq.${userId},receiver_id.eq.${userId}`)
.order('created_at', { ascending: false })
.limit(50)
.execute()
if (response.error != null) {
console.error('获取聊天记录失败:', response.error)
return []
}
return response.data as ChatMessage[]
} catch (e) {
console.error('获取聊天记录异常:', e)
return []
}
}
// 获取与特定商家的聊天记录
async getChatMessages(merchantId: string): Promise<ChatMessage[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return []
const response = await supa
.from('ml_chat_messages')
.select('*')
.or(`and(sender_id.eq.${userId},receiver_id.eq.${merchantId}),and(sender_id.eq.${merchantId},receiver_id.eq.${userId})`)
.order('created_at', { ascending: false })
.limit(50)
.execute()
if (response.error != null) {
console.error('获取聊天记录失败:', response.error)
return []
}
return response.data as ChatMessage[]
} catch (e) {
console.error('获取聊天记录异常:', e)
return []
}
}
// 发送聊天消息
async sendChatMessage(content: string, toId: string | null = null, type: string = 'text'): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return false
const payload = {
sender_id: userId,
content: content,
msg_type: type,
is_from_user: true,
created_at: new Date().toISOString()
} as UTSJSONObject
if (toId != null) {
payload.set('receiver_id', toId)
}
const response = await supa
.from('ml_chat_messages')
.insert(payload)
.execute()
if (response.error != null) {
console.error('发送消息失败:', response.error)
return false
}
return true
} catch (e) {
console.error('发送消息异常:', e)
return false
}
}
// 模拟客服回复
async simulateServiceReply(content: string): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return false
const response = await supa
.from('ml_chat_messages')
.insert({
receiver_id: userId,
content: content,
msg_type: 'text',
is_from_user: false,
created_at: new Date().toISOString()
})
.execute()
return response.error == null
} catch (e) {
return false
}
}
// 添加商品到购物车
async addToCart(productId: string, quantity: number = 1, skuId: string = '', merchantId: string = ''): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.error('用户未登录,无法添加商品到购物车')
return false
}
const realSkuId = (skuId != null && skuId.length > 0) ? skuId : null
const realMerchantId = (merchantId != null && merchantId.length > 0) ? merchantId : null
// 检查商品是否已在购物车中
// 注意:必须处理 sku_id 为空的情况,使用 is.null 过滤器
let query = supa
.from('ml_shopping_cart')
.select('*')
.eq('user_id', userId)
.eq('product_id', productId)
if (realSkuId != null) {
query = query.eq('sku_id', realSkuId)
} else {
query = query.is('sku_id', null)
}
const existingResponse = await query.single().execute()
let existingItem: any | null = null
if (existingResponse.data != null) {
const rawData = existingResponse.data as any
if (Array.isArray(rawData)) {
if (rawData.length > 0) {
existingItem = rawData[0]
}
} else {
existingItem = rawData
}
}
let response: AkReqResponse<any>
if (existingItem != null) {
// 商品已存在,更新数量
console.log('Found existing cart item:', JSON.stringify(existingItem))
// 确保 existingItem.id 存在
let itemId: string | null = null
let itemQty: any | null = null
if (existingItem instanceof UTSJSONObject) {
itemId = existingItem.getString('id')
itemQty = existingItem.getNumber('quantity')
} else {
const obj = JSON.parse(JSON.stringify(existingItem)) as UTSJSONObject
itemId = obj.getString('id')
itemQty = obj.getNumber('quantity')
}
if (itemId != null) {
let currentQty = 0
if (typeof itemQty === 'number') {
currentQty = itemQty as number
} else {
const qStr = `${itemQty ?? 0}`
currentQty = parseInt(qStr)
}
const newQty = currentQty + quantity
response = await supa
.from('ml_shopping_cart')
.update({
quantity: newQty,
merchant_id: realMerchantId,
updated_at: new Date().toISOString()
})
.eq('id', itemId)
.execute()
} else {
console.error('购物车已有商品但缺少ID无法更新. Data:', JSON.stringify(existingItem))
return false
}
} else {
// 商品不存在,添加新记录
const cartPayload = new UTSJSONObject()
cartPayload.set('user_id', userId)
cartPayload.set('product_id', productId)
cartPayload.set('sku_id', realSkuId)
cartPayload.set('quantity', quantity)
cartPayload.set('selected', true)
cartPayload.set('created_at', new Date().toISOString())
cartPayload.set('updated_at', new Date().toISOString())
if (realMerchantId != null) {
cartPayload.set('merchant_id', realMerchantId)
}
response = await supa
.from('ml_shopping_cart')
.insert(cartPayload)
.execute()
}
if (response.error != null) {
console.error('添加商品到购物车失败:', response.error)
return false
}
return true
} catch (error) {
console.error('添加商品到购物车异常:', error)
return false
}
}
// 更新购物车商品数量
async updateCartItemQuantity(cartItemId: string, quantity: number): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.error('用户未登录,无法更新购物车')
return false
}
if (quantity < 1) {
// 数量小于1时删除商品
return await this.deleteCartItem(cartItemId)
}
const response = await supa
.from('ml_shopping_cart')
.update({
quantity: quantity,
updated_at: new Date().toISOString()
})
.eq('id', cartItemId)
.eq('user_id', userId)
.execute()
if (response.error != null) {
console.error('更新购物车商品数量失败:', response.error)
return false
}
return true
} catch (error) {
console.error('更新购物车商品数量异常:', error)
return false
}
}
// 更新购物车商品选中状态
async updateCartItemSelection(cartItemId: string, selected: boolean): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.error('用户未登录,无法更新购物车')
return false
}
const response = await supa
.from('ml_shopping_cart')
.update({
selected: selected,
updated_at: new Date().toISOString()
})
.eq('id', cartItemId)
.eq('user_id', userId)
.execute()
if (response.error != null) {
console.error('更新购物车商品选中状态失败:', response.error)
return false
}
return true
} catch (error) {
console.error('更新购物车商品选中状态异常:', error)
return false
}
}
// 批量更新购物车商品选中状态
async batchUpdateCartItemSelection(cartItemIds: string[], selected: boolean): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.error('用户未登录,无法更新购物车')
return false
}
const response = await supa
.from('ml_shopping_cart')
.update({
selected: selected,
updated_at: new Date().toISOString()
})
.eq('user_id', userId)
.in('id', cartItemIds as any[])
.execute()
if (response.error != null) {
console.error('批量更新购物车商品选中状态失败:', response.error)
return false
}
return true
} catch (error) {
console.error('批量更新购物车商品选中状态异常:', error)
return false
}
}
// 删除购物车商品
async deleteCartItem(cartItemId: string): Promise<boolean> {
try {
console.log('正在执行删除购物车商品ID:', cartItemId)
const userId = this.getCurrentUserId()
if (userId == null) {
console.error('用户未登录,无法删除购物车商品')
return false
}
const response = await supa
.from('ml_shopping_cart')
.eq('id', cartItemId)
.eq('user_id', userId)
.delete()
.execute()
if (response.error != null) {
console.error('删除购物车商品失败:', response.error)
return false
}
return true
} catch (error) {
console.error('删除购物车商品异常:', error)
return false
}
}
// 批量删除购物车商品
async batchDeleteCartItems(cartItemIds: string[]): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.error('用户未登录,无法删除购物车商品')
return false
}
const response = await supa
.from('ml_shopping_cart')
.eq('user_id', userId)
.in('id', cartItemIds as any[])
.delete()
.execute()
if (response.error != null) {
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 == null) {
console.error('用户未登录,无法清空购物车')
return false
}
const response = await supa
.from('ml_shopping_cart')
.eq('user_id', userId)
.delete()
.execute()
if (response.error != null) {
console.error('清空购物车失败:', response.error)
return false
}
return true
} catch (error) {
console.error('清空购物车异常:', error)
return false
}
}
// 获取当前用户的所有地址
async getAddresses(): Promise<UserAddress[]> {
const userId = this.getCurrentUserId()
if (userId == null) {
console.warn('[getAddresses] 用户未登录,无法获取地址')
return []
}
try {
console.log('[getAddresses] 查询地址, userId:', userId)
const response = await supa
.from('ml_user_addresses')
.select('*')
.eq('user_id', userId)
.order('is_default', { ascending: false })
.order('created_at', { ascending: false })
.execute()
console.log('[getAddresses] response.error:', response.error)
console.log('[getAddresses] response.data:', JSON.stringify(response.data))
if (response.error != null) {
console.error('[getAddresses] 获取地址失败:', response.error)
return []
}
const data = response.data
if (data == null) {
return []
}
const result: UserAddress[] = []
const rawData = data as any[]
for (let i = 0; i < rawData.length; i++) {
const item = rawData[i]
let itemObj: UTSJSONObject
if (item instanceof UTSJSONObject) {
itemObj = item as UTSJSONObject
} else {
itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
}
const addrObj = new UTSJSONObject()
addrObj.set('id', itemObj.getString('id') ?? '')
addrObj.set('user_id', itemObj.getString('user_id') ?? '')
addrObj.set('recipient_name', itemObj.getString('receiver_name') ?? itemObj.getString('recipient_name') ?? '')
addrObj.set('phone', itemObj.getString('receiver_phone') ?? itemObj.getString('phone') ?? '')
addrObj.set('province', itemObj.getString('province') ?? '')
addrObj.set('city', itemObj.getString('city') ?? '')
addrObj.set('district', itemObj.getString('district') ?? '')
addrObj.set('detail_address', itemObj.getString('address_detail') ?? itemObj.getString('detail_address') ?? '')
addrObj.set('is_default', itemObj.getBoolean('is_default') ?? false)
result.push(addrObj as UserAddress)
}
console.log('[getAddresses] 返回地址数量:', result.length)
return result
} catch (error) {
console.error('[getAddresses] 获取地址异常:', error)
return []
}
}
// 根据ID获取地址详情
async getAddressById(addressId: string): Promise<UserAddress | null> {
const userId = this.getCurrentUserId()
if (userId == null) {
console.warn('用户未登录,无法获取地址')
return null
}
try {
const query = supa
.from('ml_user_addresses')
.select('*, recipient_name:receiver_name, phone:receiver_phone, detail_address:address_detail')
.eq('id', addressId)
.eq('user_id', userId)
.single()
const response = await query.execute()
if (response.error != null) {
console.error('获取地址详情失败:', response.error)
return null
}
return response.data as UserAddress
} catch (error) {
console.error('获取地址详情异常:', error)
return null
}
}
// 添加新地址
async addAddress(address: AddAddressParams): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.error('用户未登录,无法添加地址')
return false
}
// 如果设置为默认地址,需要先取消其他默认地址
if (address.is_default == true) {
await this.clearDefaultAddress(userId)
}
const response = await supa
.from('ml_user_addresses')
.insert({
user_id: userId,
receiver_name: address.recipient_name,
receiver_phone: address.phone,
province: address.province,
city: address.city,
district: address.district,
address_detail: address.detail_address,
postal_code: address.postal_code ?? null,
is_default: address.is_default ?? false,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
})
.execute()
if (response.error != null) {
console.error('添加地址失败:', response.error)
return false
}
return true
} catch (error) {
console.error('添加地址异常:', error)
return false
}
}
// 更新地址
async updateAddress(addressId: string, address: UpdateAddressParams): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.error('用户未登录,无法更新地址')
return false
}
// 如果设置为默认地址,需要先取消其他默认地址
if (address.is_default == true) {
await this.clearDefaultAddress(userId)
}
// 构造更新数据,映射字段名到数据库列名
const updateData = {}
if (address.recipient_name != null) updateData['receiver_name'] = address.recipient_name
if (address.phone != null) updateData['receiver_phone'] = address.phone
if (address.province != null) updateData['province'] = address.province
if (address.city != null) updateData['city'] = address.city
if (address.district != null) updateData['district'] = address.district
if (address.detail_address != null) updateData['address_detail'] = address.detail_address
if (address.postal_code != null) updateData['postal_code'] = address.postal_code
if (address.is_default != null) updateData['is_default'] = address.is_default
if (address.label != null) updateData['label'] = address.label
updateData['updated_at'] = new Date().toISOString()
const response = await supa
.from('ml_user_addresses')
.update(updateData)
.eq('id', addressId)
.eq('user_id', userId)
.execute()
if (response.error != null) {
console.error('更新地址失败:', response.error)
return false
}
return true
} catch (error) {
console.error('更新地址异常:', error)
return false
}
}
// 确认收货
async confirmReceipt(orderId: string): Promise<ConfirmReceiptResponse> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
return { success: false, error: '用户未登录' }
}
const response = await supa
.from('ml_orders')
.update({
order_status: 4, // 4: 已完成
delivered_at: new Date().toISOString(),
completed_at: new Date().toISOString(), // 也更新完成时间
updated_at: new Date().toISOString()
})
.eq('id', orderId)
.eq('user_id', userId)
.execute()
if (response.error != null) {
return { success: false, error: response.error.message }
}
return { success: true }
} catch (e: any) {
return { success: false, error: e.message }
}
}
// 删除地址
async deleteAddress(addressId: string): Promise<boolean> {
try {
console.log('正在执行删除地址ID:', addressId)
const userId = this.getCurrentUserId()
if (userId == null) {
console.error('用户未登录,无法删除地址')
return false
}
const response = await supa
.from('ml_user_addresses')
.eq('id', addressId)
.eq('user_id', userId)
.delete()
.execute()
if (response.error != null) {
console.error('删除地址失败:', response.error)
return false
}
return true
} catch (error) {
console.error('删除地址异常:', error)
return false
}
}
// 清除默认地址(内部使用)
private async clearDefaultAddress(userId: string): Promise<void> {
try {
await supa
.from('ml_user_addresses')
.update({
is_default: false,
updated_at: new Date().toISOString()
})
.eq('user_id', userId)
.eq('is_default', true)
.execute()
} catch (error) {
console.error('清除默认地址异常:', error)
}
}
// 获取用户资料
async getUserProfile(): Promise<any | null> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return null
// 联合查询 auth user 和 profile
// 由于 Supabase auth table 不可直接访问,这里查询 ml_user_profiles
const response = await supa
.from('ml_user_profiles')
.select('*')
.eq('user_id', userId)
.single()
.execute()
if (response.error != null) {
// 如果不存在 profile可能只有 auth user这里暂时返回空或创建默认
return null
}
return response.data
} catch (e) {
return null
}
}
// 创建订单
async createOrder(orderData: CreateOrderParams): Promise<string | null> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.error('CreateOrder: User not logged in')
return null
}
const orderNo = 'ML' + Date.now() + Math.floor(Math.random() * 1000)
let merchantId = orderData.merchant_id
console.log('[CreateOrder] 原始 merchant_id:', merchantId)
if (merchantId == null || merchantId == '' || merchantId == 'unknown') {
console.warn('[CreateOrder] merchant_id 为空或无效,将使用 userId 作为 fallback')
merchantId = userId
}
console.log('[CreateOrder] 最终使用的 merchant_id:', merchantId)
let shippingAddrStr = '{}'
if (orderData.shipping_address != null) {
if (typeof orderData.shipping_address === 'string') {
shippingAddrStr = orderData.shipping_address
} else {
shippingAddrStr = JSON.stringify(orderData.shipping_address)
}
}
const orderPayload = new UTSJSONObject()
orderPayload.set('user_id', userId)
orderPayload.set('merchant_id', merchantId)
orderPayload.set('order_no', orderNo)
orderPayload.set('product_amount', orderData.product_amount)
orderPayload.set('shipping_fee', orderData.shipping_fee)
orderPayload.set('total_amount', orderData.total_amount)
orderPayload.set('paid_amount', 0)
orderPayload.set('shipping_address', shippingAddrStr)
orderPayload.set('order_status', 1)
orderPayload.set('payment_status', 1)
orderPayload.set('shipping_status', 1)
orderPayload.set('created_at', new Date().toISOString())
orderPayload.set('updated_at', new Date().toISOString())
console.log('[CreateOrder] 插入订单数据:', JSON.stringify(orderPayload))
console.log('[CreateOrder] 期望的订单号:', orderNo)
const orderResponse = await supa
.from('ml_orders')
.insert(orderPayload)
.execute()
console.log('[CreateOrder] insert 完成')
console.log('[CreateOrder] orderResponse.error:', orderResponse.error)
if (orderResponse.error != null) {
console.error('[CreateOrder] 创建订单失败:', orderResponse.error)
return null
}
console.log('[CreateOrder] 开始查询新创建的订单, order_no:', orderNo)
const queryResponse = await supa
.from('ml_orders')
.select('id, order_no')
.eq('order_no', orderNo)
.execute()
console.log('[CreateOrder] queryResponse.error:', queryResponse.error)
console.log('[CreateOrder] queryResponse.data:', JSON.stringify(queryResponse.data))
if (queryResponse.error != null) {
console.error('[CreateOrder] 查询订单失败:', queryResponse.error)
return null
}
const queryData = queryResponse.data as any
let orderId = ''
if (Array.isArray(queryData) && queryData.length > 0) {
const firstItem = queryData[0] as Record<string, any>
orderId = firstItem['id'] as string
console.log('[CreateOrder] 找到新创建的订单, id:', orderId)
} else {
console.error('[CreateOrder] 未找到新创建的订单,插入可能失败')
return null
}
console.log('[CreateOrder] 订单创建成功, orderId:', orderId)
const orderItems: UTSJSONObject[] = []
const rawItems = orderData.items as any[]
for(let i = 0; i < rawItems.length; i++) {
let item: UTSJSONObject
const rawItem = rawItems[i]
item = rawItem as UTSJSONObject
const itemJson = new UTSJSONObject()
let pId = item.get('product_id')
if (pId == null) {
pId = item.get('id')
}
itemJson.set('order_id', orderId)
itemJson.set('product_id', pId)
const skuIdVal = item.get('sku_id')
if (skuIdVal != null && skuIdVal !== '') {
itemJson.set('sku_id', skuIdVal)
}
itemJson.set('product_name', item.get('product_name') ?? '')
const sName = item.get('sku_name')
itemJson.set('sku_name', sName ?? '')
const specVal = item.get('specifications')
let skuSnapshot = '{}'
if (specVal != null) {
if (typeof specVal === 'string') {
skuSnapshot = specVal as string
} else {
skuSnapshot = JSON.stringify(specVal)
}
}
itemJson.set('sku_snapshot', skuSnapshot)
itemJson.set('specifications', skuSnapshot)
const img1 = item.get('product_image')
const img2 = item.get('image_url')
let imgUrl = (img1 ?? img2 ?? '') as string
while (imgUrl.indexOf('`') >= 0) {
imgUrl = imgUrl.replace('`', '')
}
itemJson.set('image_url', imgUrl)
const iPrice = item.getNumber('price') ?? 0
const iQty = item.getNumber('quantity') ?? 1
itemJson.set('price', iPrice)
itemJson.set('quantity', iQty)
itemJson.set('total_amount', iPrice * iQty)
itemJson.set('created_at', new Date().toISOString())
orderItems.push(itemJson)
}
console.log('[CreateOrder] 插入订单项数量:', orderItems.length)
console.log('[CreateOrder] 订单项数据:', JSON.stringify(orderItems))
for (let j: number = 0; j < orderItems.length; j++) {
const itemJson = orderItems[j]
const itemsResponse = await supa
.from('ml_order_items')
.insert(itemJson)
.execute()
if (itemsResponse.error != null) {
console.error('[CreateOrder] 创建订单项失败:', itemsResponse.error)
}
}
console.log('[CreateOrder] 订单项创建成功')
const cartItemIds: string[] = []
for(let i = 0; i < rawItems.length; i++) {
const item = rawItems[i] as UTSJSONObject
const iid = item.getString('id')
if (iid != null && iid.length > 10) {
cartItemIds.push(iid)
}
}
if (cartItemIds.length > 0) {
await this.batchDeleteCartItems(cartItemIds)
}
return orderId
} catch (error) {
console.error('[CreateOrder] 创建订单异常:', error)
return null
}
}
// 批量通过店铺创建订单
async createOrdersByShop(params: ShopOrderParams): Promise<ShopOrderResponse> {
try {
const orderIds: string[] = []
const groups = params.shopGroups as any[]
let grandTotal = 0.0
for(let k = 0; k < groups.length; k++) {
const g = groups[k] as UTSJSONObject
// 安全获取 items 数组
const gItemsRaw = g.get('items')
if (gItemsRaw == null) continue
const gItems = gItemsRaw as any[]
for(let gi = 0; gi < gItems.length; gi++) {
const it = gItems[gi] as UTSJSONObject
const itPrice = it.getNumber('price') ?? 0
const itQty = it.getNumber('quantity') ?? 1
grandTotal += itPrice * itQty
}
}
// 为每个店铺创建一个订单
for (let i = 0; i < groups.length; i++) {
const group = groups[i] as UTSJSONObject
const shopItemsRaw = group.get('items')
if (shopItemsRaw == null) continue
const shopItems = shopItemsRaw as any[]
let productAmount = 0.0
for(let j = 0; j < shopItems.length; j++) {
const sItem = shopItems[j] as UTSJSONObject
const siPrice = sItem.getNumber('price') ?? 0
const siQty = sItem.getNumber('quantity') ?? 1
productAmount += siPrice * siQty
}
// 简单平摊运费和优惠 (实际逻辑可能更复杂)
const ratio = grandTotal > 0 ? (productAmount / grandTotal) : 0
const shopShippingFee = params.deliveryFee * ratio
const shopDiscount = params.discountAmount * ratio
const shopTotal = productAmount + shopShippingFee - shopDiscount
const mId = group.getString('merchant_id')
const sId = group.getString('shopId')
const shopName = group.getString('shopName')
console.log('[createOrdersByShop] 店铺组信息:', {
merchant_id: mId,
shopId: sId,
shopName: shopName
})
const finalMerchantId = (mId != null && mId != '') ? mId : (sId ?? '')
console.log('[createOrdersByShop] 最终使用的 merchant_id:', finalMerchantId)
const orderId = await this.createOrder({
merchant_id: finalMerchantId,
product_amount: productAmount,
shipping_fee: shopShippingFee,
total_amount: shopTotal,
shipping_address: params.shipping_address,
items: shopItems
})
if (orderId != null) {
orderIds.push(orderId)
} else {
return { success: false, orderIds, error: `店铺 ${shopName} 订单创建失败` }
}
}
return { success: true, orderIds }
} catch (e) {
console.error('批量创建订单异常:', e)
return { success: false, orderIds: [], error: '系统异常' }
}
}
// 获取订单列表
async getOrders(status: number = 0): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: any[] = []
return empty
}
let query = supa
.from('ml_orders')
.select(`
*,
ml_order_items (*)
`)
.eq('user_id', userId)
.order('created_at', { ascending: false })
if (status > 0) {
query = query.eq('order_status', status)
}
const response = await query.execute()
if (response.error != null) {
console.error('获取订单列表失败:', response.error)
const empty: any[] = []
return empty
}
const data = response.data
if (data == null) {
const empty: any[] = []
return empty
}
return data as any[]
} catch (error) {
console.error('获取订单列表异常:', error)
const empty: any[] = []
return empty
}
}
// 获取订单详情
async getOrderDetail(orderId: string): Promise<any | null> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return null
const response = await supa
.from('ml_orders')
.select(`
*,
ml_order_items (*)
`)
.eq('id', orderId)
.eq('user_id', userId)
.single()
.execute()
if (response.error != null) {
return null
}
return response.data
} catch (e) {
return null
}
}
// 支付订单
async payOrder(orderId: string, paymentMethod: string, amount: number): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.error('[payOrder] 用户未登录')
return false
}
console.log('[payOrder] 开始更新订单状态, orderId:', orderId, 'userId:', userId)
const updatePayload = new UTSJSONObject()
updatePayload.set('order_status', 2)
updatePayload.set('payment_status', 1)
updatePayload.set('payment_method', paymentMethod)
updatePayload.set('payment_time', new Date().toISOString())
updatePayload.set('paid_amount', amount)
updatePayload.set('updated_at', new Date().toISOString())
console.log('[payOrder] 更新数据:', JSON.stringify(updatePayload))
const response = await supa
.from('ml_orders')
.update(updatePayload)
.eq('id', orderId)
.eq('user_id', userId)
.execute()
if (response.error != null) {
console.error('[payOrder] 更新订单失败:', response.error)
return false
}
console.log('[payOrder] 订单状态更新成功')
if (paymentMethod === 'balance') {
console.log('[payOrder] 余额支付,暂不扣减余额')
}
return true
} catch (e) {
console.error('[payOrder] 支付异常:', e)
return false
}
}
// 根据ID获取订单信息
async getOrderById(orderId: string): Promise<UTSJSONObject | null> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.error('[getOrderById] 用户未登录')
return null
}
console.log('[getOrderById] 查询订单, orderId:', orderId)
const response = await supa
.from('ml_orders')
.select('*')
.eq('id', orderId)
.eq('user_id', userId)
.execute()
if (response.error != null) {
console.error('[getOrderById] 查询订单失败:', response.error)
return null
}
const data = response.data as any[]
if (data == null || data.length === 0) {
console.log('[getOrderById] 未找到订单')
return null
}
const orderRaw = data[0]
let orderObj: UTSJSONObject
if (orderRaw instanceof UTSJSONObject) {
orderObj = orderRaw as UTSJSONObject
} else {
orderObj = JSON.parse(JSON.stringify(orderRaw)) as UTSJSONObject
}
console.log('[getOrderById] 订单数据:', JSON.stringify(orderObj))
return orderObj
} catch (e) {
console.error('[getOrderById] 查询异常:', e)
return null
}
}
// 提交售后申请
async createRefund(data: any): Promise<RefundResponse> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return { success: false, message: '请先登录' }
const d = data as UTSJSONObject
const orderId = d.getString('order_id')
const refundType = d.getNumber('refund_type')
const refundReason = d.getString('refund_reason')
const refundAmount = d.getNumber('refund_amount')
const description = d.getString('description')
const images = d.getArray('images')
const payload = {
user_id: userId,
order_id: orderId,
refund_no: 'REF' + Date.now() + Math.floor(Math.random() * 1000),
refund_type: refundType,
refund_reason: refundReason,
refund_amount: refundAmount,
description: description ?? '',
images: images ?? ([] as any[]),
status: 1 // Pending
}
const response = await supa
.from('ml_refunds')
.insert(payload)
.execute()
if (response.error != null) {
console.error('提交售后失败:', response.error)
return { success: false, message: '提交失败: ' + (response.error.message ?? '未知错误') }
}
return { success: true, message: '申请提交成功' }
} catch (e) {
console.error('提交售后异常:', e)
return { success: false, message: '系统异常' }
}
}
// 再次购买
async rePurchase(order: any): Promise<boolean> {
try {
// 将 order 转换为 UTSJSONObject 以安全访问属性
const orderObj = order as UTSJSONObject
// 尝试获取 ml_order_items 或 items
let itemsKey = 'ml_order_items'
let itemsRaw = orderObj.get(itemsKey)
if (itemsRaw == null) {
itemsKey = 'items'
itemsRaw = orderObj.get(itemsKey)
}
if (itemsRaw == null) return false
// 断言为数组
const items = itemsRaw as any[]
if (items.length === 0) return false
// 简单的循环添加,实际项目中可以优化为批量插入
for (let i = 0; i < items.length; i++) {
// 同样item 也是 UTSJSONObject 或支持访问的对象
const item = items[i] as UTSJSONObject
const productId = item.getString('product_id')
const skuId = item.getString('sku_id')
// 数量可能是数字或字符串
const quantity = item.getNumber('quantity') ?? 1
if (productId != null) {
await this.addToCart(productId, quantity, skuId ?? '', '')
}
}
return true
} catch (e) {
console.error('rePurchase error', e)
return false
}
}
// 申请售后 (Legacy/Simple update)
async applyRefund(orderId: string, reason: string): Promise<boolean> {
try {
// 更新订单状态为 退款中 (6)
const response = await supa
.from('ml_orders')
.update({
order_status: 6,
cancel_reason: reason,
updated_at: new Date().toISOString()
})
.eq('id', orderId)
.execute()
return response.error === null
} catch (e) {
return false
}
}
// 获取售后记录列表
async getRefunds(statusList: number[] = [], page: number = 1, pageSize: number = 10): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: any[] = []
return empty
}
let query = supa
.from('ml_refunds')
.select(`
*,
order:ml_orders!inner (
order_no,
created_at,
ml_order_items (
product_id,
product_name,
image_url
)
)
`)
.eq('user_id', userId)
.order('created_at', { ascending: false })
if (statusList.length > 0) {
// 显式转换为 any[] 以匹配 .in 方法的参数要求
const anyList = statusList as any[]
query = query.in('status', anyList)
}
query = query.range((page - 1) * pageSize, page * pageSize - 1)
const response = await query.execute()
if (response.error != null) {
console.error('获取售后列表失败:', response.error)
const empty: any[] = []
return empty
}
const data = response.data
if (data == null) {
const empty: any[] = []
return empty
}
return data
} catch (e) {
console.error('获取售后列表异常:', e)
const empty: any[] = []
return empty
}
}
async deleteRefund(refundId: string): Promise<boolean> {
try {
const response = await supa
.from('ml_refunds')
.delete()
.eq('id', refundId)
.execute()
if (response.error != null) {
console.error('删除退款记录失败:', response.error)
return false
}
return true
} catch (e) {
console.error('删除退款记录异常:', e)
return false
}
}
async getUserBalance(): Promise<number> {
try {
const userId = this.getCurrentUserId()
console.log('[Supabase] getUserBalance userId:', userId)
if (userId == null) return 0
// 优先查 ml_user_wallets
const walletRes = await supa
.from('ml_user_wallets')
.select('balance')
.eq('user_id', userId!)
.single()
.execute()
if (walletRes.error != null) {
console.error('[Supabase] getUserBalance error:', walletRes.error)
} else {
console.log('[Supabase] getUserBalance data:', walletRes.data)
}
if (walletRes.error == null && walletRes.data != null) {
let data = walletRes.data
// 如果是数组,取第一项
if (Array.isArray(data)) {
const arr = data as any[]
if (arr.length > 0) {
data = arr[0]
}
}
let val:number = 0
if (data instanceof UTSJSONObject) {
val = data.getNumber('balance') ?? 0
// 尝试字符串转换防止精度丢失导致转为string
if (val === 0 && data.getString('balance') != null) {
val = parseFloat(data.getString('balance')!)
}
return val
} else {
// 对于 Map 或 loose object
const jsonObj = JSON.parse(JSON.stringify(data)) as UTSJSONObject
val = jsonObj.getNumber('balance') ?? 0
if (val === 0 && jsonObj.getString('balance') != null) {
val = parseFloat(jsonObj.getString('balance')!)
}
return val
}
}
console.log('[Supabase] Wallet table empty, checking profile...')
// Fallback to profile
const profile = await this.getUserProfile()
if (profile != null) {
if (profile instanceof UTSJSONObject) {
return profile.getNumber('balance') ?? 0
} else {
const pObj = JSON.parse(JSON.stringify(profile)) as UTSJSONObject
return pObj.getNumber('balance') ?? 0
}
}
return 0
} catch(e) {
console.error('[Supabase] getUserBalance exception:', e)
return 0
}
}
// 获取用户积分
async getUserPoints(): Promise<number> {
try {
const userId = this.getCurrentUserId()
console.log('[Supabase] getUserPoints userId:', userId)
if (userId == null) return 0
// 查 ml_user_points
const res = await supa
.from('ml_user_points')
.select('points')
.eq('user_id', userId!)
.single()
.execute()
if (res.error != null) {
console.error('[Supabase] getUserPoints error:', res.error)
} else {
console.log('[Supabase] getUserPoints data:', res.data)
}
if (res.error == null && res.data != null) {
let data = res.data
// 如果是数组,取第一项
if (Array.isArray(data)) {
const arr = data as any[]
if (arr.length > 0) {
data = arr[0]
}
}
if (data instanceof UTSJSONObject) {
return data.getNumber('points') ?? 0
} else {
// 尝试转为 UTSJSONObject
const jsonObj = JSON.parse(JSON.stringify(data)) as UTSJSONObject
const val = jsonObj.getNumber('points')
if (val != null) return val
return 0
}
}
// Fallback check profile if needed
const profile = await this.getUserProfile()
if (profile != null) {
if (profile instanceof UTSJSONObject) {
return profile.getNumber('points') ?? 0
} else {
const pObj = JSON.parse(JSON.stringify(profile)) as UTSJSONObject
return pObj.getNumber('points') ?? 0
}
}
return 0
} catch (e) {
console.error('[Supabase] getUserPoints exception:', e)
return 0
}
}
// 获取钱包交易记录
async getTransactions(page: number = 1, limit: number = 20): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: any[] = []
return empty
}
const from = (page - 1) * limit
const to = from + limit - 1
const response = await supa
.from('ml_wallet_transactions')
.select('*')
.eq('user_id', userId!)
.order('created_at', { ascending: false })
.range(from, to)
.execute()
if (response.error != null) {
console.error('获取交易记录失败:', response.error)
const empty: any[] = []
return empty
}
const data = response.data
if (data == null) {
const empty: any[] = []
return empty
}
return data as any[]
} catch (e) {
console.error('获取交易记录异常:', e)
const empty: any[] = []
return empty
}
}
// 获取积分记录
async getPointRecords(): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: any[] = []
return empty
}
const res = await supa
.from('ml_point_records')
.select('*')
.eq('user_id', userId!)
.order('created_at', { ascending: false })
.execute()
if (res.error != null) {
const empty: any[] = []
return empty
}
const data = res.data
if (data == null) {
const empty: any[] = []
return empty
}
return data as any[]
} catch (e) {
const empty: any[] = []
return empty
}
}
// 获取用户红包
async getUserRedPackets(): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: any[] = []
return empty
}
const res = await supa
.from('ml_user_red_packets')
.select('*')
.eq('user_id', userId!)
.order('created_at', { ascending: false })
.execute()
if (res.error != null) {
console.error('获取红包失败:', res.error)
const empty: any[] = []
return empty
}
const data = res.data
if (data == null) {
const empty: any[] = []
return empty
}
return data as any[]
} catch (e) {
console.error('获取红包异常:', e)
const empty: any[] = []
return empty
}
}
// 获取用户银行卡
async getUserBankCards(): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: any[] = []
return empty
}
const res = await supa
.from('ml_user_bank_cards')
.select('*')
.eq('user_id', userId!)
.order('created_at', { ascending: false })
.execute()
if (res.error != null) {
console.error('获取银行卡失败:', res.error)
const empty: any[] = []
return empty
}
const data = res.data
if (data == null) {
const empty: any[] = []
return empty
}
return data as any[]
} catch (e) {
console.error('获取银行卡异常:', e)
const empty: any[] = []
return empty
}
}
// 余额充值 (调用 RPC)
async rechargeBalance(amount: number): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return false
const res = await supa.rpc('recharge_wallet', {
p_user_id: userId,
p_amount: amount
})
if (res.error != null) {
console.error('充值失败RPC:', res.error)
return false
}
// 简单判断: 如果没有error且data里success为true
const data = res.data
if (data instanceof UTSJSONObject) {
return data.getBoolean('success') ?? false
}
// 如果返回不是对象,作为失败处理
return false
} catch (e) {
console.error('充值异常:', e)
return false
}
}
// 余额提现 (调用 RPC)
async withdrawBalance(amount: number): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return false
const res = await supa.rpc('withdraw_wallet', {
p_user_id: userId,
p_amount: amount
})
if (res.error != null) {
console.error('提现失败RPC:', res.error)
return false
}
const data = res.data
if (data instanceof UTSJSONObject) {
return data.getBoolean('success') ?? false
}
return false
} catch (e) {
console.error('提现异常:', e)
return false
}
}
// 添加银行卡
async addBankCard(card: UTSJSONObject): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return false
// 补全 user_id
card.set('user_id', userId)
const res = await supa
.from('ml_user_bank_cards')
.insert(card)
.execute()
if (res.error != null) {
console.error('添加银行卡失败:', res.error)
return false
}
return true
} catch (e) {
console.error('添加银行卡异常:', e)
return false
}
}
// 删除银行卡
async deleteBankCard(cardId: string): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return false
const res = await supa
.from('ml_user_bank_cards')
.eq('id', cardId)
.eq('user_id', userId!)
.delete()
.execute()
if (res.error != null) {
console.error('删除银行卡失败:', res.error)
return false
}
return true
} catch (e) {
console.error('删除银行卡异常:', e)
return false
}
}
// 收藏相关
async checkFavorite(productId: string): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
console.log(`[CheckFav] Checking for User: ${userId}, Product: ${productId}`)
if (userId == null) return false
const response = await supa
.from('ml_user_favorites')
.select('*') // Select all to verify data
.eq('user_id', userId!)
.eq('target_id', productId)
.eq('target_type', 1) // 1 for product
.limit(1)
.execute()
// console.log(`[CheckFav] Response: ${JSON.stringify(response)}`)
if (response.error != null) {
console.error(`[CheckFav] Error: ${JSON.stringify(response.error)}`)
return false
}
const data = response.data
if (Array.isArray(data)) {
if ((data as any[]).length > 0) {
// Double check: ensure the returned item actually matches the product ID
// This guards against potential query filter failures
const item = data[0]
let targetId = ''
if (item instanceof UTSJSONObject) {
targetId = item.getString('target_id') ?? ''
} else {
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
targetId = itemObj.getString('target_id') ?? ''
}
if (targetId !== '' && targetId !== productId) {
console.error(`[CheckFav] ID Mismatch! Query ${productId}, Got ${targetId}`)
return false
}
return true
}
} else if (data instanceof UTSJSONObject) {
// Handle single object return case (though limit(1) usually returns array)
let targetId = data.getString('target_id') ?? ''
if (targetId !== '' && targetId !== productId) {
return false
}
return true
}
return false
} catch(e) {
console.error(`[CheckFav] Exception: ${e}`)
return false
}
}
async toggleFavorite(productId: string): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return false
console.log(`[ToggleFav] Toggling for ${productId}`)
// Check if exists
const exists = await this.checkFavorite(productId)
console.log(`[ToggleFav] Current status: ${exists}`)
if (exists) {
// Delete
const response = await supa
.from('ml_user_favorites')
.eq('user_id', userId!)
.eq('target_id', productId)
.eq('target_type', 1)
.delete()
.execute()
if (response.error != null) {
console.error('取消收藏失败:', response.error)
return true // 仍然是收藏状态
}
return false // 已取消收藏
} else {
// Add
const response = await supa
.from('ml_user_favorites')
.insert({
user_id: userId,
target_id: productId,
target_type: 1,
created_at: new Date().toISOString()
})
.execute()
if (response.error != null) {
console.error('添加收藏失败:', response.error)
return false // 添加失败,仍未收藏
}
return true // 已收藏
}
} catch (e) {
console.error('切换收藏状态异常:', e)
// 发生异常时,尝试查询当前状态返回
return await this.checkFavorite(productId)
}
}
async getFavorites(): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: any[] = []
return empty
}
// 第一步:查询收藏列表
const response = await supa
.from('ml_user_favorites')
.select('*')
.eq('user_id', userId!)
.eq('target_type', 1)
.order('created_at', { ascending: false })
.execute()
if (response.error != null) {
const empty: any[] = []
return empty
}
const favorites = response.data as any[]
if (favorites == null || favorites.length === 0) {
const empty: any[] = []
return empty
}
// 第二步收集商品ID
const productIds: string[] = []
for (let i = 0; i < favorites.length; i++) {
let item: any = favorites[i]
let pid = ''
if (item instanceof UTSJSONObject) {
pid = item.getString('target_id') ?? ''
} else {
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
pid = itemObj.getString('target_id') ?? ''
}
if (pid !== '') productIds.push(pid)
}
if (productIds.length === 0) return []
// 第三步:批量查询商品详情
const anyProductIds = productIds as any[]
const productRes = await supa
.from('ml_products')
.select('id, name, main_image_url, base_price, sale_count')
.in('id', anyProductIds)
.execute()
if (productRes.error != null) {
const empty: any[] = []
return empty
}
const products = productRes.data as any[]
const productMap = new Map<string, any>()
for (let i = 0; i < products.length; i++) {
// 显式声明类型为 any
let p: any = products[i]
let pid = ''
if (p instanceof UTSJSONObject) {
pid = p.getString('id') ?? ''
} else {
const pObj = JSON.parse(JSON.stringify(p)) as UTSJSONObject
pid = pObj.getString('id') ?? ''
}
if (pid !== '') productMap.set(pid, p)
}
// 第四步:组合数据
const result: any[] = []
for (let i = 0; i < favorites.length; i++) {
let item: any = favorites[i]
let newItem: UTSJSONObject
if (item instanceof UTSJSONObject) {
// Deep copy to ensure we have a fresh object to modify
newItem = JSON.parse(JSON.stringify(item)) as UTSJSONObject
} else {
newItem = JSON.parse(JSON.stringify(item)) as UTSJSONObject
}
let targetId = newItem.getString('target_id')
// Careful with null targetId
if (targetId != null) {
const product = productMap.get(targetId as string)
if (product != null) {
newItem.set('ml_products', product)
result.push(newItem)
}
}
}
return result
} catch (e) {
console.error('获取收藏列表异常:', e)
return []
}
}
// 获取足迹列表
async getFootprints(): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.log('[getFootprints] 用户未登录')
const empty: any[] = []
return empty
}
console.log('[getFootprints] 查询足迹, userId:', userId)
// 1. 获取足迹记录
const response = await supa
.from('ml_user_footprints')
.select('*')
.eq('user_id', userId!)
.order('updated_at', { ascending: false })
.limit(50)
.execute()
console.log('[getFootprints] 足迹查询 error:', response.error)
console.log('[getFootprints] 足迹查询 data:', JSON.stringify(response.data))
if (response.error != null) {
console.error('[getFootprints] 获取足迹失败:', response.error)
const empty: any[] = []
return empty
}
const footprints = response.data as any[]
if (footprints == null || footprints.length === 0) {
console.log('[getFootprints] 没有足迹记录')
const empty: any[] = []
return empty
}
console.log('[getFootprints] 足迹记录数量:', footprints.length)
// 2. 收集商品ID
const productIds: string[] = []
for (let i = 0; i < footprints.length; i++) {
let item = footprints[i]
let pid = ''
if (item instanceof UTSJSONObject) {
pid = item.getString('product_id') ?? ''
} else {
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
pid = itemObj.getString('product_id') ?? ''
}
if (pid !== '' && !productIds.includes(pid)) productIds.push(pid)
}
if (productIds.length === 0) return []
const productIdsAny: any[] = []
for(let i=0; i<productIds.length; i++) {
productIdsAny.push(productIds[i])
}
// 3. 批量查询商品详情
const productRes = await supa
.from('ml_products_detail_view')
.select('id, name, main_image_url, base_price, market_price, sale_count, merchant_id, shop_name')
.in('id', productIdsAny)
.execute()
// 如果视图失败,回退查基础表
let products: any[] = []
if (productRes.error == null && productRes.data != null) {
products = productRes.data as any[]
} else {
console.warn('View查询失败尝试查询基础表')
const baseRes = await supa
.from('ml_products')
.select('id, name, main_image_url, base_price, market_price, sale_count, merchant_id')
.in('id', productIdsAny)
.execute()
if (baseRes.error == null) {
products = baseRes.data as any[]
}
}
const productMap = new Map<string, any>()
for(let i=0; i<products.length; i++) {
let p = products[i]
let pid = ''
if (p instanceof UTSJSONObject) {
pid = p.getString('id') ?? ''
} else {
const pObj = JSON.parse(JSON.stringify(p)) as UTSJSONObject
pid = pObj.getString('id') ?? ''
}
if (pid !== '') productMap.set(pid, p)
}
// 4. 组合结果
const result: any[] = []
for (let i = 0; i < footprints.length; i++) {
let fp = footprints[i]
let pid = ''
let viewTime = 0
if (fp instanceof UTSJSONObject) {
pid = fp.getString('product_id') ?? ''
const dateStr = fp.getString('updated_at')
if (dateStr != null) viewTime = new Date(dateStr).getTime()
} else {
const fpObj = JSON.parse(JSON.stringify(fp)) as UTSJSONObject
pid = fpObj.getString('product_id') ?? ''
const dateStr = fpObj.getString('updated_at')
if (dateStr != null) viewTime = new Date(dateStr).getTime()
}
const product = productMap.get(pid)
if (product != null) {
let pName = ''
let pImage = ''
let pPrice = 0
let pOriginalPrice = 0
let pSales = 0
let pShopId = ''
let pShopName = ''
if (product instanceof UTSJSONObject) {
pName = product.getString('name') ?? ''
pImage = product.getString('main_image_url') ?? ''
pPrice = product.getNumber('base_price') ?? 0
pOriginalPrice = product.getNumber('market_price') ?? 0
pSales = product.getNumber('sale_count') ?? 0
pShopId = product.getString('merchant_id') ?? ''
pShopName = product.getString('shop_name') ?? ''
} else {
const pObj = JSON.parse(JSON.stringify(product)) as UTSJSONObject
pName = pObj.getString('name') ?? ''
pImage = pObj.getString('main_image_url') ?? ''
pPrice = pObj.getNumber('base_price') ?? 0
pOriginalPrice = pObj.getNumber('market_price') ?? 0
pSales = pObj.getNumber('sale_count') ?? 0
pShopId = pObj.getString('merchant_id') ?? ''
pShopName = pObj.getString('shop_name') ?? ''
}
const fpObj = new UTSJSONObject()
fpObj.set('id', pid)
fpObj.set('name', pName)
fpObj.set('price', pPrice)
fpObj.set('original_price', pOriginalPrice)
fpObj.set('image', pImage)
fpObj.set('sales', pSales)
fpObj.set('shopId', pShopId)
fpObj.set('shopName', pShopName)
fpObj.set('merchant_id', pShopId)
fpObj.set('viewTime', viewTime)
result.push(fpObj)
}
}
return result
} catch (error) {
console.error('获取足迹异常:', error)
return []
}
}
// 添加/更新足迹
async addFootprint(productId: string): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.log('[addFootprint] 用户未登录')
return false
}
console.log('[addFootprint] 添加足迹, userId:', userId, 'productId:', productId)
// 检查是否已存在
const checkRes = await supa
.from('ml_user_footprints')
.select('id')
.eq('user_id', userId!)
.eq('product_id', productId)
.execute()
console.log('[addFootprint] 检查结果 error:', checkRes.error)
console.log('[addFootprint] 检查结果 data:', JSON.stringify(checkRes.data))
const checkData = checkRes.data as any[]
const exists = checkData != null && Array.isArray(checkData) && checkData.length > 0
if (checkRes.error == null && exists) {
console.log('[addFootprint] 足迹已存在,更新时间')
// 更新时间
const updateRes = await supa
.from('ml_user_footprints')
.update({ updated_at: new Date().toISOString() })
.eq('user_id', userId!)
.eq('product_id', productId)
.execute()
console.log('[addFootprint] 更新结果 error:', updateRes.error)
} else {
console.log('[addFootprint] 足迹不存在,插入新记录')
// 插入新记录
const insertPayload = new UTSJSONObject()
insertPayload.set('user_id', userId!)
insertPayload.set('product_id', productId)
insertPayload.set('created_at', new Date().toISOString())
insertPayload.set('updated_at', new Date().toISOString())
const insertRes = await supa
.from('ml_user_footprints')
.insert(insertPayload)
.execute()
console.log('[addFootprint] 插入结果 error:', insertRes.error)
}
return true
} catch (e) {
console.error('[addFootprint] 添加足迹异常:', e)
return false
}
}
// 删除单个足迹
async deleteFootprint(productId: string): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.log('[deleteFootprint] 用户未登录')
return false
}
const response = await supa
.from('ml_user_footprints')
.eq('user_id', userId)
.eq('product_id', productId)
.delete()
.execute()
if (response.error != null) {
console.error('[deleteFootprint] 删除足迹失败:', response.error)
return false
}
console.log('[deleteFootprint] 删除足迹成功')
return true
} catch (e) {
console.error('[deleteFootprint] 删除足迹异常:', e)
return false
}
}
// 批量删除足迹
async deleteFootprints(productIds: string[]): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.log('[deleteFootprints] 用户未登录')
return false
}
const idsAny: any[] = []
for (let i = 0; i < productIds.length; i++) {
idsAny.push(productIds[i])
}
const response = await supa
.from('ml_user_footprints')
.eq('user_id', userId)
.in('product_id', idsAny)
.delete()
.execute()
if (response.error != null) {
console.error('[deleteFootprints] 批量删除足迹失败:', response.error)
return false
}
console.log('[deleteFootprints] 批量删除足迹成功')
return true
} catch (e) {
console.error('[deleteFootprints] 批量删除足迹异常:', e)
return false
}
}
// 清空所有足迹
async clearFootprints(): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.log('[clearFootprints] 用户未登录')
return false
}
const response = await supa
.from('ml_user_footprints')
.eq('user_id', userId)
.delete()
.execute()
if (response.error != null) {
console.error('[clearFootprints] 清空足迹失败:', response.error)
return false
}
console.log('[clearFootprints] 清空足迹成功')
return true
} catch (e) {
console.error('[clearFootprints] 清空足迹异常:', e)
return false
}
}
async getAddressList(): Promise<UserAddress[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: UserAddress[] = []
return empty
}
const response = await supa
.from('ml_user_addresses')
.select('*, recipient_name:receiver_name, phone:receiver_phone, detail_address:address_detail')
.eq('user_id', userId!)
.order('is_default', { ascending: false })
.order('created_at', { ascending: false })
.execute()
if (response.error != null) {
console.error('获取地址列表失败:', response.error)
const empty: UserAddress[] = []
return empty
}
return response.data as UserAddress[]
} catch (e) {
console.error('获取地址列表异常:', e)
const empty: UserAddress[] = []
return empty
}
}
// 设置默认地址
async setDefaultAddress(addressId: string): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
console.error('用户未登录,无法设置默认地址')
return false
}
// 先取消所有默认地址
await this.clearDefaultAddress(userId!)
// 设置新的默认地址
const response = await supa
.from('ml_user_addresses')
.update({
is_default: true,
updated_at: new Date().toISOString()
})
.eq('id', addressId)
.eq('user_id', userId!)
.execute()
if (response.error != null) {
console.error('设置默认地址失败:', response.error)
return false
}
return true
} catch (error) {
console.error('设置默认地址异常:', error)
return false
}
}
// 获取用户优惠券列表
async getUserCoupons(status: number = 1): Promise<UserCoupon[]> {
try {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: UserCoupon[] = []
return empty
}
// 假设有一个视图或者直接关联 ml_user_coupons 和 ml_coupon_templates
// 这里简化处理,尝试直接从 ml_user_coupons 读取,并且加入 template 信息
// 如果没有 view可能需要改为两个查询或者使用 left join
const response = await supa
.from('ml_user_coupons')
.select(`
*,
template:ml_coupon_templates(name, amount, min_spend)
`)
.eq('user_id', userId!)
.eq('status', status)
.order('expire_at', { ascending: true })
.execute()
if (response.error != null) {
console.error('获取优惠券失败:', response.error)
const empty: UserCoupon[] = []
return empty
}
// 映射数据,将 template 的字段展平
const coupons: UserCoupon[] = []
const rawData = response.data as any[]
for (let i = 0; i < rawData.length; i++) {
const item = rawData[i]
let template: any | null = null
let itemId = ''
let itemUserId = ''
let itemTmplId = ''
let itemCode = ''
let itemStatus = 0
let itemRecv = ''
let itemExpire = ''
if (item instanceof UTSJSONObject) {
template = item.get('template') as any | null
itemId = item.getString('id') ?? ''
itemUserId = item.getString('user_id') ?? ''
itemTmplId = item.getString('template_id') ?? ''
itemCode = item.getString('coupon_code') ?? ''
itemStatus = item.getNumber('status') ?? 0
itemRecv = item.getString('received_at') ?? ''
itemExpire = item.getString('expire_at') ?? ''
} else {
const iObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
template = iObj.get('template') as any | null
itemId = iObj.getString('id') ?? ''
itemUserId = iObj.getString('user_id') ?? ''
itemTmplId = iObj.getString('template_id') ?? ''
itemCode = iObj.getString('coupon_code') ?? ''
itemStatus = iObj.getNumber('status') ?? 0
itemRecv = iObj.getString('received_at') ?? ''
itemExpire = iObj.getString('expire_at') ?? ''
}
if (template == null) template = new UTSJSONObject()
let tName = ''
let tAmount = 0
let tMin = 0
if (template instanceof UTSJSONObject) {
tName = template.getString('name') ?? '优惠券'
tAmount = template.getNumber('amount') ?? 0
tMin = template.getNumber('min_spend') ?? 0
} else {
const tObj = JSON.parse(JSON.stringify(template)) as UTSJSONObject
tName = tObj.getString('name') ?? '优惠券'
tAmount = tObj.getNumber('amount') ?? 0
tMin = tObj.getNumber('min_spend') ?? 0
}
const couponObj = new UTSJSONObject()
couponObj.set('id', itemId)
couponObj.set('user_id', itemUserId)
couponObj.set('template_id', itemTmplId)
couponObj.set('coupon_code', itemCode)
couponObj.set('status', itemStatus)
couponObj.set('received_at', itemRecv)
couponObj.set('expire_at', itemExpire)
couponObj.set('template_name', tName)
couponObj.set('amount', tAmount)
couponObj.set('min_spend', tMin)
coupons.push(couponObj as UserCoupon)
}
return coupons
} catch (e) {
console.error('获取优惠券异常:', e)
const empty: UserCoupon[] = []
return empty
}
}
// 获取可用优惠券数量
async getUserCouponCount(): Promise<number> {
try {
const userId = this.getCurrentUserId()
if (userId == null) return 0
const response = await supa
.from('ml_user_coupons')
.select('id', { count: 'exact' })
.eq('user_id', userId!)
.eq('status', 1) // 1: unused
.gt('expire_at', new Date().toISOString()) // 未过期
.limit(1) // Limit to 1 to reduce data transfer, we only want the count
.execute()
if (response.error != null) {
return 0
}
return response.total ?? 0
} catch (e) {
return 0
}
}
// 获取店铺/商品可用优惠券
async getAvailableCoupons(merchantId: string): Promise<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 != null) {
console.error('Fetch coupons failed:', response.error)
const empty: any[] = []
return empty
}
const data = response.data
if (data == null) {
const empty: any[] = []
return empty
}
return data as any[]
} catch (e) {
console.error('Fetch coupons error:', e)
const empty: any[] = []
return empty
}
}
// 领取优惠券
async claimCoupon(templateId: string, userId: string): Promise<boolean> {
return this.claimShopCoupon(templateId, userId)
}
// ALIAS for Cache busting
async claimShopCoupon(templateId: string, userId: string): Promise<boolean> {
try {
console.log('Claiming coupon templateId:', templateId, 'userId:', userId)
// 1. Fetch template details to get merchant_id and validity
const tmplRes = await supa
.from('ml_coupon_templates')
.select('*')
.eq('id', templateId)
.limit(1)
.execute()
if (tmplRes.error != null) {
console.error('Claim Coupon: Template query error', tmplRes.error)
return false
}
// Null check for data
if (tmplRes.data == null) {
console.error('Claim Coupon: Template data response is null')
return false
}
const dataList = tmplRes.data as any[]
if (dataList.length === 0) {
console.error('Claim Coupon: Template not found (empty list)')
return false
}
const template = dataList[0]
// Safe property access
let validDays = 0
let endTimeStr: string | null = null
let merchantId: string | null = null
if (template instanceof UTSJSONObject) {
validDays = template.getNumber('valid_days') ?? 0
endTimeStr = template.getString('end_time')
merchantId = template.getString('merchant_id')
} else {
const tJson = JSON.parse(JSON.stringify(template)) as UTSJSONObject
validDays = tJson.getNumber('valid_days') ?? 0
endTimeStr = tJson.getString('end_time')
merchantId = tJson.getString('merchant_id')
}
// Calculate expire_at
let expireAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()
if (validDays > 0) {
expireAt = new Date(Date.now() + (validDays * 24 * 60 * 60 * 1000)).toISOString()
} else if (endTimeStr != null && endTimeStr !== '') {
expireAt = endTimeStr
}
// Handle UUID fields: Empty string is not valid UUID, must be null
if (merchantId != null && merchantId.length === 0) {
merchantId = null
}
// 2. Insert into user coupons with merchant_id
const insertData = {
user_id: userId,
template_id: templateId,
merchant_id: merchantId, // Important for shop filtering: null for platform coupons
coupon_code: 'C' + Date.now() + Math.floor(Math.random() * 1000),
status: 1,
expire_at: expireAt,
received_at: new Date().toISOString()
}
console.log('Claim Coupon Insert Payload:', JSON.stringify(insertData))
const response = await supa
.from('ml_user_coupons')
.insert(insertData)
.execute()
if (response.error != null) {
console.error('Claim Coupon: Insert failed:', JSON.stringify(response.error))
// 尝试降级:如果 merchant_id 报错,尝试不带 merchant_id (仅调试用,或兼容旧表结构)
if (JSON.stringify(response.error).includes('merchant_id')) {
console.log('Retrying without merchant_id...')
const fallbackData = {
user_id: userId,
template_id: templateId,
coupon_code: 'C' + Date.now() + Math.random().toString().substring(2,6),
status: 1,
expire_at: expireAt,
received_at: new Date().toISOString()
}
const res2 = await supa.from('ml_user_coupons').insert(fallbackData).execute()
if (res2.error == null) return true
}
return false
}
return true
} catch(e) {
console.error('Claim coupon error:', e)
return false
}
}
// ==========================================
// 聊天相关方法
// ==========================================
// 获取特定会话的消息历史
async getChatMessages(merchantId: string, page: number = 1, pageSize: number = 20): Promise<ChatMessage[]> {
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: ChatMessage[] = []
return empty
}
// 计算分页 range
const fromIndex = (page - 1) * pageSize
const toIndex = fromIndex + pageSize - 1
try {
// 使用 or 组合条件查询:(sender_id=me AND receiver_id=merchant) OR (sender_id=merchant AND receiver_id=me)
// 注意Supabase postgrest-js 的 .or() 语法如果是针对同一列很简单,针对复杂逻辑用 string syntax
// 这里简化处理,如果不加 userId 过滤,全靠 RLS
const response = await supa
.from('ml_chat_messages')
.select('*')
.or(`sender_id.eq.${merchantId},receiver_id.eq.${merchantId}`)
.order('created_at', { ascending: false })
.range(fromIndex, toIndex)
.execute()
if (response.error != null) {
console.error('getChatMessages error:', response.error)
const empty: ChatMessage[] = []
return empty
}
const data = response.data
if (data == null) {
const empty: ChatMessage[] = []
return empty
}
return data as ChatMessage[]
} catch (e) {
console.error('getChatMessages exception:', e)
const empty: ChatMessage[] = []
return empty
}
}
// 发送消息
async sendMessage(merchantId: string, content: string, msgType: string = 'text'): Promise<boolean> {
// 确保 session 有效
const userId = this.getCurrentUserId()
if (userId == null) {
console.error("sendMessage failed: user not logged in or session lost")
return false
}
try {
// Debug check
// const session = supa.getSession()
// console.log("Sending check: UserID", userId, "AuthID:", session.user?.getString('id'))
const msg = {
sender_id: userId!,
receiver_id: merchantId,
content: content,
msg_type: msgType,
is_read: false,
is_from_user: true
}
const response = await supa
.from('ml_chat_messages')
.insert(msg)
.execute()
if (response.error != null) {
console.error('sendMessage error:', response.error)
return false
}
return true
} catch (e) {
console.error('sendMessage exception:', e)
return false
}
}
// 标记会话已读
async markRead(merchantId: string): Promise<boolean> {
const userId = this.getCurrentUserId()
if (userId == null) return false
try {
const response = await supa
.from('ml_chat_messages')
.update({ is_read: true })
.eq('sender_id', merchantId)
.eq('receiver_id', userId)
.eq('is_read', false)
.execute()
if (response.error != null) return false
} catch (e) { return false }
return true
}
// 提交商品评价
async submitProductReviews(reviews: Array<UTSJSONObject>): Promise<boolean> {
try {
for (let i: number = 0; i < reviews.length; i++) {
const review = reviews[i]
const response = await supa
.from('ml_product_reviews')
.insert(review)
.execute()
if (response.error != null) {
console.error('提交商品评价失败:', response.error)
return false
}
}
return true
} catch (e) {
console.error('提交商品评价失败:', e)
return false
}
}
// 提交店铺评价
async submitShopReview(review: UTSJSONObject): Promise<boolean> {
try {
const response = await supa
.from('ml_shop_reviews')
.insert(review)
.execute()
return response.error == null
} catch (e) {
console.error('提交店铺评价失败:', e)
return false
}
}
// 更新订单状态
async updateOrderStatus(orderId: string, status: number): Promise<boolean> {
try {
const updateData = new UTSJSONObject()
updateData.set('order_status', status)
const response = await supa
.from('ml_orders')
.update(updateData)
.eq('id', orderId)
.execute()
return response.error == null
} catch (e) {
console.error('更新订单状态失败:', e)
return false
}
}
}
// 导出单例实例
export const supabaseService = new SupabaseService()
// 默认导出
export default supabaseService