3745 lines
118 KiB
Plaintext
3745 lines
118 KiB
Plaintext
import supa from '@/components/supadb/aksupainstance.uts'
|
||
import type { AkReqResponse } from '@/uni_modules/ak-req/index.uts'
|
||
|
||
// 使用单例 Supabase 客户端
|
||
// const supa = createClient(SUPA_URL, SUPA_KEY)
|
||
|
||
// 类型定义
|
||
export interface Brand {
|
||
id: string
|
||
name: string
|
||
logo_url: string
|
||
description: string
|
||
}
|
||
|
||
export interface Category {
|
||
id: string
|
||
name: string
|
||
icon: string
|
||
description: string
|
||
color: string
|
||
created_at?: string
|
||
}
|
||
|
||
export interface Product {
|
||
id: string
|
||
category_id: string
|
||
merchant_id: string
|
||
name: string
|
||
subtitle?: string
|
||
description?: string
|
||
base_price: number
|
||
market_price?: number
|
||
cost_price?: number
|
||
main_image_url?: string
|
||
image_urls?: string // JSON string array
|
||
video_urls?: string // JSON string array
|
||
sale_count?: number
|
||
view_count?: number
|
||
total_stock?: number
|
||
available_stock?: number
|
||
is_hot?: boolean
|
||
is_new?: boolean
|
||
is_featured?: boolean
|
||
status?: number
|
||
rating_avg?: number
|
||
rating_count?: number
|
||
tags?: string // array string in DB
|
||
attributes?: string // JSON string
|
||
created_at?: string
|
||
updated_at?: string
|
||
// Alias fields for compatibility
|
||
price?: number
|
||
original_price?: number
|
||
stock?: number
|
||
sales?: number
|
||
images?: string
|
||
cover?: string
|
||
// View fields
|
||
brand_name?: string
|
||
category_name?: string
|
||
shop_name?: string
|
||
merchant_name?: string
|
||
}
|
||
|
||
export interface Shop {
|
||
id: string
|
||
merchant_id: string
|
||
shop_name: string
|
||
shop_logo?: string
|
||
shop_banner?: string
|
||
description?: string
|
||
contact_name?: string
|
||
contact_phone?: string
|
||
rating_avg?: number
|
||
total_sales?: number
|
||
product_count?: number
|
||
total_sales_count?: number
|
||
created_at?: string
|
||
}
|
||
|
||
export 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 interface UserAddress {
|
||
id: string
|
||
user_id: string
|
||
recipient_name: string
|
||
phone: string
|
||
province: string
|
||
city: string
|
||
district: string
|
||
detail_address: string
|
||
postal_code?: string
|
||
is_default: boolean
|
||
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 interface Notification {
|
||
id: string
|
||
user_id: string
|
||
type: string
|
||
title: string
|
||
content: string
|
||
icon_url?: string
|
||
link_url?: string
|
||
is_read: boolean
|
||
extra_data?: string
|
||
created_at?: string
|
||
}
|
||
|
||
export interface ChatMessage {
|
||
id: string
|
||
session_id?: string
|
||
sender_id?: string
|
||
receiver_id?: string
|
||
content: string
|
||
msg_type: string
|
||
is_read: boolean
|
||
is_from_user: boolean
|
||
extra_data?: string
|
||
created_at?: string
|
||
}
|
||
|
||
export interface PaginatedResponse<T> {
|
||
data: T[]
|
||
total: number
|
||
page: number
|
||
limit: number
|
||
hasmore: boolean
|
||
}
|
||
|
||
export interface ProductSku {
|
||
id: string
|
||
product_id: string
|
||
sku_code: string
|
||
specifications: string // JSON string
|
||
price: number
|
||
market_price?: number
|
||
cost_price?: number
|
||
stock?: number
|
||
warning_stock?: number
|
||
image_url?: string
|
||
weight?: number
|
||
status?: number
|
||
created_at?: string
|
||
}
|
||
|
||
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 []
|
||
}
|
||
|
||
return response.data as Category[]
|
||
} catch (error) {
|
||
console.error('获取分类异常:', error)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 获取所有品牌
|
||
async getBrands(): Promise<Brand[]> {
|
||
try {
|
||
const response = await supa
|
||
.from('ml_brands')
|
||
.select('*')
|
||
.eq('is_active', true)
|
||
.order('name', { ascending: true })
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('获取品牌失败:', response.error)
|
||
return []
|
||
}
|
||
|
||
return response.data as Brand[]
|
||
} catch (error) {
|
||
console.error('获取品牌异常:', error)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 获取指定分类的商品
|
||
async getProductsByCategory(
|
||
categoryId: string,
|
||
page: number = 1,
|
||
limit: number = 20
|
||
): Promise<PaginatedResponse<Product>> {
|
||
try {
|
||
const response = await supa
|
||
.from('ml_products_detail_view')
|
||
.select('*', { count: 'exact' })
|
||
.eq('category_id', categoryId)
|
||
.eq('status', 1)
|
||
.order('sale_count', { ascending: false })
|
||
.page(page)
|
||
.limit(limit)
|
||
.execute()
|
||
|
||
if (response.error != 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`)
|
||
|
||
// Map raw data to Product interface (manually if needed for extra safety)
|
||
const mappedData: Product[] = []
|
||
const rawData = res2.data as any[]
|
||
for(let i = 0; i < rawData.length; i++) {
|
||
mappedData.push(rawData[i] as 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
|
||
}
|
||
}
|
||
}
|
||
|
||
// 获取热销商品(按销量排序)
|
||
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 []
|
||
}
|
||
|
||
return response.data as Product[]
|
||
} catch (error) {
|
||
console.error('获取热销商品异常:', error)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 获取按价格排序的商品(升序:从低到高)
|
||
async getProductsByPrice(limit: number = 10, ascending: boolean = true): Promise<Product[]> {
|
||
try {
|
||
const response = await supa
|
||
.from('ml_products_detail_view')
|
||
.select('*')
|
||
.eq('status', 1)
|
||
.order('base_price', { ascending })
|
||
.limit(limit)
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('获取价格排序商品失败:', response.error)
|
||
return []
|
||
}
|
||
|
||
return response.data as Product[]
|
||
} catch (error) {
|
||
console.error('获取价格排序商品异常:', error)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 获取新品(按创建时间排序,最新的在前)
|
||
async getProductsByNewest(limit: number = 10): Promise<Product[]> {
|
||
try {
|
||
const response = await supa
|
||
.from('ml_products_detail_view')
|
||
.select('*')
|
||
.eq('is_new', true)
|
||
.eq('status', 1)
|
||
.order('published_at', { ascending: false }) // Use published_at for newest
|
||
.limit(limit)
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('获取新品失败:', response.error)
|
||
return []
|
||
}
|
||
|
||
return response.data as Product[]
|
||
} catch (error) {
|
||
console.error('获取新品异常:', error)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 获取推荐商品(is_featured=true)
|
||
async getRecommendedProducts(limit: number = 10): Promise<Product[]> {
|
||
try {
|
||
// 查询 is_featured = true 的商品
|
||
const response = await supa
|
||
.from('ml_products_detail_view')
|
||
.select('*')
|
||
.eq('is_featured', true)
|
||
.eq('status', 1)
|
||
.order('sale_count', { ascending: false })
|
||
.limit(limit)
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('获取推荐商品失败:', response.error)
|
||
return []
|
||
}
|
||
|
||
console.log('推荐商品查询结果条数:', (response.data as any[])?.length ?? 0)
|
||
const data = response.data as Product[]
|
||
return data ?? []
|
||
} 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 = ''
|
||
|
||
if (item instanceof UTSJSONObject) {
|
||
itemId = item.getString('id') ?? ''
|
||
userIdVal = item.getString('user_id') ?? ''
|
||
productId = item.getString('product_id') ?? ''
|
||
skuId = item.getString('sku_id') ?? ''
|
||
quantity = item.getNumber('quantity') ?? 0
|
||
selected = item.getBoolean('selected') ?? false
|
||
createdAt = item.getString('created_at') ?? ''
|
||
updatedAt = item.getString('updated_at') ?? ''
|
||
} else {
|
||
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') ?? ''
|
||
}
|
||
|
||
const product = productMap.get(productId)
|
||
const sku = (skuId !== '' && skuMap.has(skuId)) ? skuMap.get(skuId) : null
|
||
|
||
let merchantId: string = ''
|
||
let productName: string = ''
|
||
let productImage: string = ''
|
||
let productPrice: number = 0
|
||
let productSpec: string = ''
|
||
let shopNameStr: string = '未知店铺'
|
||
|
||
if (product != null) {
|
||
if (product instanceof UTSJSONObject) {
|
||
merchantId = product.getString('merchant_id') ?? ''
|
||
productName = product.getString('name') ?? ''
|
||
productImage = product.getString('main_image_url') ?? ''
|
||
productPrice = product.getNumber('base_price') ?? 0
|
||
shopNameStr = product.getString('shop_name') ?? '未知店铺'
|
||
|
||
// 只有当没有sku信息时,才尝试使用商品的attributes(作为降级显示)
|
||
// 修改策略:不使用 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): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
console.error('用户未登录,无法添加商品到购物车')
|
||
return false
|
||
}
|
||
|
||
const realSkuId = (skuId != null && skuId.length > 0) ? skuId : 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,
|
||
updated_at: new Date().toISOString()
|
||
})
|
||
.eq('id', itemId)
|
||
.execute()
|
||
} else {
|
||
console.error('购物车已有商品但缺少ID,无法更新. Data:', JSON.stringify(existingItem))
|
||
return false
|
||
}
|
||
} else {
|
||
// 商品不存在,添加新记录
|
||
response = await supa
|
||
.from('ml_shopping_cart')
|
||
.insert({
|
||
user_id: userId,
|
||
product_id: productId,
|
||
sku_id: realSkuId,
|
||
quantity: quantity,
|
||
selected: true,
|
||
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 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> {
|
||
return true
|
||
/*
|
||
try {
|
||
console.log('正在执行删除购物车商品,ID:', cartItemId)
|
||
const userId = this.getCurrentUserId()
|
||
if (!userId) {
|
||
console.error('用户未登录,无法删除购物车商品')
|
||
return false
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_shopping_cart')
|
||
.eq('id', cartItemId)
|
||
.eq('user_id', userId)
|
||
.delete()
|
||
.execute()
|
||
|
||
if (response.error) {
|
||
console.error('删除购物车商品失败:', response.error)
|
||
return false
|
||
}
|
||
|
||
return true
|
||
} catch (error) {
|
||
console.error('删除购物车商品异常:', error)
|
||
return false
|
||
}
|
||
*/
|
||
}
|
||
|
||
// 批量删除购物车商品
|
||
async batchDeleteCartItems(cartItemIds: string[]): Promise<boolean> {
|
||
return true
|
||
/*
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (!userId) {
|
||
console.error('用户未登录,无法删除购物车商品')
|
||
return false
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_shopping_cart')
|
||
.eq('user_id', userId)
|
||
.in('id', cartItemIds)
|
||
.delete()
|
||
.execute()
|
||
|
||
if (response.error) {
|
||
console.error('批量删除购物车商品失败:', response.error)
|
||
return false
|
||
}
|
||
|
||
return true
|
||
} catch (error) {
|
||
console.error('批量删除购物车商品异常:', error)
|
||
return false
|
||
}
|
||
*/
|
||
}
|
||
|
||
// 清空购物车
|
||
async clearCart(): Promise<boolean> {
|
||
return true
|
||
/*
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (!userId) {
|
||
console.error('用户未登录,无法清空购物车')
|
||
return false
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_shopping_cart')
|
||
.eq('user_id', userId)
|
||
.delete()
|
||
.execute()
|
||
|
||
if (response.error) {
|
||
console.error('清空购物车失败:', response.error)
|
||
return false
|
||
}
|
||
|
||
return true
|
||
} catch (error) {
|
||
console.error('清空购物车异常:', error)
|
||
return false
|
||
}
|
||
*/
|
||
}
|
||
|
||
// 获取当前用户的所有地址
|
||
async getAddresses(): Promise<UserAddress[]> {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
console.warn('[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
|
||
if (merchantId == null || merchantId == '' || merchantId == 'unknown') {
|
||
merchantId = userId
|
||
}
|
||
|
||
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))
|
||
|
||
const itemsResponse = await supa
|
||
.from('ml_order_items')
|
||
.insert(orderItems)
|
||
.execute()
|
||
|
||
if (itemsResponse.error != null) {
|
||
console.error('[CreateOrder] 创建订单项失败:', itemsResponse.error)
|
||
console.error('[CreateOrder] 错误详情:', JSON.stringify(itemsResponse.error))
|
||
console.log('[CreateOrder] 订单主表已创建,但订单项插入失败,返回订单ID')
|
||
return orderId
|
||
}
|
||
|
||
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')
|
||
|
||
const orderId = await this.createOrder({
|
||
merchant_id: (mId != null && mId != '') ? mId : (sId ?? ''), // 兼容旧字段
|
||
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 ?? null)
|
||
}
|
||
}
|
||
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 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('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 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
|
||
}
|
||
}
|
||
|
||
// 导出单例实例
|
||
export const supabaseService = new SupabaseService()
|
||
|
||
// 默认导出
|
||
export default supabaseService
|