7662 lines
249 KiB
Plaintext
7662 lines
249 KiB
Plaintext
import supa from '@/components/supadb/aksupainstance.uts'
|
||
import type { AkReqResponse } from '@/uni_modules/ak-req/index.uts'
|
||
|
||
const OLD_URL = '192.168.1.61:18000'
|
||
const NEW_URL = '119.146.131.237:9126'
|
||
|
||
function fixImageUrl(url: string | null): string {
|
||
if (url == null) return ''
|
||
if (url.indexOf(OLD_URL) >= 0) {
|
||
return url.replace(OLD_URL, NEW_URL)
|
||
}
|
||
return url
|
||
}
|
||
|
||
function fixImageUrls(urls: any): string[] {
|
||
if (urls == null) return []
|
||
if (Array.isArray(urls)) {
|
||
const result: string[] = []
|
||
const arr = urls as any[]
|
||
for (let i = 0; i < arr.length; i++) {
|
||
const fixed = fixImageUrl(arr[i] as string)
|
||
if (fixed !== '') result.push(fixed)
|
||
}
|
||
return result
|
||
}
|
||
return []
|
||
}
|
||
|
||
// 使用单例 Supabase 客户端
|
||
// const supa = createClient(SUPA_URL, SUPA_KEY)
|
||
|
||
// 辅助函数:安全获取字符串值
|
||
function safeGetString(obj: UTSJSONObject, key: string): string {
|
||
try {
|
||
const rawVal = obj.get(key)
|
||
if (rawVal == null) return ''
|
||
if (typeof rawVal == 'string') return rawVal as string
|
||
if (typeof rawVal == 'number') return (rawVal as number).toString()
|
||
if (typeof rawVal == 'boolean') return (rawVal as boolean) ? 'true' : 'false'
|
||
return ''
|
||
} catch (e) {
|
||
console.error('safeGetString error for key:', key, e)
|
||
return ''
|
||
}
|
||
}
|
||
|
||
// 辅助函数:安全获取数值
|
||
function safeGetNumber(obj: UTSJSONObject, key: string): number {
|
||
try {
|
||
const rawVal = obj.get(key)
|
||
if (rawVal == null) return 0
|
||
if (typeof rawVal == 'number') return rawVal as number
|
||
if (typeof rawVal == 'string') {
|
||
const parsed = parseFloat(rawVal as string)
|
||
return isNaN(parsed) ? 0 : parsed
|
||
}
|
||
return 0
|
||
} catch (e) {
|
||
console.error('safeGetNumber error for key:', key, e)
|
||
return 0
|
||
}
|
||
}
|
||
|
||
// 辅助函数:安全获取布尔值
|
||
function safeGetBoolean(obj: UTSJSONObject, key: string): boolean {
|
||
try {
|
||
const rawVal = obj.get(key)
|
||
if (rawVal == null) return false
|
||
if (typeof rawVal == 'boolean') return rawVal as boolean
|
||
if (typeof rawVal == 'string') return (rawVal as string) === 'true'
|
||
if (typeof rawVal == 'number') return (rawVal as number) !== 0
|
||
return false
|
||
} catch (e) {
|
||
console.error('safeGetBoolean error for key:', key, e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 辅助函数:安全获取字符串数组
|
||
function safeGetStringArray(obj: UTSJSONObject, key: string): string[] {
|
||
try {
|
||
const rawVal = obj.get(key)
|
||
if (rawVal != null && Array.isArray(rawVal)) {
|
||
return rawVal as string[]
|
||
}
|
||
return [] as string[]
|
||
} catch (e) {
|
||
console.error('safeGetStringArray error for key:', key, e)
|
||
return [] as string[]
|
||
}
|
||
}
|
||
|
||
// 辅助函数:从原始数据解析商品
|
||
function parseProductFromRaw(item: any): Product {
|
||
try {
|
||
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
|
||
const getSafeString = (key: string): string => {
|
||
const val = itemObj.get(key)
|
||
if (val == null) return ''
|
||
if (typeof val === 'string') return val
|
||
if (typeof val === 'number') return val.toString()
|
||
if (typeof val === 'boolean') return val ? 'true' : 'false'
|
||
return ''
|
||
}
|
||
|
||
const getSafeNumber = (key: string): number => {
|
||
const val = itemObj.get(key)
|
||
if (val == null) return 0
|
||
if (typeof val === 'number') return val
|
||
if (typeof val === 'string') {
|
||
const parsed = parseFloat(val)
|
||
return isNaN(parsed) ? 0 : parsed
|
||
}
|
||
return 0
|
||
}
|
||
|
||
const getSafeBoolean = (key: string): boolean => {
|
||
const val = itemObj.get(key)
|
||
if (val == null) return false
|
||
if (typeof val === 'boolean') return val
|
||
if (typeof val === 'string') return val === 'true'
|
||
if (typeof val === 'number') return val !== 0
|
||
return false
|
||
}
|
||
|
||
const getSafeStringArray = (key: string): string[] => {
|
||
const val = itemObj.get(key)
|
||
if (val != null && Array.isArray(val)) {
|
||
return val as string[]
|
||
}
|
||
return []
|
||
}
|
||
|
||
const mainImageUrl = fixImageUrl(getSafeString('main_image_url'))
|
||
const imageUrls = fixImageUrls(getSafeStringArray('image_urls'))
|
||
|
||
return {
|
||
id: getSafeString('id'),
|
||
name: getSafeString('name'),
|
||
description: getSafeString('description'),
|
||
base_price: getSafeNumber('base_price'),
|
||
price: getSafeNumber('base_price'),
|
||
original_price: getSafeNumber('market_price'),
|
||
market_price: getSafeNumber('market_price'),
|
||
main_image_url: mainImageUrl,
|
||
image_url: mainImageUrl,
|
||
images: imageUrls,
|
||
category_id: getSafeString('category_id'),
|
||
brand_id: getSafeString('brand_id'),
|
||
merchant_id: getSafeString('merchant_id'),
|
||
total_stock: getSafeNumber('total_stock'),
|
||
stock: getSafeNumber('total_stock'),
|
||
sale_count: getSafeNumber('sale_count'),
|
||
status: getSafeNumber('status'),
|
||
is_featured: getSafeBoolean('is_featured'),
|
||
is_new: getSafeBoolean('is_new'),
|
||
is_hot: getSafeBoolean('is_hot'),
|
||
specification: getSafeString('specification'),
|
||
usage: getSafeString('usage'),
|
||
side_effects: getSafeString('side_effects'),
|
||
precautions: getSafeString('precautions'),
|
||
expiry_date: getSafeString('expiry_date'),
|
||
storage_conditions: getSafeString('storage_conditions'),
|
||
approval_number: getSafeString('approval_number'),
|
||
created_at: getSafeString('created_at')
|
||
} as Product
|
||
} catch (e) {
|
||
console.error('parseProductFromRaw error:', e)
|
||
return {
|
||
id: '',
|
||
name: '',
|
||
description: '',
|
||
base_price: 0,
|
||
price: 0,
|
||
original_price: 0,
|
||
market_price: 0,
|
||
main_image_url: '',
|
||
image_url: '',
|
||
images: [] as string[],
|
||
category_id: '',
|
||
brand_id: '',
|
||
merchant_id: '',
|
||
total_stock: 0,
|
||
stock: 0,
|
||
sale_count: 0,
|
||
status: 0,
|
||
is_featured: false,
|
||
is_new: false,
|
||
is_hot: false,
|
||
specification: '',
|
||
usage: '',
|
||
side_effects: '',
|
||
precautions: '',
|
||
expiry_date: '',
|
||
storage_conditions: '',
|
||
approval_number: '',
|
||
created_at: ''
|
||
} as Product
|
||
}
|
||
}
|
||
|
||
// 类型定义
|
||
export type Brand = {
|
||
id: string
|
||
name: string
|
||
logo_url: string
|
||
description: string
|
||
}
|
||
|
||
export type Category = {
|
||
id: string
|
||
name: string
|
||
icon: string
|
||
description: string
|
||
color: string
|
||
parent_id?: string
|
||
level?: number
|
||
slug?: string
|
||
created_at?: string
|
||
}
|
||
|
||
export type Product = {
|
||
id: string
|
||
category_id: string
|
||
merchant_id: string
|
||
name: string
|
||
subtitle?: string
|
||
description?: string
|
||
base_price?: number
|
||
market_price?: number
|
||
cost_price?: number
|
||
main_image_url?: string
|
||
image_url?: string
|
||
image_urls?: string
|
||
video_urls?: string
|
||
images?: string[]
|
||
sale_count?: number
|
||
view_count?: number
|
||
total_stock?: number
|
||
available_stock?: number
|
||
is_hot?: boolean
|
||
is_new?: boolean
|
||
is_featured?: boolean
|
||
status?: number
|
||
rating_avg?: number
|
||
rating_count?: number
|
||
rating?: number
|
||
review_count?: number
|
||
brand_id?: string
|
||
shop_id?: string
|
||
tags?: string
|
||
attributes?: string
|
||
specification?: string
|
||
usage?: string
|
||
side_effects?: string
|
||
precautions?: string
|
||
expiry_date?: string
|
||
storage_conditions?: string
|
||
approval_number?: string
|
||
created_at?: string
|
||
updated_at?: string
|
||
price?: number
|
||
original_price?: number
|
||
stock?: number
|
||
sales?: number
|
||
cover?: string
|
||
brand_name?: string
|
||
category_name?: string
|
||
shop_name?: string
|
||
merchant_name?: string
|
||
}
|
||
|
||
export type Shop = {
|
||
id: string
|
||
merchant_id: string
|
||
shop_name: string
|
||
shop_logo?: string
|
||
shop_banner?: string
|
||
description?: string
|
||
contact_name?: string
|
||
contact_phone?: string
|
||
rating_avg?: number
|
||
total_sales?: number
|
||
product_count?: number
|
||
total_sales_count?: number
|
||
created_at?: string
|
||
}
|
||
|
||
export type CartItem = {
|
||
id: string
|
||
user_id: string
|
||
product_id: string
|
||
sku_id?: string
|
||
merchant_id?: string
|
||
quantity: number
|
||
selected: boolean
|
||
product_name?: string
|
||
product_image?: string
|
||
product_price?: number
|
||
product_specification?: string
|
||
shop_id?: string
|
||
shop_name?: string
|
||
created_at?: string
|
||
updated_at?: string
|
||
}
|
||
|
||
export type UserAddress = {
|
||
id: string
|
||
user_id: string
|
||
recipient_name: string
|
||
phone: string
|
||
province: string
|
||
city: string
|
||
district: string
|
||
detail_address: string
|
||
postal_code?: string
|
||
is_default: boolean
|
||
label?: string
|
||
created_at?: string
|
||
updated_at?: string
|
||
}
|
||
|
||
export type UserCoupon = {
|
||
id: string
|
||
user_id: string
|
||
template_id: string
|
||
coupon_code: string
|
||
status: number // 1: unused, 2: used, 3: expired
|
||
received_at: string
|
||
expire_at: string
|
||
used_at?: string
|
||
// join fields from template or view
|
||
template_name?: string
|
||
amount?: number
|
||
min_spend?: number
|
||
name?: string
|
||
title?: string
|
||
}
|
||
|
||
export type ChatRoom = {
|
||
id: string
|
||
user_id: string
|
||
merchant_id: string
|
||
shop_name: string
|
||
shop_logo?: string
|
||
last_message?: string
|
||
last_message_at?: string
|
||
unread_count: number
|
||
is_top: boolean
|
||
created_at?: string
|
||
updated_at?: string
|
||
}
|
||
|
||
export type Notification = {
|
||
id: string
|
||
user_id: string
|
||
type: string
|
||
title: string
|
||
content: string
|
||
icon_url?: string
|
||
link_url?: string
|
||
is_read: boolean
|
||
extra_data?: string
|
||
created_at?: string
|
||
}
|
||
|
||
export type ChatMessage = {
|
||
id: string
|
||
session_id?: string
|
||
sender_id?: string
|
||
receiver_id?: string
|
||
content: string
|
||
msg_type: string
|
||
is_read: boolean
|
||
is_from_user: boolean
|
||
extra_data?: string
|
||
created_at?: string
|
||
}
|
||
|
||
export type PaginatedResponse<T> = {
|
||
data: T[]
|
||
total: number
|
||
page: number
|
||
limit: number
|
||
hasmore: boolean
|
||
}
|
||
|
||
export type ProductSku = {
|
||
id: string
|
||
product_id: string
|
||
sku_code: string
|
||
specifications: string // JSON string
|
||
price: number
|
||
market_price?: number
|
||
cost_price?: number
|
||
stock?: number
|
||
warning_stock?: number
|
||
image_url?: string
|
||
weight?: number
|
||
status?: number
|
||
created_at?: string
|
||
}
|
||
|
||
export type AddAddressParams = {
|
||
recipient_name: string
|
||
phone: string
|
||
province: string
|
||
city: string
|
||
district: string
|
||
detail_address: string
|
||
postal_code?: string
|
||
is_default?: boolean
|
||
label?: string
|
||
}
|
||
|
||
export type UpdateAddressParams = {
|
||
recipient_name?: string
|
||
phone?: string
|
||
province?: string
|
||
city?: string
|
||
district?: string
|
||
detail_address?: string
|
||
postal_code?: string
|
||
is_default?: boolean
|
||
label?: string
|
||
}
|
||
|
||
export type CreateOrderParams = {
|
||
merchant_id: string
|
||
product_amount: number
|
||
shipping_fee: number
|
||
total_amount: number
|
||
shipping_address: any
|
||
items: any[]
|
||
}
|
||
|
||
export type ShopOrderParams = {
|
||
shipping_address: any
|
||
shopGroups: any[]
|
||
deliveryFee: number
|
||
discountAmount: number
|
||
}
|
||
|
||
export type ShopOrderResponse = {
|
||
success: boolean
|
||
orderIds: string[]
|
||
error?: string
|
||
}
|
||
|
||
export type RefundResponse = {
|
||
success: boolean
|
||
message: string
|
||
}
|
||
|
||
export type ConfirmReceiptResponse = {
|
||
success: boolean
|
||
error?: string
|
||
}
|
||
|
||
class SupabaseService {
|
||
// 获取当前用户ID
|
||
public getCurrentUserId(): string | null {
|
||
try {
|
||
// 1. 优先从 Supabase 会话获取
|
||
const session = supa.getSession()
|
||
if (session != null && session.user != null) {
|
||
return session.user.getString('id')
|
||
}
|
||
|
||
// 2. 尝试从 Storage 恢复 Session (针对 App 重启后内存丢失的情况)
|
||
// 注意:这里无法异步调用 hydrate,所以只能依赖 UI 层或 init 层的预加载
|
||
// 但我们可以返回本地存储 ID 作为 fallback,前提是 Token 有效
|
||
|
||
// 后备:尝试从本地存储获取
|
||
const userId = uni.getStorageSync('user_id')
|
||
return userId != null ? userId as string : null
|
||
} catch (e) {
|
||
console.error('获取用户ID失败:', e)
|
||
return null
|
||
}
|
||
}
|
||
|
||
// 确保会话有效 (异步)
|
||
async ensureSession(): Promise<string | null> {
|
||
let session = supa.getSession()
|
||
if (session.user == null) {
|
||
console.log('Session user is null, attempting to hydrate from storage...')
|
||
await supa.hydrateSessionFromStorage()
|
||
session = supa.getSession()
|
||
}
|
||
|
||
if (session.user != null) {
|
||
// 同步 user_id 到 storage 保持一致
|
||
const uid = session.user!!.getString('id')
|
||
if (uid != null) {
|
||
uni.setStorageSync('user_id', uid)
|
||
return uid
|
||
}
|
||
}
|
||
return this.getCurrentUserId()
|
||
}
|
||
|
||
// 获取所有分类
|
||
async getCategories(): Promise<Category[]> {
|
||
try {
|
||
const response = await supa
|
||
.from('ml_categories')
|
||
.select('*')
|
||
.order('name', { ascending: true })
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('获取分类失败:', response.error)
|
||
return []
|
||
}
|
||
|
||
const rawData = response.data
|
||
if (rawData == null) {
|
||
return []
|
||
}
|
||
|
||
const categories: Category[] = []
|
||
const rawList = rawData as any[]
|
||
for (let i: number = 0; i < rawList.length; i++) {
|
||
const item = rawList[i]
|
||
const catObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
const idVal = catObj.get('id')
|
||
const nameVal = catObj.get('name')
|
||
const iconVal = catObj.get('icon')
|
||
const iconUrlVal = catObj.get('icon_url')
|
||
const descVal = catObj.get('description')
|
||
const colorVal = catObj.get('color')
|
||
const parentIdVal = catObj.get('parent_id')
|
||
const levelVal = catObj.get('level')
|
||
|
||
const cat: Category = {
|
||
id: (typeof idVal == 'string') ? (idVal as string) : '',
|
||
name: (typeof nameVal == 'string') ? (nameVal as string) : '',
|
||
icon: (typeof iconVal == 'string') ? (iconVal as string) : ((typeof iconUrlVal == 'string') ? (iconUrlVal as string) : ''),
|
||
description: (typeof descVal == 'string') ? (descVal as string) : '',
|
||
color: (typeof colorVal == 'string') ? (colorVal as string) : '#4CAF50',
|
||
parent_id: (typeof parentIdVal == 'string') ? (parentIdVal as string) : null,
|
||
level: (typeof levelVal == 'number') ? (levelVal as number) : 0
|
||
} as Category
|
||
categories.push(cat)
|
||
}
|
||
return categories
|
||
} catch (error) {
|
||
console.error('获取分类异常:', error)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 根据ID获取单个分类
|
||
async getCategoryById(categoryId: string): Promise<Category | null> {
|
||
try {
|
||
const response = await supa
|
||
.from('ml_categories')
|
||
.select('*')
|
||
.eq('id', categoryId)
|
||
.limit(1)
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('获取分类失败:', response.error)
|
||
return null
|
||
}
|
||
|
||
const rawData = response.data
|
||
if (rawData == null) {
|
||
return null
|
||
}
|
||
|
||
// 处理数组返回值
|
||
const rawList = rawData as any[]
|
||
if (rawList.length == 0) {
|
||
return null
|
||
}
|
||
|
||
const item = rawList[0]
|
||
const catObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
const idVal = catObj.get('id')
|
||
const nameVal = catObj.get('name')
|
||
const iconVal = catObj.get('icon')
|
||
const iconUrlVal = catObj.get('icon_url')
|
||
const descVal = catObj.get('description')
|
||
const colorVal = catObj.get('color')
|
||
const parentIdVal = catObj.get('parent_id')
|
||
const levelVal = catObj.get('level')
|
||
|
||
const cat: Category = {
|
||
id: (typeof idVal == 'string') ? (idVal as string) : '',
|
||
name: (typeof nameVal == 'string') ? (nameVal as string) : '',
|
||
icon: (typeof iconVal == 'string') ? (iconVal as string) : ((typeof iconUrlVal == 'string') ? (iconUrlVal as string) : ''),
|
||
description: (typeof descVal == 'string') ? (descVal as string) : '',
|
||
color: (typeof colorVal == 'string') ? (colorVal as string) : '#4CAF50',
|
||
parent_id: (typeof parentIdVal == 'string') ? (parentIdVal as string) : null,
|
||
level: (typeof levelVal == 'number') ? (levelVal as number) : 0
|
||
} as Category
|
||
return cat
|
||
} catch (error) {
|
||
console.error('获取分类异常:', error)
|
||
return null
|
||
}
|
||
}
|
||
|
||
// 获取一级分类
|
||
async getParentCategories(): Promise<Category[]> {
|
||
try {
|
||
const response = await supa
|
||
.from('ml_categories')
|
||
.select('*')
|
||
.is('parent_id', null)
|
||
.order('sort_order', { ascending: true })
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('获取一级分类失败:', response.error)
|
||
return []
|
||
}
|
||
|
||
const rawData = response.data
|
||
if (rawData == null) {
|
||
return []
|
||
}
|
||
|
||
const categories: Category[] = []
|
||
const rawList = rawData as Array<UTSJSONObject>
|
||
for (let i = 0; i < rawList.length; i++) {
|
||
const item = rawList[i]
|
||
const icon = this.getCategoryIcon(item)
|
||
|
||
// 安全获取属性
|
||
const idVal = item['id']
|
||
const nameVal = item['name']
|
||
const descVal = item['description']
|
||
const colorVal = item['color']
|
||
const slugVal = item['slug']
|
||
|
||
const cat: Category = {
|
||
id: (typeof idVal == 'string') ? (idVal as string) : '',
|
||
name: (typeof nameVal == 'string') ? (nameVal as string) : '',
|
||
icon: icon,
|
||
description: (typeof descVal == 'string') ? (descVal as string) : '',
|
||
color: (typeof colorVal == 'string') ? (colorVal as string) : '#ff5000',
|
||
level: 1,
|
||
slug: (typeof slugVal == 'string') ? (slugVal as string) : ''
|
||
}
|
||
categories.push(cat)
|
||
}
|
||
return categories
|
||
} catch (error) {
|
||
console.error('获取一级分类异常:', error)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 获取子分类
|
||
async getSubCategories(parentId: string): Promise<Category[]> {
|
||
try {
|
||
console.log('[getSubCategories] 开始获取子分类, parentId:', parentId)
|
||
const response = await supa
|
||
.from('ml_categories')
|
||
.select('*')
|
||
.order('sort_order', { ascending: true })
|
||
.execute()
|
||
|
||
console.log('[getSubCategories] 查询完成')
|
||
|
||
if (response.error != null) {
|
||
console.error('获取子分类失败:', response.error)
|
||
return []
|
||
}
|
||
|
||
const rawData = response.data
|
||
if (rawData == null) {
|
||
console.log('[getSubCategories] 数据为空')
|
||
return []
|
||
}
|
||
|
||
const categories: Category[] = []
|
||
const rawList = rawData as any[]
|
||
console.log('[getSubCategories] 原始数据条数:', rawList.length)
|
||
|
||
for (let i = 0; i < rawList.length; i++) {
|
||
const item = rawList[i]
|
||
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
|
||
// 手动过滤 parent_id
|
||
const itemParentId = safeGetString(itemObj, 'parent_id')
|
||
const isMatch = (itemParentId.length > 0 && itemParentId == parentId)
|
||
if (!isMatch) {
|
||
continue
|
||
}
|
||
|
||
const icon = this.getCategoryIcon(itemObj)
|
||
const cat: Category = {
|
||
id: safeGetString(itemObj, 'id'),
|
||
name: safeGetString(itemObj, 'name'),
|
||
icon: icon,
|
||
description: safeGetString(itemObj, 'description'),
|
||
color: safeGetString(itemObj, 'color').length > 0 ? safeGetString(itemObj, 'color') : '#ff5000',
|
||
level: 2,
|
||
parent_id: safeGetString(itemObj, 'parent_id'),
|
||
slug: safeGetString(itemObj, 'slug')
|
||
}
|
||
categories.push(cat)
|
||
}
|
||
console.log('[getSubCategories] 返回分类数量:', categories.length)
|
||
return categories
|
||
} catch (error) {
|
||
console.error('获取子分类异常:', error)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 获取分类图标的辅助方法
|
||
getCategoryIcon(item: UTSJSONObject): string {
|
||
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
const icon = safeGetString(itemObj, 'icon')
|
||
if (icon.length > 0) {
|
||
return icon
|
||
}
|
||
const iconUrl = safeGetString(itemObj, 'icon_url')
|
||
if (iconUrl.length > 0) {
|
||
return iconUrl
|
||
}
|
||
const name = safeGetString(itemObj, 'name')
|
||
if (name.includes('数码') || name.includes('电器') || name.includes('手机')) return '📱'
|
||
if (name.includes('服装') || name.includes('衣服') || name.includes('鞋')) return '👕'
|
||
if (name.includes('食品') || name.includes('水果') || name.includes('零食')) return '🍎'
|
||
if (name.includes('美妆') || name.includes('护肤') || name.includes('化妆')) return '💄'
|
||
if (name.includes('母婴') || name.includes('婴儿') || name.includes('儿童')) return '👶'
|
||
if (name.includes('家居') || name.includes('家具') || name.includes('装饰')) return '🏠'
|
||
if (name.includes('图书') || name.includes('文具')) return '📚'
|
||
if (name.includes('运动') || name.includes('户外') || name.includes('健身')) return '⚽'
|
||
if (name.includes('医药') || name.includes('保健') || name.includes('健康')) return '💊'
|
||
return '📦'
|
||
}
|
||
|
||
// 获取所有品牌
|
||
async getBrands(): Promise<Brand[]> {
|
||
try {
|
||
console.log('[getBrands] 开始获取品牌数据...')
|
||
const response = await supa
|
||
.from('ml_brands')
|
||
.select('id, name, logo_url, description, is_active')
|
||
.order('name', { ascending: true })
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('获取品牌失败:', response.error)
|
||
return []
|
||
}
|
||
|
||
const rawData = response.data
|
||
if (rawData == null) {
|
||
console.log('[getBrands] 数据为空')
|
||
return []
|
||
}
|
||
|
||
const brands: Brand[] = []
|
||
const rawList = rawData as any[]
|
||
console.log('[getBrands] 数据条数:', rawList.length)
|
||
|
||
for (let i: number = 0; i < rawList.length; i++) {
|
||
const item = rawList[i]
|
||
const brandObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
const idVal = brandObj.get('id')
|
||
const nameVal = brandObj.get('name')
|
||
const logoVal = brandObj.get('logo_url')
|
||
const descVal = brandObj.get('description')
|
||
const isActiveVal = brandObj.get('is_active')
|
||
|
||
let isActiveBool: boolean = true
|
||
if (isActiveVal != null) {
|
||
if (typeof isActiveVal == 'boolean') {
|
||
isActiveBool = isActiveVal as boolean
|
||
} else if (typeof isActiveVal == 'number') {
|
||
isActiveBool = (isActiveVal as number) === 1
|
||
}
|
||
}
|
||
if (!isActiveBool) {
|
||
continue
|
||
}
|
||
|
||
const brand: Brand = {
|
||
id: (typeof idVal == 'string') ? (idVal as string) : '',
|
||
name: (typeof nameVal == 'string') ? (nameVal as string) : '',
|
||
logo_url: (typeof logoVal == 'string') ? (logoVal as string) : '',
|
||
description: (typeof descVal == 'string') ? (descVal as string) : ''
|
||
} as Brand
|
||
brands.push(brand)
|
||
}
|
||
console.log('[getBrands] 返回品牌数量:', brands.length)
|
||
return brands
|
||
} catch (error) {
|
||
console.error('获取品牌异常:', error)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 获取指定分类的商品
|
||
async getProductsByCategory(
|
||
categoryId: string,
|
||
page: number = 1,
|
||
limit: number = 20
|
||
): Promise<PaginatedResponse<Product>> {
|
||
try {
|
||
console.log('[getProductsByCategory] 开始查询,分类ID:', categoryId, '页码:', page)
|
||
|
||
// 在数据库层面进行分类过滤
|
||
const response = await supa
|
||
.from('ml_products_detail_view')
|
||
.select('*', { count: 'exact' })
|
||
.eq('category_id', categoryId)
|
||
.eq('status', '1') // 使用字符串 '1' 而不是整数 1
|
||
.order('sale_count', { ascending: false })
|
||
.page(page)
|
||
.limit(limit)
|
||
.execute()
|
||
|
||
console.log('[getProductsByCategory] 查询完成,total:', response.total)
|
||
|
||
if (response.error != null) {
|
||
console.error('获取商品失败:', response.error)
|
||
return {
|
||
data: [] as Product[],
|
||
total: 0,
|
||
page,
|
||
limit,
|
||
hasmore: false
|
||
}
|
||
}
|
||
|
||
const rawData = response.data
|
||
if (rawData == null) {
|
||
return {
|
||
data: [] as Product[],
|
||
total: 0,
|
||
page,
|
||
limit,
|
||
hasmore: false
|
||
}
|
||
}
|
||
|
||
const products: Product[] = []
|
||
const rawList = rawData as any[]
|
||
console.log('[getProductsByCategory] 返回数据条数:', rawList.length)
|
||
|
||
for (let i: number = 0; i < rawList.length; i++) {
|
||
const item = rawList[i]
|
||
products.push(parseProductFromRaw(item))
|
||
}
|
||
|
||
return {
|
||
data: products,
|
||
total: response.total ?? products.length,
|
||
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 {
|
||
console.log('[getProductSkus] 开始获取SKU,商品ID:', productId)
|
||
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 []
|
||
}
|
||
|
||
const rawData = response.data
|
||
if (rawData == null) return []
|
||
|
||
const skus: ProductSku[] = []
|
||
const rawList = rawData as any[]
|
||
console.log('[getProductSkus] 获取到SKU数量:', rawList.length)
|
||
|
||
for (let i = 0; i < rawList.length; i++) {
|
||
const item = rawList[i]
|
||
const skuObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
|
||
const rawId = skuObj.get('id')
|
||
const rawSkuCode = skuObj.get('sku_code')
|
||
const rawProdId = skuObj.get('product_id')
|
||
const rawPrice = skuObj.get('price')
|
||
const rawStock = skuObj.get('stock')
|
||
const rawImageUrl = skuObj.get('image_url')
|
||
const rawSpecs = skuObj.get('specifications')
|
||
|
||
let specsStr = ''
|
||
if (rawSpecs != null) {
|
||
try {
|
||
if (typeof rawSpecs == 'string') {
|
||
specsStr = rawSpecs as string
|
||
} else {
|
||
specsStr = JSON.stringify(rawSpecs)
|
||
}
|
||
} catch(e) {
|
||
console.error('解析SKU规格失败', e)
|
||
}
|
||
}
|
||
|
||
const sku: ProductSku = {
|
||
id: (typeof rawId == 'string') ? (rawId as string) : '',
|
||
product_id: (typeof rawProdId == 'string') ? (rawProdId as string) : '',
|
||
sku_code: (typeof rawSkuCode == 'string') ? (rawSkuCode as string) : '',
|
||
specifications: specsStr,
|
||
price: (typeof rawPrice == 'number') ? (rawPrice as number) : 0,
|
||
stock: (typeof rawStock == 'number') ? (rawStock as number) : 0,
|
||
image_url: (typeof rawImageUrl == 'string') ? (rawImageUrl as string) : '',
|
||
status: 1
|
||
}
|
||
skus.push(sku)
|
||
}
|
||
return skus
|
||
} 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' })
|
||
|
||
// 根据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
|
||
}
|
||
}
|
||
|
||
const rawData = response.data
|
||
if (rawData == null) {
|
||
return {
|
||
data: [] as Product[],
|
||
total: 0,
|
||
page,
|
||
limit,
|
||
hasmore: false
|
||
}
|
||
}
|
||
|
||
const products: Product[] = []
|
||
const rawList = rawData as any[]
|
||
const keywordLower = keyword.toLowerCase()
|
||
|
||
for (let i = 0; i < rawList.length; i++) {
|
||
const item = rawList[i]
|
||
const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
|
||
// 手动过滤 status
|
||
const rawStatus = prodObj.get('status')
|
||
let statusNum: number = 0
|
||
if (typeof rawStatus == 'number') {
|
||
statusNum = rawStatus as number
|
||
}
|
||
if (statusNum !== 1) continue
|
||
|
||
// 手动过滤关键词
|
||
const name = safeGetString(prodObj, 'name').toLowerCase()
|
||
const desc = safeGetString(prodObj, 'description').toLowerCase()
|
||
const subtitle = safeGetString(prodObj, 'subtitle').toLowerCase()
|
||
const brandName = safeGetString(prodObj, 'brand_name').toLowerCase()
|
||
|
||
if (name.indexOf(keywordLower) === -1 &&
|
||
desc.indexOf(keywordLower) === -1 &&
|
||
subtitle.indexOf(keywordLower) === -1 &&
|
||
brandName.indexOf(keywordLower) === -1) {
|
||
continue
|
||
}
|
||
|
||
products.push(parseProductFromRaw(item))
|
||
}
|
||
|
||
return {
|
||
data: products,
|
||
total: response.total ?? products.length,
|
||
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' })
|
||
.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 rawData = response.data
|
||
if (rawData == null) {
|
||
return { data: [] as Shop[], total: 0, page, limit, hasmore: false }
|
||
}
|
||
|
||
const shops: Shop[] = []
|
||
const dataList = rawData as any[]
|
||
for (let i = 0; i < dataList.length; i++) {
|
||
const item = dataList[i]
|
||
const shopObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
|
||
// 手动过滤 status
|
||
const rawStatus = shopObj.get('status')
|
||
let statusNum: number = 0
|
||
if (typeof rawStatus == 'number') {
|
||
statusNum = rawStatus as number
|
||
}
|
||
if (statusNum !== 1) continue
|
||
|
||
// 手动创建 Shop 对象,避免安卓端类型转换错误
|
||
const shop: Shop = {
|
||
id: shopObj.getString('id') ?? '',
|
||
merchant_id: shopObj.getString('merchant_id') ?? '',
|
||
shop_name: shopObj.getString('shop_name') ?? '',
|
||
shop_logo: shopObj.getString('shop_logo'),
|
||
shop_banner: shopObj.getString('shop_banner'),
|
||
description: shopObj.getString('description'),
|
||
contact_name: shopObj.getString('contact_name'),
|
||
contact_phone: shopObj.getString('contact_phone'),
|
||
rating_avg: shopObj.getNumber('rating_avg'),
|
||
total_sales: shopObj.getNumber('total_sales'),
|
||
product_count: shopObj.getNumber('product_count'),
|
||
total_sales_count: shopObj.getNumber('total_sales_count'),
|
||
created_at: shopObj.getString('created_at')
|
||
}
|
||
shops.push(shop)
|
||
}
|
||
|
||
return {
|
||
data: shops,
|
||
total: response.total ?? shops.length,
|
||
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 {
|
||
console.log('[getProductById] 开始获取商品详情,ID:', productId)
|
||
const response = await supa
|
||
.from('ml_products_detail_view')
|
||
.select('*')
|
||
.eq('id', productId)
|
||
.limit(1)
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('获取商品详情失败:', response.error)
|
||
return null
|
||
}
|
||
|
||
const rawData = response.data
|
||
if (rawData == null) {
|
||
console.log('[getProductById] 数据为空')
|
||
return null
|
||
}
|
||
|
||
const rawList = rawData as any[]
|
||
if (rawList.length == 0) {
|
||
console.log('[getProductById] 未找到商品')
|
||
return null
|
||
}
|
||
|
||
const item = rawList[0]
|
||
const product = parseProductFromRaw(item)
|
||
console.log('[getProductById] 成功获取商品:', product.name)
|
||
return 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 {
|
||
console.log('[getShopByMerchantId] 开始获取店铺信息,merchantId:', merchantId)
|
||
// 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) {
|
||
const shopData = (response.data as any[])[0]
|
||
const shop = this.parseShopFromRaw(shopData)
|
||
console.log('[getShopByMerchantId] 通过 merchant_id 找到店铺:', shop.shop_name)
|
||
return 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')
|
||
const shopData = (response.data as any[])[0]
|
||
const shop = this.parseShopFromRaw(shopData)
|
||
return shop
|
||
}
|
||
|
||
if (response.error != null) {
|
||
console.error('获取店铺信息失败:', response.error)
|
||
}
|
||
return null
|
||
} catch (error) {
|
||
console.error('获取店铺信息异常:', error)
|
||
return null
|
||
}
|
||
}
|
||
|
||
// 解析店铺数据
|
||
parseShopFromRaw(item: any): Shop {
|
||
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
|
||
const getSafeString = (key: string): string => {
|
||
const val = itemObj.get(key)
|
||
if (val == null) return ''
|
||
if (typeof val == 'string') return val
|
||
return ''
|
||
}
|
||
|
||
const getSafeNumber = (key: string): number => {
|
||
const val = itemObj.get(key)
|
||
if (val == null) return 0
|
||
if (typeof val == 'number') return val
|
||
return 0
|
||
}
|
||
|
||
return {
|
||
id: getSafeString('id'),
|
||
merchant_id: getSafeString('merchant_id'),
|
||
shop_name: getSafeString('shop_name'),
|
||
shop_logo: getSafeString('shop_logo'),
|
||
shop_banner: getSafeString('shop_banner'),
|
||
description: getSafeString('description'),
|
||
contact_name: getSafeString('contact_name'),
|
||
contact_phone: getSafeString('contact_phone'),
|
||
rating_avg: getSafeNumber('rating_avg'),
|
||
total_sales: getSafeNumber('total_sales'),
|
||
product_count: getSafeNumber('product_count'),
|
||
total_sales_count: getSafeNumber('total_sales_count'),
|
||
created_at: getSafeString('created_at')
|
||
} as Shop
|
||
}
|
||
|
||
// 根据商户ID获取商品列表
|
||
async getProductsByMerchantId(merchantId: string, page: number = 1, limit: number = 20): Promise<PaginatedResponse<Product>> {
|
||
try {
|
||
console.log('getProductsByMerchantId querying for:', merchantId)
|
||
|
||
// 1. Try fetching from view strictly first
|
||
let query = supa
|
||
.from('ml_products_detail_view')
|
||
.select('*', { count: 'exact' })
|
||
.eq('merchant_id', merchantId)
|
||
// .eq('status', 1) // Temporarily disabled status check to see if products exist at all
|
||
.order('created_at', { ascending: false })
|
||
.page(page)
|
||
.limit(limit)
|
||
|
||
const response = await query.execute()
|
||
|
||
// 检查视图结果:如果有错误 OR 数据为空,都尝试去查原始表
|
||
if (response.error != null || (response.data != null && (response.data as any[]).length === 0)) {
|
||
if (response.error != null) {
|
||
console.error('获取商户商品失败 (View):', response.error)
|
||
} else {
|
||
console.log('View returned 0 products, trying raw table fallback...')
|
||
}
|
||
|
||
// Fallback: Try raw table
|
||
console.log('Falling back to raw ml_products table...')
|
||
const query2 = supa
|
||
.from('ml_products')
|
||
.select('*', { count: 'exact' })
|
||
.eq('merchant_id', merchantId)
|
||
// .eq('status', 1) // Also disabled here
|
||
.order('created_at', { ascending: false })
|
||
.page(page)
|
||
.limit(limit)
|
||
|
||
const res2 = await query2.execute()
|
||
if (res2.error != null) {
|
||
console.error('获取商户商品失败 (Raw):', res2.error)
|
||
return {data:[] as Product[], total:0, page, limit, hasmore:false}
|
||
}
|
||
|
||
console.log(`Fallback (Raw) found: ${(res2.data as any[]).length} products`)
|
||
|
||
const mappedData: Product[] = []
|
||
const rawData = res2.data as any[]
|
||
for(let i = 0; i < rawData.length; i++) {
|
||
const item = JSON.parse(JSON.stringify(rawData[i])) as UTSJSONObject
|
||
const images: string[] = []
|
||
|
||
const mainImageUrl = fixImageUrl(item.getString('main_image_url'))
|
||
if (mainImageUrl != null && mainImageUrl !== '') {
|
||
images.push(mainImageUrl)
|
||
}
|
||
|
||
const imageUrlsRaw = item.get('image_urls')
|
||
if (imageUrlsRaw != null) {
|
||
try {
|
||
if (Array.isArray(imageUrlsRaw)) {
|
||
const arr = imageUrlsRaw as string[]
|
||
if (arr.length > 0 && images.length === 0) {
|
||
for (let j = 0; j < arr.length; j++) {
|
||
const fixedUrl = fixImageUrl(arr[j])
|
||
if (fixedUrl !== '') images.push(fixedUrl)
|
||
}
|
||
}
|
||
} else {
|
||
const rawUrlStr = imageUrlsRaw as string
|
||
if (rawUrlStr.startsWith('[')) {
|
||
const parsed = JSON.parse(rawUrlStr)
|
||
if (Array.isArray(parsed) && images.length === 0) {
|
||
for (let j = 0; j < parsed.length; j++) {
|
||
const fixedUrl = fixImageUrl(parsed[j] as string)
|
||
if (fixedUrl !== '') images.push(fixedUrl)
|
||
}
|
||
}
|
||
} else {
|
||
const fixedUrl = fixImageUrl(rawUrlStr)
|
||
if (fixedUrl !== '' && images.indexOf(fixedUrl) === -1) images.push(fixedUrl)
|
||
}
|
||
}
|
||
} catch(e) {
|
||
console.error('解析图片数组失败:', e)
|
||
}
|
||
}
|
||
|
||
if (images.length === 0) {
|
||
images.push('/static/default-product.png')
|
||
}
|
||
|
||
let safePrice = item.getNumber('base_price')
|
||
if (safePrice == null) {
|
||
const p = item.getNumber('price')
|
||
safePrice = p != null ? p : 0
|
||
}
|
||
|
||
let safeOriginalPrice = item.getNumber('market_price')
|
||
if (safeOriginalPrice == null) {
|
||
const op = item.getNumber('original_price')
|
||
safeOriginalPrice = op != null ? op : safePrice
|
||
}
|
||
|
||
let safeStock = item.getNumber('total_stock')
|
||
if (safeStock == null) {
|
||
let as_ = item.getNumber('available_stock')
|
||
if (as_ == null) {
|
||
const s = item.getNumber('stock')
|
||
safeStock = s != null ? s : 0
|
||
} else {
|
||
safeStock = as_
|
||
}
|
||
}
|
||
|
||
let safeSales = item.getNumber('sale_count')
|
||
if (safeSales == null) {
|
||
const s = item.getNumber('sales')
|
||
safeSales = s != null ? s : 0
|
||
}
|
||
|
||
const product: Product = {
|
||
id: item.getString('id') ?? '',
|
||
category_id: item.getString('category_id') ?? '',
|
||
merchant_id: item.getString('merchant_id') ?? '',
|
||
name: item.getString('name') ?? '',
|
||
description: item.getString('description') ?? '',
|
||
images: images,
|
||
price: safePrice,
|
||
original_price: safeOriginalPrice,
|
||
stock: safeStock,
|
||
sales: safeSales,
|
||
status: item.getNumber('status') ?? 1,
|
||
created_at: item.getString('created_at') ?? '',
|
||
base_price: safePrice,
|
||
market_price: safeOriginalPrice,
|
||
main_image_url: images.length > 0 ? images[0] : '',
|
||
sale_count: safeSales,
|
||
total_stock: safeStock
|
||
} as Product
|
||
mappedData.push(product)
|
||
}
|
||
|
||
return {
|
||
data: mappedData,
|
||
total: res2.total ?? 0,
|
||
page,
|
||
limit,
|
||
hasmore: res2.hasmore ?? false
|
||
}
|
||
}
|
||
|
||
console.log(`Merchant products found: ${(response.data as any[]).length}`)
|
||
|
||
const viewData = response.data as any[]
|
||
const parsedProducts: Product[] = []
|
||
for (let i = 0; i < viewData.length; i++) {
|
||
parsedProducts.push(parseProductFromRaw(viewData[i]))
|
||
}
|
||
|
||
return {
|
||
data: parsedProducts,
|
||
total: response.total ?? 0,
|
||
page,
|
||
limit,
|
||
hasmore: response.hasmore ?? false
|
||
}
|
||
} catch (error) {
|
||
console.error('获取商户商品异常:', error)
|
||
return {
|
||
data: [] as Product[],
|
||
total: 0,
|
||
page,
|
||
limit,
|
||
hasmore: false
|
||
}
|
||
}
|
||
}
|
||
|
||
// 根据店铺ID获取商品列表(新增)
|
||
async getProductsByShopId(shopId: string, page: number = 1, limit: number = 20): Promise<PaginatedResponse<Product>> {
|
||
try {
|
||
console.log('getProductsByShopId querying for:', shopId)
|
||
|
||
// 1. Try fetching from view with shop_id
|
||
let query = supa
|
||
.from('ml_products_detail_view')
|
||
.select('*', { count: 'exact' })
|
||
.eq('shop_id', shopId)
|
||
.order('created_at', { ascending: false })
|
||
.page(page)
|
||
.limit(limit)
|
||
|
||
const response = await query.execute()
|
||
|
||
// 检查视图结果:如果有错误 OR 数据为空,都尝试去查原始表
|
||
if (response.error != null || (response.data != null && (response.data as any[]).length === 0)) {
|
||
if (response.error != null) {
|
||
console.error('获取店铺商品失败 (View):', response.error)
|
||
} else {
|
||
console.log('View returned 0 products, trying raw table fallback...')
|
||
}
|
||
|
||
// Fallback: Try raw table with shop_id
|
||
console.log('Falling back to raw ml_products table with shop_id...')
|
||
const query2 = supa
|
||
.from('ml_products')
|
||
.select('*', { count: 'exact' })
|
||
.eq('shop_id', shopId)
|
||
.order('created_at', { ascending: false })
|
||
.page(page)
|
||
.limit(limit)
|
||
|
||
const res2 = await query2.execute()
|
||
if (res2.error != null) {
|
||
console.error('获取店铺商品失败 (Raw):', res2.error)
|
||
return {data:[] as Product[], total:0, page, limit, hasmore:false}
|
||
}
|
||
|
||
console.log(`Fallback (Raw) found: ${(res2.data as any[]).length} products`)
|
||
|
||
const mappedData: Product[] = []
|
||
const rawData = res2.data as any[]
|
||
for(let i = 0; i < rawData.length; i++) {
|
||
const item = JSON.parse(JSON.stringify(rawData[i])) as UTSJSONObject
|
||
const images: string[] = []
|
||
|
||
const mainImageUrl = fixImageUrl(item.getString('main_image_url'))
|
||
if (mainImageUrl != null && mainImageUrl !== '') {
|
||
images.push(mainImageUrl)
|
||
}
|
||
|
||
const imageUrlsRaw = item.get('image_urls')
|
||
if (imageUrlsRaw != null) {
|
||
try {
|
||
if (Array.isArray(imageUrlsRaw)) {
|
||
const arr = imageUrlsRaw as string[]
|
||
if (arr.length > 0 && images.length === 0) {
|
||
for (let j = 0; j < arr.length; j++) {
|
||
const fixedUrl = fixImageUrl(arr[j])
|
||
if (fixedUrl !== '') images.push(fixedUrl)
|
||
}
|
||
}
|
||
} else {
|
||
const rawUrlStr = imageUrlsRaw as string
|
||
if (rawUrlStr.startsWith('[')) {
|
||
const parsed = JSON.parse(rawUrlStr)
|
||
if (Array.isArray(parsed) && images.length === 0) {
|
||
for (let j = 0; j < parsed.length; j++) {
|
||
const fixedUrl = fixImageUrl(parsed[j] as string)
|
||
if (fixedUrl !== '') images.push(fixedUrl)
|
||
}
|
||
}
|
||
} else {
|
||
const fixedUrl = fixImageUrl(rawUrlStr)
|
||
if (fixedUrl !== '' && images.indexOf(fixedUrl) === -1) images.push(fixedUrl)
|
||
}
|
||
}
|
||
} catch(e) {
|
||
console.error('解析图片数组失败:', e)
|
||
}
|
||
}
|
||
|
||
if (images.length === 0) {
|
||
images.push('/static/default-product.png')
|
||
}
|
||
|
||
let safePrice = item.getNumber('base_price')
|
||
if (safePrice == null) {
|
||
const p = item.getNumber('price')
|
||
safePrice = p != null ? p : 0
|
||
}
|
||
|
||
let safeOriginalPrice = item.getNumber('market_price')
|
||
if (safeOriginalPrice == null) {
|
||
const op = item.getNumber('original_price')
|
||
safeOriginalPrice = op != null ? op : safePrice
|
||
}
|
||
|
||
let safeStock = item.getNumber('total_stock')
|
||
if (safeStock == null) {
|
||
let as_ = item.getNumber('available_stock')
|
||
if (as_ == null) {
|
||
const s = item.getNumber('stock')
|
||
safeStock = s != null ? s : 0
|
||
} else {
|
||
safeStock = as_
|
||
}
|
||
}
|
||
|
||
let safeSales = item.getNumber('sale_count')
|
||
if (safeSales == null) {
|
||
const s = item.getNumber('sales')
|
||
safeSales = s != null ? s : 0
|
||
}
|
||
|
||
const product: Product = {
|
||
id: item.getString('id') ?? '',
|
||
category_id: item.getString('category_id') ?? '',
|
||
merchant_id: item.getString('merchant_id') ?? '',
|
||
name: item.getString('name') ?? '',
|
||
description: item.getString('description') ?? '',
|
||
images: images,
|
||
price: safePrice,
|
||
original_price: safeOriginalPrice,
|
||
stock: safeStock,
|
||
sales: safeSales,
|
||
status: item.getNumber('status') ?? 1,
|
||
created_at: item.getString('created_at') ?? '',
|
||
base_price: safePrice,
|
||
market_price: safeOriginalPrice,
|
||
main_image_url: images.length > 0 ? images[0] : '',
|
||
sale_count: safeSales,
|
||
total_stock: safeStock
|
||
} as Product
|
||
mappedData.push(product)
|
||
}
|
||
|
||
return {
|
||
data: mappedData,
|
||
total: res2.total ?? 0,
|
||
page,
|
||
limit,
|
||
hasmore: res2.hasmore ?? false
|
||
}
|
||
}
|
||
|
||
console.log(`Shop products found: ${(response.data as any[]).length}`)
|
||
|
||
const viewData = response.data as any[]
|
||
const parsedProducts: Product[] = []
|
||
for (let i = 0; i < viewData.length; i++) {
|
||
parsedProducts.push(parseProductFromRaw(viewData[i]))
|
||
}
|
||
|
||
return {
|
||
data: parsedProducts,
|
||
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 {
|
||
console.log('[getHotProducts] 开始获取热销商品...')
|
||
|
||
// 在数据库层面过滤 status,获取更多数据以便手动过滤 is_hot
|
||
const response = await supa
|
||
.from('ml_products_detail_view')
|
||
.select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')
|
||
.eq('status', '1') // 使用字符串 '1'
|
||
.order('sale_count', { ascending: false })
|
||
.limit(limit * 5) // 获取更多数据以便过滤
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('获取热销商品失败:', response.error)
|
||
return []
|
||
}
|
||
|
||
const rawData = response.data
|
||
console.log('[getHotProducts] 原始数据条数:', rawData != null ? (rawData as any[]).length : 0)
|
||
if (rawData == null) {
|
||
console.log('[getHotProducts] 数据为空')
|
||
return []
|
||
}
|
||
|
||
const products: Product[] = []
|
||
const rawList = rawData as any[]
|
||
for (let i: number = 0; i < rawList.length; i++) {
|
||
const item = rawList[i]
|
||
const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
|
||
// 手动过滤 is_hot
|
||
const rawIsHot = prodObj.get('is_hot')
|
||
let isHotBool: boolean = false
|
||
if (typeof rawIsHot == 'boolean') {
|
||
isHotBool = rawIsHot as boolean
|
||
} else if (typeof rawIsHot == 'number') {
|
||
isHotBool = (rawIsHot as number) == 1
|
||
}
|
||
if (!isHotBool) continue
|
||
|
||
products.push(parseProductFromRaw(item))
|
||
|
||
// 达到目标数量就停止
|
||
if (products.length >= limit) break
|
||
}
|
||
console.log('[getHotProducts] 最终返回商品数:', products.length)
|
||
return products
|
||
} catch (error) {
|
||
console.error('获取热销商品异常:', error)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 获取按销量排序的商品(所有商品,不限制 is_hot)
|
||
async getProductsBySales(limit: number = 10): Promise<Product[]> {
|
||
try {
|
||
console.log('[getProductsBySales] 开始获取销量排序商品...')
|
||
const response = await supa
|
||
.from('ml_products_detail_view')
|
||
.select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')
|
||
.eq('status', '1')
|
||
.order('sale_count', { ascending: false })
|
||
.limit(limit)
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('获取销量排序商品失败:', response.error)
|
||
return []
|
||
}
|
||
|
||
const rawData = response.data
|
||
if (rawData == null) {
|
||
return []
|
||
}
|
||
|
||
const products: Product[] = []
|
||
const rawList = rawData as any[]
|
||
for (let i: number = 0; i < rawList.length; i++) {
|
||
const item = rawList[i]
|
||
products.push(parseProductFromRaw(item))
|
||
}
|
||
console.log('[getProductsBySales] 返回商品数:', products.length)
|
||
return products
|
||
} catch (error) {
|
||
console.error('获取销量排序商品异常:', error)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 获取按价格排序的商品(升序:从低到高)
|
||
async getProductsByPrice(limit: number = 10, ascending: boolean = true): Promise<Product[]> {
|
||
try {
|
||
const response = await supa
|
||
.from('ml_products_detail_view')
|
||
.select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')
|
||
.eq('status', '1') // 在数据库层面过滤
|
||
.order('base_price', { ascending })
|
||
.limit(limit)
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('获取价格排序商品失败:', response.error)
|
||
return []
|
||
}
|
||
|
||
const rawData = response.data
|
||
if (rawData == null) {
|
||
return []
|
||
}
|
||
|
||
const products: Product[] = []
|
||
const rawList = rawData as any[]
|
||
for (let i: number = 0; i < rawList.length; i++) {
|
||
const item = rawList[i]
|
||
products.push(parseProductFromRaw(item))
|
||
}
|
||
return products
|
||
} catch (error) {
|
||
console.error('获取价格排序商品异常:', error)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 获取新品(按创建时间排序,最新的在前)
|
||
async getProductsByNewest(limit: number = 10): Promise<Product[]> {
|
||
try {
|
||
console.log('[getProductsByNewest] 开始获取新品...')
|
||
const response = await supa
|
||
.from('ml_products_detail_view')
|
||
.select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')
|
||
.eq('status', '1')
|
||
.order('created_at', { ascending: false })
|
||
.limit(limit * 5)
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('获取新品失败:', response.error)
|
||
return []
|
||
}
|
||
|
||
const rawData = response.data
|
||
if (rawData == null) {
|
||
return []
|
||
}
|
||
|
||
const products: Product[] = []
|
||
const rawList = rawData as any[]
|
||
for (let i: number = 0; i < rawList.length; i++) {
|
||
const item = rawList[i]
|
||
const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
|
||
// 手动过滤 is_new
|
||
const rawIsNew = prodObj.get('is_new')
|
||
let isNewBool: boolean = false
|
||
if (typeof rawIsNew == 'boolean') {
|
||
isNewBool = rawIsNew as boolean
|
||
} else if (typeof rawIsNew == 'number') {
|
||
isNewBool = (rawIsNew as number) == 1
|
||
}
|
||
if (!isNewBool) continue
|
||
|
||
products.push(parseProductFromRaw(item))
|
||
if (products.length >= limit) break
|
||
}
|
||
|
||
// 如果 is_new 商品不足,补充普通商品
|
||
if (products.length < limit) {
|
||
console.log('[getProductsByNewest] is_new商品不足,补充普通商品')
|
||
const addedIds = new Set<string>()
|
||
for (let i = 0; i < products.length; i++) {
|
||
addedIds.add(products[i].id)
|
||
}
|
||
|
||
for (let i: number = 0; i < rawList.length; i++) {
|
||
const item = rawList[i]
|
||
const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
const rawId = prodObj.get('id')
|
||
const itemId = (typeof rawId == 'string') ? (rawId as string) : ''
|
||
|
||
if (!addedIds.has(itemId)) {
|
||
products.push(parseProductFromRaw(item))
|
||
addedIds.add(itemId)
|
||
if (products.length >= limit) break
|
||
}
|
||
}
|
||
}
|
||
|
||
console.log('[getProductsByNewest] 返回商品数:', products.length)
|
||
return products
|
||
} catch (error) {
|
||
console.error('获取新品异常:', error)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 获取推荐商品(is_featured=true)
|
||
async getRecommendedProducts(limit: number = 10): Promise<Product[]> {
|
||
try {
|
||
console.log('[getRecommendedProducts] 开始获取推荐商品...')
|
||
const response = await supa
|
||
.from('ml_products_detail_view')
|
||
.select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')
|
||
.eq('status', '1') // 在数据库层面过滤
|
||
.order('sale_count', { ascending: false })
|
||
.limit(limit * 5) // 获取更多数据以便过滤 is_featured
|
||
.execute()
|
||
|
||
console.log('[getRecommendedProducts] 查询完成')
|
||
|
||
if (response.error != null) {
|
||
console.error('获取推荐商品失败:', response.error)
|
||
return []
|
||
}
|
||
|
||
const rawData = response.data
|
||
if (rawData == null) {
|
||
console.log('[getRecommendedProducts] 数据为空')
|
||
return []
|
||
}
|
||
|
||
const products: Product[] = []
|
||
const rawList = rawData as any[]
|
||
console.log('[getRecommendedProducts] 数据条数:', rawList.length)
|
||
|
||
for (let i: number = 0; i < rawList.length; i++) {
|
||
const item = rawList[i]
|
||
const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
const rawFeatured = prodObj.get('is_featured')
|
||
|
||
let isFeaturedBool: boolean = false
|
||
if (typeof rawFeatured == 'boolean') {
|
||
isFeaturedBool = rawFeatured as boolean
|
||
} else if (typeof rawFeatured == 'number') {
|
||
isFeaturedBool = (rawFeatured as number) == 1
|
||
}
|
||
if (!isFeaturedBool) {
|
||
continue
|
||
}
|
||
|
||
products.push(parseProductFromRaw(item))
|
||
if (products.length >= limit) break
|
||
}
|
||
return products
|
||
} catch (error) {
|
||
console.error('获取推荐商品异常:', error)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 获取特价商品(这里假设没有specific flag, just use logic or tag if exists, defaulting to hot for now or just skip)
|
||
// Modify to use compatible logic if badge column doesn't exist
|
||
async getDiscountProducts(limit: number = 10): Promise<Product[]> {
|
||
return [] as Product[] // 暂无特价字段
|
||
}
|
||
|
||
// 获取当前用户的购物车商品(关联商品和店铺信息)
|
||
async getCartItems(): Promise<CartItem[]> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
console.warn('用户未登录,无法获取购物车')
|
||
return []
|
||
}
|
||
|
||
// 查询购物车表,并关联商品表(使用内联关联)
|
||
// 注意:使用 !inner 进行内连接,或者 left join (默认)
|
||
// 修改查询语法以符合 PostgREST 规范
|
||
// 尝试简化查询,先只查购物车,再查商品,避免复杂的嵌套查询导致 400 错误
|
||
const response = await supa
|
||
.from('ml_shopping_cart')
|
||
.select('*')
|
||
.eq('user_id', userId)
|
||
.order('created_at', { ascending: false })
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('获取购物车失败:', response.error)
|
||
return []
|
||
}
|
||
|
||
const cartData = response.data as any[]
|
||
// console.log('Raw Cart Data:', JSON.stringify(cartData))
|
||
|
||
if (cartData == null || cartData.length === 0) {
|
||
return []
|
||
}
|
||
|
||
// 收集所有 product_id 和 sku_id
|
||
const productIds: string[] = []
|
||
const skuIds: string[] = []
|
||
for (let i = 0; i < cartData.length; i++) {
|
||
let item = cartData[i]
|
||
let pid: string = ''
|
||
let sid: string = ''
|
||
if (item instanceof UTSJSONObject) {
|
||
pid = item.getString('product_id') ?? ''
|
||
sid = item.getString('sku_id') ?? ''
|
||
} else {
|
||
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
pid = itemObj.getString('product_id') ?? ''
|
||
sid = itemObj.getString('sku_id') ?? ''
|
||
}
|
||
if (pid !== '' && !productIds.includes(pid)) {
|
||
productIds.push(pid)
|
||
}
|
||
if (sid !== '' && !skuIds.includes(sid)) {
|
||
skuIds.push(sid)
|
||
}
|
||
}
|
||
|
||
// 批量查询商品详情 (使用视图关联店铺信息,修复字段名 specification -> attributes)
|
||
const productMap = new Map<string, any>()
|
||
|
||
if (productIds.length > 0) {
|
||
// Convert string array to any array for .in()
|
||
const productIdsAny: any[] = []
|
||
for(let i=0; i<productIds.length; i++) {
|
||
productIdsAny.push(productIds[i])
|
||
}
|
||
|
||
const productRes = await supa
|
||
.from('ml_products_detail_view')
|
||
.select('id,name,main_image_url,base_price,attributes,merchant_id,shop_name')
|
||
.in('id', productIdsAny)
|
||
.execute()
|
||
|
||
if (productRes.error == null && productRes.data != null) {
|
||
const products = productRes.data as any[]
|
||
for (let i = 0; i < products.length; i++) {
|
||
let p = products[i]
|
||
let pid: string = ''
|
||
|
||
if (p instanceof UTSJSONObject) {
|
||
pid = p.getString('id') ?? ''
|
||
} else {
|
||
const pObj = JSON.parse(JSON.stringify(p)) as UTSJSONObject
|
||
pid = pObj.getString('id') ?? ''
|
||
}
|
||
|
||
if (pid !== '') {
|
||
productMap.set(pid, p)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 批量查询 SKU 详情
|
||
const skuMap = new Map<string, any>()
|
||
if (skuIds.length > 0) {
|
||
const skuIdsAny: any[] = []
|
||
for(let i=0; i<skuIds.length; i++) {
|
||
skuIdsAny.push(skuIds[i])
|
||
}
|
||
const skuRes = await supa
|
||
.from('ml_product_skus')
|
||
.select('id, specifications, price, image_url')
|
||
.in('id', skuIdsAny)
|
||
.execute()
|
||
|
||
if (skuRes.error == null && skuRes.data != null) {
|
||
const skus = skuRes.data as any[]
|
||
for (let i = 0; i < skus.length; i++) {
|
||
let s = skus[i]
|
||
let sid: string = ''
|
||
if (s instanceof UTSJSONObject) {
|
||
sid = s.getString('id') ?? ''
|
||
} else {
|
||
const sObj = JSON.parse(JSON.stringify(s)) as UTSJSONObject
|
||
sid = sObj.getString('id') ?? ''
|
||
}
|
||
|
||
if (sid !== '') {
|
||
skuMap.set(sid, s)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 处理返回数据,构建CartItem数组
|
||
const cartItems: CartItem[] = []
|
||
if ((cartData as any[]) != null) { // Simplify: cartData is already any[]
|
||
const cartArray = cartData as any[]
|
||
for (let i = 0; i < cartArray.length; i++) {
|
||
let item = cartArray[i]
|
||
let itemId: string = ''
|
||
let userIdVal: string = ''
|
||
let productId: string = ''
|
||
let skuId: string = ''
|
||
let quantity: number = 0
|
||
let selected: boolean = false
|
||
let createdAt: string = ''
|
||
let updatedAt: string = ''
|
||
let cartMerchantId: string = ''
|
||
|
||
if (item instanceof UTSJSONObject) {
|
||
itemId = item.getString('id') ?? ''
|
||
userIdVal = item.getString('user_id') ?? ''
|
||
productId = item.getString('product_id') ?? ''
|
||
skuId = item.getString('sku_id') ?? ''
|
||
quantity = item.getNumber('quantity') ?? 0
|
||
selected = item.getBoolean('selected') ?? false
|
||
createdAt = item.getString('created_at') ?? ''
|
||
updatedAt = item.getString('updated_at') ?? ''
|
||
cartMerchantId = item.getString('merchant_id') ?? ''
|
||
} else {
|
||
const iObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
itemId = iObj.getString('id') ?? ''
|
||
userIdVal = iObj.getString('user_id') ?? ''
|
||
productId = iObj.getString('product_id') ?? ''
|
||
skuId = iObj.getString('sku_id') ?? ''
|
||
quantity = iObj.getNumber('quantity') ?? 0
|
||
selected = iObj.getBoolean('selected') ?? false
|
||
createdAt = iObj.getString('created_at') ?? ''
|
||
updatedAt = iObj.getString('updated_at') ?? ''
|
||
cartMerchantId = iObj.getString('merchant_id') ?? ''
|
||
}
|
||
|
||
const product = productMap.get(productId)
|
||
const sku = (skuId !== '' && skuMap.has(skuId)) ? skuMap.get(skuId) : null
|
||
|
||
let merchantId: string = cartMerchantId
|
||
let productName: string = ''
|
||
let productImage: string = ''
|
||
let productPrice: number = 0
|
||
let productSpec: string = ''
|
||
let shopNameStr: string = '未知店铺'
|
||
|
||
if (product != null) {
|
||
if (product instanceof UTSJSONObject) {
|
||
// 优先使用购物车中的 merchant_id,如果没有则使用商品中的
|
||
if (merchantId == '') {
|
||
merchantId = product.getString('merchant_id') ?? ''
|
||
}
|
||
productName = product.getString('name') ?? ''
|
||
productImage = product.getString('main_image_url') ?? ''
|
||
productPrice = product.getNumber('base_price') ?? 0
|
||
shopNameStr = product.getString('shop_name') ?? '未知店铺'
|
||
|
||
// 只有当没有sku信息时,才尝试使用商品的attributes(作为降级显示)
|
||
// 修改策略:不使用 product.attributes 作为规格显示,因为那通常包含品牌、风格等静态属性,非用户选择的规格
|
||
// 如果 sku 为空,则让前端显示默认"标准规格"
|
||
/*
|
||
if (sku == null) {
|
||
const specRaw = product.get('attributes')
|
||
if (specRaw != null) {
|
||
if (typeof specRaw === 'string') {
|
||
productSpec = specRaw
|
||
} else if (specRaw instanceof UTSJSONObject) {
|
||
const keys = UTSJSONObject.keys(specRaw)
|
||
const parts: string[] = []
|
||
for(let k = 0; k < keys.length; k++) {
|
||
let val = specRaw.get(keys[k])
|
||
if (val != null) {
|
||
parts.push(`${keys[k]}: ${val}`)
|
||
}
|
||
}
|
||
productSpec = parts.join('; ')
|
||
} else {
|
||
try {
|
||
let jsonStr = JSON.stringify(specRaw)
|
||
productSpec = jsonStr.replace(/["{}]/g, '').replace(/,/g, '; ')
|
||
} catch (e) {}
|
||
}
|
||
}
|
||
}
|
||
*/
|
||
} else {
|
||
const pObj = JSON.parse(JSON.stringify(product)) as UTSJSONObject
|
||
merchantId = pObj.getString('merchant_id') ?? ''
|
||
productName = pObj.getString('name') ?? ''
|
||
productImage = pObj.getString('main_image_url') ?? ''
|
||
productPrice = pObj.getNumber('base_price') ?? 0
|
||
shopNameStr = pObj.getString('shop_name') ?? '未知店铺'
|
||
|
||
// Same here: disable fallback to attributes
|
||
/*
|
||
if (sku == null) {
|
||
const specRaw = product['attributes']
|
||
if (specRaw != null) {
|
||
if (typeof specRaw === 'string') {
|
||
productSpec = specRaw
|
||
} else if (specRaw instanceof UTSJSONObject) {
|
||
const keys = UTSJSONObject.keys(specRaw)
|
||
const parts: string[] = []
|
||
for(let k = 0; k < keys.length; k++) {
|
||
let val = specRaw.get(keys[k])
|
||
if (val != null) {
|
||
parts.push(`${keys[k]}: ${val}`)
|
||
}
|
||
}
|
||
productSpec = parts.join('; ')
|
||
} else {
|
||
try {
|
||
let jsonStr = JSON.stringify(specRaw)
|
||
productSpec = jsonStr.replace(/["{}]/g, '').replace(/,/g, '; ')
|
||
} catch (e) {}
|
||
}
|
||
}
|
||
}
|
||
*/
|
||
}
|
||
}
|
||
|
||
// 如果有SKU信息,覆盖价格、图片和规格
|
||
if (sku != null) {
|
||
if (sku instanceof UTSJSONObject) {
|
||
const skuPrice = sku.getNumber('price')
|
||
if (skuPrice != null && skuPrice > 0) {
|
||
productPrice = skuPrice
|
||
}
|
||
const skuImg = sku.getString('image_url')
|
||
if (skuImg != null && skuImg !== '') {
|
||
productImage = skuImg
|
||
}
|
||
|
||
const specRaw = sku.get('specifications')
|
||
if (specRaw != null) {
|
||
// 优先使用SKU的规格
|
||
if (typeof specRaw === 'string') {
|
||
productSpec = specRaw
|
||
} else if (specRaw instanceof UTSJSONObject) {
|
||
const keys = ['规格', '颜色', '尺码', '容量', '版本', '型号']
|
||
const result: string[] = []
|
||
for (let k = 0; k < keys.length; k++) {
|
||
const key = keys[k]
|
||
const val = specRaw.get(key)
|
||
if (val != null && val !== '') {
|
||
result.push(`${val}`)
|
||
}
|
||
}
|
||
if (result.length > 0) {
|
||
productSpec = result.join(' ')
|
||
} else {
|
||
// Fallback for other keys
|
||
const allKeys = UTSJSONObject.keys(specRaw)
|
||
const parts: string[] = []
|
||
for(let k = 0; k < allKeys.length; k++) {
|
||
let val = specRaw.get(allKeys[k])
|
||
if (val != null) {
|
||
parts.push(`${val}`)
|
||
}
|
||
}
|
||
productSpec = parts.join(' ')
|
||
}
|
||
} else {
|
||
try {
|
||
let jsonStr = JSON.stringify(specRaw)
|
||
productSpec = jsonStr.replace(/["{}]/g, '').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 = ['规格', '颜色', '尺码', '容量', '版本', '型号']
|
||
const result: string[] = []
|
||
for (let k = 0; k < keys.length; k++) {
|
||
const key = keys[k]
|
||
const val = specRaw.get(key)
|
||
if (val != null && val !== '') {
|
||
result.push(`${val}`)
|
||
}
|
||
}
|
||
if (result.length > 0) {
|
||
productSpec = result.join(' ')
|
||
} else {
|
||
// Fallback for other keys
|
||
const allKeys = UTSJSONObject.keys(specRaw)
|
||
const parts: string[] = []
|
||
for(let k = 0; k < allKeys.length; k++) {
|
||
let val = specRaw.get(allKeys[k])
|
||
if (val != null) {
|
||
parts.push(`${val}`)
|
||
}
|
||
}
|
||
productSpec = parts.join(' ')
|
||
}
|
||
} else {
|
||
try {
|
||
let jsonStr = JSON.stringify(specRaw)
|
||
productSpec = jsonStr.replace(/["{}]/g, '').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 {
|
||
console.log('[getUserNotifications] 开始获取通知')
|
||
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 []
|
||
}
|
||
|
||
const rawData = response.data
|
||
if (rawData == null) return []
|
||
|
||
const notifications: Notification[] = []
|
||
const rawList = rawData as any[]
|
||
console.log('[getUserNotifications] 获取到通知数量:', rawList.length)
|
||
|
||
for (let i = 0; i < rawList.length; i++) {
|
||
const item = rawList[i]
|
||
const noteObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
|
||
const getSafeString = (key: string): string => {
|
||
const val = noteObj.get(key)
|
||
if (val == null) return ''
|
||
if (typeof val == 'string') return val
|
||
return ''
|
||
}
|
||
|
||
const getSafeNumber = (key: string): number => {
|
||
const val = noteObj.get(key)
|
||
if (val == null) return 0
|
||
if (typeof val == 'number') return val
|
||
return 0
|
||
}
|
||
|
||
const getSafeBoolean = (key: string): boolean => {
|
||
const val = noteObj.get(key)
|
||
if (val == null) return false
|
||
if (typeof val == 'boolean') return val
|
||
if (typeof val == 'number') return (val as number) == 1
|
||
return false
|
||
}
|
||
|
||
const note: Notification = {
|
||
id: getSafeString('id'),
|
||
user_id: getSafeString('user_id'),
|
||
type: getSafeString('type'),
|
||
title: getSafeString('title'),
|
||
content: getSafeString('content'),
|
||
is_read: getSafeBoolean('is_read'),
|
||
icon_url: getSafeString('icon_url'),
|
||
link_url: getSafeString('link_url'),
|
||
extra_data: getSafeString('extra_data'),
|
||
created_at: getSafeString('created_at')
|
||
}
|
||
notifications.push(note)
|
||
}
|
||
return notifications
|
||
} catch (e) {
|
||
console.error('获取通知异常:', e)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 获取聊天会话列表
|
||
async getChatRooms(): Promise<ChatRoom[]> {
|
||
try {
|
||
console.log('[getChatRooms] 开始获取聊天会话')
|
||
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 []
|
||
}
|
||
|
||
const rawData = response.data
|
||
if (rawData == null) return []
|
||
|
||
const rooms: ChatRoom[] = []
|
||
const rawList = rawData as any[]
|
||
console.log('[getChatRooms] 获取到会话数量:', rawList.length)
|
||
|
||
for (let i = 0; i < rawList.length; i++) {
|
||
const item = rawList[i]
|
||
const roomObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
|
||
const getSafeString = (key: string): string => {
|
||
const val = roomObj.get(key)
|
||
if (val == null) return ''
|
||
if (typeof val == 'string') return val
|
||
return ''
|
||
}
|
||
|
||
const getSafeNumber = (key: string): number => {
|
||
const val = roomObj.get(key)
|
||
if (val == null) return 0
|
||
if (typeof val == 'number') return val
|
||
return 0
|
||
}
|
||
|
||
const getSafeBoolean = (key: string): boolean => {
|
||
const val = roomObj.get(key)
|
||
if (val == null) return false
|
||
if (typeof val == 'boolean') return val
|
||
if (typeof val == 'number') return (val as number) == 1
|
||
return false
|
||
}
|
||
|
||
const room: ChatRoom = {
|
||
id: getSafeString('id'),
|
||
user_id: getSafeString('user_id'),
|
||
merchant_id: getSafeString('merchant_id'),
|
||
shop_name: getSafeString('shop_name'),
|
||
shop_logo: getSafeString('shop_logo'),
|
||
last_message: getSafeString('last_message'),
|
||
last_message_at: getSafeString('last_message_at'),
|
||
unread_count: getSafeNumber('unread_count'),
|
||
is_top: getSafeBoolean('is_top'),
|
||
created_at: getSafeString('created_at'),
|
||
updated_at: getSafeString('updated_at')
|
||
}
|
||
rooms.push(room)
|
||
}
|
||
return rooms
|
||
} 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, page: number = 1, pageSize: number = 20): Promise<ChatMessage[]> {
|
||
try {
|
||
console.log('[getChatMessages] 开始获取聊天记录,merchantId:', merchantId, 'page:', page)
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) return []
|
||
|
||
const fromIndex = (page - 1) * pageSize
|
||
const toIndex = fromIndex + pageSize - 1
|
||
|
||
// 使用 or 组合精确条件查询:(我发给商家) OR (商家发给我)
|
||
const queryStr = `and(sender_id.eq.${userId},receiver_id.eq.${merchantId}),and(sender_id.eq.${merchantId},receiver_id.eq.${userId})`
|
||
|
||
const response = await supa
|
||
.from('ml_chat_messages')
|
||
.select('*')
|
||
.or(queryStr)
|
||
.order('created_at', { ascending: false }) // 最新在前
|
||
.range(fromIndex, toIndex)
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('getChatMessages error:', response.error)
|
||
return []
|
||
}
|
||
|
||
const rawData = response.data
|
||
if (rawData == null) return []
|
||
|
||
const messages: ChatMessage[] = []
|
||
const rawList = rawData as any[]
|
||
console.log('[getChatMessages] 获取到消息数量:', rawList.length)
|
||
|
||
for (let i = 0; i < rawList.length; i++) {
|
||
const item = rawList[i]
|
||
const msgObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
|
||
const getSafeString = (key: string): string => {
|
||
const val = msgObj.get(key)
|
||
if (val == null) return ''
|
||
return val.toString()
|
||
}
|
||
|
||
const getSafeBoolean = (key: string): boolean => {
|
||
const val = msgObj.get(key)
|
||
if (val == null) return false
|
||
if (typeof val == 'boolean') return val as boolean
|
||
return (val.toString() == '1' || val.toString() == 'true')
|
||
}
|
||
|
||
const msg: ChatMessage = {
|
||
id: getSafeString('id'),
|
||
session_id: getSafeString('session_id'),
|
||
sender_id: getSafeString('sender_id'),
|
||
receiver_id: getSafeString('receiver_id'),
|
||
content: getSafeString('content'),
|
||
msg_type: getSafeString('msg_type'),
|
||
is_read: getSafeBoolean('is_read'),
|
||
is_from_user: getSafeBoolean('is_from_user'),
|
||
extra_data: getSafeString('extra_data'),
|
||
created_at: getSafeString('created_at')
|
||
}
|
||
messages.push(msg)
|
||
}
|
||
return messages
|
||
} catch (e) {
|
||
console.error('获取聊天记录异常:', e)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 发送聊天消息
|
||
async sendChatMessage(content: string, toId: string | null = null, type: string = 'text'): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) return false
|
||
|
||
const payload = {
|
||
sender_id: userId,
|
||
content: content,
|
||
msg_type: type,
|
||
is_from_user: true,
|
||
created_at: new Date().toISOString()
|
||
} as UTSJSONObject
|
||
if (toId != null) {
|
||
payload.set('receiver_id', toId)
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_chat_messages')
|
||
.insert(payload)
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('发送消息失败:', response.error)
|
||
return false
|
||
}
|
||
return true
|
||
} catch (e) {
|
||
console.error('发送消息异常:', e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 模拟客服回复
|
||
async simulateServiceReply(content: string): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) return false
|
||
|
||
const response = await supa
|
||
.from('ml_chat_messages')
|
||
.insert({
|
||
receiver_id: userId,
|
||
content: content,
|
||
msg_type: 'text',
|
||
is_from_user: false,
|
||
created_at: new Date().toISOString()
|
||
})
|
||
.execute()
|
||
return response.error == null
|
||
} catch (e) {
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 添加商品到购物车
|
||
async addToCart(productId: string, quantity: number = 1, skuId: string = '', merchantId: string = ''): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
console.error('用户未登录,无法添加商品到购物车')
|
||
return false
|
||
}
|
||
|
||
const realSkuId = (skuId != null && skuId.length > 0) ? skuId : null
|
||
const realMerchantId = (merchantId != null && merchantId.length > 0) ? merchantId : null
|
||
|
||
// 检查商品是否已在购物车中
|
||
// 注意:必须处理 sku_id 为空的情况,使用 is.null 过滤器
|
||
let query = supa
|
||
.from('ml_shopping_cart')
|
||
.select('*')
|
||
.eq('user_id', userId)
|
||
.eq('product_id', productId)
|
||
|
||
if (realSkuId != null) {
|
||
query = query.eq('sku_id', realSkuId)
|
||
} else {
|
||
query = query.is('sku_id', null)
|
||
}
|
||
|
||
const existingResponse = await query.single().execute()
|
||
|
||
let existingItem: any | null = null
|
||
|
||
if (existingResponse.data != null) {
|
||
const rawData = existingResponse.data as any
|
||
if (Array.isArray(rawData)) {
|
||
if (rawData.length > 0) {
|
||
existingItem = rawData[0]
|
||
}
|
||
} else {
|
||
existingItem = rawData
|
||
}
|
||
}
|
||
|
||
let response: AkReqResponse<any>
|
||
if (existingItem != null) {
|
||
// 商品已存在,更新数量
|
||
console.log('Found existing cart item:', JSON.stringify(existingItem))
|
||
|
||
// 确保 existingItem.id 存在
|
||
let itemId: string | null = null
|
||
let itemQty: any | null = null
|
||
|
||
if (existingItem instanceof UTSJSONObject) {
|
||
itemId = existingItem.getString('id')
|
||
itemQty = existingItem.getNumber('quantity')
|
||
} else {
|
||
const obj = JSON.parse(JSON.stringify(existingItem)) as UTSJSONObject
|
||
itemId = obj.getString('id')
|
||
itemQty = obj.getNumber('quantity')
|
||
}
|
||
|
||
if (itemId != null) {
|
||
let currentQty = 0
|
||
if (typeof itemQty === 'number') {
|
||
currentQty = itemQty as number
|
||
} else {
|
||
const qStr = `${itemQty ?? 0}`
|
||
currentQty = parseInt(qStr)
|
||
}
|
||
const newQty = currentQty + quantity
|
||
|
||
response = await supa
|
||
.from('ml_shopping_cart')
|
||
.update({
|
||
quantity: newQty,
|
||
merchant_id: realMerchantId,
|
||
updated_at: new Date().toISOString()
|
||
})
|
||
.eq('id', itemId)
|
||
.execute()
|
||
} else {
|
||
console.error('购物车已有商品但缺少ID,无法更新. Data:', JSON.stringify(existingItem))
|
||
return false
|
||
}
|
||
} else {
|
||
// 商品不存在,添加新记录
|
||
const cartPayload = new UTSJSONObject()
|
||
cartPayload.set('user_id', userId)
|
||
cartPayload.set('product_id', productId)
|
||
cartPayload.set('sku_id', realSkuId)
|
||
cartPayload.set('quantity', quantity)
|
||
cartPayload.set('selected', true)
|
||
cartPayload.set('created_at', new Date().toISOString())
|
||
cartPayload.set('updated_at', new Date().toISOString())
|
||
if (realMerchantId != null) {
|
||
cartPayload.set('merchant_id', realMerchantId)
|
||
}
|
||
|
||
response = await supa
|
||
.from('ml_shopping_cart')
|
||
.insert(cartPayload)
|
||
.execute()
|
||
}
|
||
|
||
if (response.error != null) {
|
||
console.error('添加商品到购物车失败:', response.error)
|
||
return false
|
||
}
|
||
|
||
return true
|
||
} catch (error) {
|
||
console.error('添加商品到购物车异常:', error)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 更新购物车商品数量
|
||
async updateCartItemQuantity(cartItemId: string, quantity: number): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
console.error('用户未登录,无法更新购物车')
|
||
return false
|
||
}
|
||
|
||
if (quantity < 1) {
|
||
// 数量小于1时删除商品
|
||
return await this.deleteCartItem(cartItemId)
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_shopping_cart')
|
||
.update({
|
||
quantity: quantity,
|
||
updated_at: new Date().toISOString()
|
||
})
|
||
.eq('id', cartItemId)
|
||
.eq('user_id', userId)
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('更新购物车商品数量失败:', response.error)
|
||
return false
|
||
}
|
||
|
||
return true
|
||
} catch (error) {
|
||
console.error('更新购物车商品数量异常:', error)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 更新购物车商品选中状态
|
||
async updateCartItemSelection(cartItemId: string, selected: boolean): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
console.error('用户未登录,无法更新购物车')
|
||
return false
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_shopping_cart')
|
||
.update({
|
||
selected: selected,
|
||
updated_at: new Date().toISOString()
|
||
})
|
||
.eq('id', cartItemId)
|
||
.eq('user_id', userId)
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('更新购物车商品选中状态失败:', response.error)
|
||
return false
|
||
}
|
||
|
||
return true
|
||
} catch (error) {
|
||
console.error('更新购物车商品选中状态异常:', error)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 批量更新购物车商品选中状态
|
||
async batchUpdateCartItemSelection(cartItemIds: string[], selected: boolean): Promise<boolean> {
|
||
try {
|
||
console.log('[batchUpdateCartItemSelection] 开始批量更新')
|
||
console.log('[batchUpdateCartItemSelection] cartItemIds:', JSON.stringify(cartItemIds))
|
||
console.log('[batchUpdateCartItemSelection] cartItemIds length:', cartItemIds.length)
|
||
console.log('[batchUpdateCartItemSelection] selected:', selected)
|
||
|
||
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()
|
||
|
||
console.log('[batchUpdateCartItemSelection] response.error:', response.error)
|
||
console.log('[batchUpdateCartItemSelection] response.data:', JSON.stringify(response.data))
|
||
|
||
if (response.error != null) {
|
||
console.error('批量更新购物车商品选中状态失败:', response.error)
|
||
return false
|
||
}
|
||
|
||
return true
|
||
} catch (error) {
|
||
console.error('批量更新购物车商品选中状态异常:', error)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 删除购物车商品
|
||
async deleteCartItem(cartItemId: string): Promise<boolean> {
|
||
try {
|
||
console.log('正在执行删除购物车商品,ID:', cartItemId)
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
console.error('用户未登录,无法删除购物车商品')
|
||
return false
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_shopping_cart')
|
||
.eq('id', cartItemId)
|
||
.eq('user_id', userId)
|
||
.delete()
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('删除购物车商品失败:', response.error)
|
||
return false
|
||
}
|
||
|
||
return true
|
||
} catch (error) {
|
||
console.error('删除购物车商品异常:', error)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 批量删除购物车商品
|
||
async batchDeleteCartItems(cartItemIds: string[]): Promise<boolean> {
|
||
try {
|
||
console.log('[batchDeleteCartItems] 开始删除, ids:', cartItemIds.length)
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
console.error('用户未登录,无法删除购物车商品')
|
||
return false
|
||
}
|
||
|
||
console.log('[batchDeleteCartItems] userId:', userId)
|
||
const response = await supa
|
||
.from('ml_shopping_cart')
|
||
.eq('user_id', userId)
|
||
.in('id', cartItemIds as any[])
|
||
.delete()
|
||
.execute()
|
||
|
||
console.log('[batchDeleteCartItems] response.error:', response.error)
|
||
if (response.error != null) {
|
||
console.error('批量删除购物车商品失败:', response.error)
|
||
return false
|
||
}
|
||
|
||
console.log('[batchDeleteCartItems] 删除成功')
|
||
return true
|
||
} catch (error) {
|
||
console.error('批量删除购物车商品异常:', error)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 清空购物车
|
||
async clearCart(): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
console.error('用户未登录,无法清空购物车')
|
||
return false
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_shopping_cart')
|
||
.eq('user_id', userId)
|
||
.delete()
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('清空购物车失败:', response.error)
|
||
return false
|
||
}
|
||
|
||
return true
|
||
} catch (error) {
|
||
console.error('清空购物车异常:', error)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 获取当前用户的所有地址
|
||
async getAddresses(): Promise<UserAddress[]> {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
console.warn('[getAddresses] 用户未登录,无法获取地址')
|
||
return []
|
||
}
|
||
|
||
try {
|
||
console.log('[getAddresses] 查询地址, userId:', userId)
|
||
|
||
const response = await supa
|
||
.from('ml_user_addresses')
|
||
.select('*')
|
||
.eq('user_id', userId)
|
||
.order('is_default', { ascending: false })
|
||
.order('created_at', { ascending: false })
|
||
.execute()
|
||
|
||
console.log('[getAddresses] response.error:', response.error)
|
||
console.log('[getAddresses] response.data:', JSON.stringify(response.data))
|
||
|
||
if (response.error != null) {
|
||
console.error('[getAddresses] 获取地址失败:', response.error)
|
||
return []
|
||
}
|
||
|
||
const data = response.data
|
||
if (data == null) {
|
||
return []
|
||
}
|
||
|
||
const result: UserAddress[] = []
|
||
const rawData = data as any[]
|
||
for (let i = 0; i < rawData.length; i++) {
|
||
const item = rawData[i]
|
||
let itemObj: UTSJSONObject
|
||
if (item instanceof UTSJSONObject) {
|
||
itemObj = item as UTSJSONObject
|
||
} else {
|
||
itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
}
|
||
|
||
const addr: UserAddress = {
|
||
id: itemObj.getString('id') ?? '',
|
||
user_id: itemObj.getString('user_id') ?? '',
|
||
recipient_name: itemObj.getString('receiver_name') ?? itemObj.getString('recipient_name') ?? '',
|
||
phone: itemObj.getString('receiver_phone') ?? itemObj.getString('phone') ?? '',
|
||
province: itemObj.getString('province') ?? '',
|
||
city: itemObj.getString('city') ?? '',
|
||
district: itemObj.getString('district') ?? '',
|
||
detail_address: itemObj.getString('address_detail') ?? itemObj.getString('detail_address') ?? '',
|
||
is_default: itemObj.getBoolean('is_default') ?? false,
|
||
label: itemObj.getString('label') ?? '',
|
||
created_at: itemObj.getString('created_at') ?? '',
|
||
updated_at: itemObj.getString('updated_at') ?? ''
|
||
}
|
||
result.push(addr)
|
||
}
|
||
|
||
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> {
|
||
console.log('[confirmReceipt] 开始确认收货, orderId:', orderId)
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
console.log('[confirmReceipt] userId:', userId)
|
||
if (userId == null) {
|
||
return { success: false, error: '用户未登录' }
|
||
}
|
||
|
||
const updateData = new UTSJSONObject()
|
||
updateData.set('order_status', 4)
|
||
updateData.set('delivered_at', new Date().toISOString())
|
||
updateData.set('completed_at', new Date().toISOString())
|
||
updateData.set('updated_at', new Date().toISOString())
|
||
|
||
console.log('[confirmReceipt] 准备更新订单状态, updateData:', JSON.stringify(updateData))
|
||
|
||
const response = await supa
|
||
.from('ml_orders')
|
||
.update(updateData)
|
||
.eq('id', orderId)
|
||
.eq('user_id', userId)
|
||
.execute()
|
||
|
||
console.log('[confirmReceipt] response.status:', response.status)
|
||
console.log('[confirmReceipt] response.error:', response.error)
|
||
console.log('[confirmReceipt] response.data:', JSON.stringify(response.data))
|
||
|
||
// 检查 HTTP 状态码
|
||
if (response.status != null && response.status >= 400) {
|
||
// 尝试从 response.data 中提取错误信息
|
||
let errorMsg = '请求失败'
|
||
if (response.data != null) {
|
||
try {
|
||
const errorData = response.data as UTSJSONObject
|
||
const msg = errorData.getString('message')
|
||
if (msg != null) {
|
||
errorMsg = msg
|
||
}
|
||
} catch (e) {
|
||
// ignore
|
||
}
|
||
}
|
||
console.log('[confirmReceipt] HTTP错误:', response.status, errorMsg)
|
||
return { success: false, error: errorMsg }
|
||
}
|
||
|
||
if (response.error != null) {
|
||
return { success: false, error: response.error.message }
|
||
}
|
||
|
||
// 检查 response.data 是否包含错误代码
|
||
if (response.data != null) {
|
||
try {
|
||
const respData = response.data as UTSJSONObject
|
||
const errorCode = respData.getString('code')
|
||
if (errorCode != null) {
|
||
const errorMsg = respData.getString('message') ?? '数据库错误'
|
||
console.log('[confirmReceipt] 数据库错误:', errorCode, errorMsg)
|
||
return { success: false, error: errorMsg }
|
||
}
|
||
} catch (e) {
|
||
// ignore
|
||
}
|
||
}
|
||
|
||
// 检查是否有数据被更新
|
||
if (response.data == null || (Array.isArray(response.data) && response.data.length === 0)) {
|
||
console.log('[confirmReceipt] 没有数据被更新,可能订单不存在或无权限')
|
||
return { success: false, error: '订单不存在或无权限更新' }
|
||
}
|
||
|
||
console.log('[confirmReceipt] 确认收货成功')
|
||
return { success: true }
|
||
} catch (e: any) {
|
||
console.error('[confirmReceipt] 异常:', e)
|
||
return { success: false, error: e.message }
|
||
}
|
||
}
|
||
|
||
// 取消订单
|
||
async cancelOrder(orderId: string): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
return false
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_orders')
|
||
.update({
|
||
order_status: 5,
|
||
updated_at: new Date().toISOString()
|
||
})
|
||
.eq('id', orderId)
|
||
.eq('user_id', userId)
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('取消订单失败:', response.error)
|
||
return false
|
||
}
|
||
|
||
return true
|
||
} catch (e) {
|
||
console.error('取消订单异常:', e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 删除订单
|
||
async deleteOrder(orderId: string): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
return false
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_orders')
|
||
.delete()
|
||
.eq('id', orderId)
|
||
.eq('user_id', userId)
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('删除订单失败:', response.error)
|
||
return false
|
||
}
|
||
|
||
return true
|
||
} catch (e) {
|
||
console.error('删除订单异常:', e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 确认收货
|
||
async confirmOrderReceived(orderId: string): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
return false
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_orders')
|
||
.update({
|
||
order_status: 4,
|
||
shipping_status: 3,
|
||
updated_at: new Date().toISOString()
|
||
})
|
||
.eq('id', orderId)
|
||
.eq('user_id', userId)
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('确认收货失败:', response.error)
|
||
return false
|
||
}
|
||
|
||
return true
|
||
} catch (e) {
|
||
console.error('确认收货异常:', e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 删除地址
|
||
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
|
||
// 注意:使用 limit(1) 替代 single() 以避免 Android 端类型转换错误
|
||
const response = await supa
|
||
.from('ml_user_profiles')
|
||
.select('*')
|
||
.eq('user_id', userId)
|
||
.limit(1)
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
// 如果不存在 profile,可能只有 auth user,这里暂时返回空或创建默认
|
||
return null
|
||
}
|
||
|
||
const rawData = response.data
|
||
if (rawData == null) return null
|
||
|
||
// 作为数组处理
|
||
const rawList = rawData as any[]
|
||
if (rawList.length == 0) return null
|
||
|
||
return rawList[0]
|
||
} catch (e) {
|
||
return null
|
||
}
|
||
}
|
||
|
||
// 创建订单
|
||
async createOrder(orderData: CreateOrderParams): Promise<string | null> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
console.error('CreateOrder: User not logged in')
|
||
return null
|
||
}
|
||
|
||
const orderNo = 'ML' + Date.now() + Math.floor(Math.random() * 1000)
|
||
|
||
let merchantId = orderData.merchant_id
|
||
console.log('[CreateOrder] 原始 merchant_id:', merchantId)
|
||
if (merchantId == null || merchantId == '' || merchantId == 'unknown') {
|
||
console.warn('[CreateOrder] merchant_id 为空或无效,将使用 userId 作为 fallback')
|
||
merchantId = userId
|
||
}
|
||
console.log('[CreateOrder] 最终使用的 merchant_id:', merchantId)
|
||
|
||
let shippingAddrStr = '{}'
|
||
if (orderData.shipping_address != null) {
|
||
if (typeof orderData.shipping_address === 'string') {
|
||
shippingAddrStr = orderData.shipping_address
|
||
} else {
|
||
shippingAddrStr = JSON.stringify(orderData.shipping_address)
|
||
}
|
||
}
|
||
|
||
const orderPayload = new UTSJSONObject()
|
||
orderPayload.set('user_id', userId)
|
||
orderPayload.set('merchant_id', merchantId)
|
||
orderPayload.set('order_no', orderNo)
|
||
orderPayload.set('product_amount', orderData.product_amount)
|
||
orderPayload.set('shipping_fee', orderData.shipping_fee)
|
||
orderPayload.set('total_amount', orderData.total_amount)
|
||
orderPayload.set('paid_amount', 0)
|
||
orderPayload.set('shipping_address', shippingAddrStr)
|
||
orderPayload.set('order_status', 1)
|
||
orderPayload.set('payment_status', 1)
|
||
orderPayload.set('shipping_status', 1)
|
||
orderPayload.set('created_at', new Date().toISOString())
|
||
orderPayload.set('updated_at', new Date().toISOString())
|
||
|
||
console.log('[CreateOrder] 插入订单数据:', JSON.stringify(orderPayload))
|
||
console.log('[CreateOrder] 期望的订单号:', orderNo)
|
||
|
||
const orderResponse = await supa
|
||
.from('ml_orders')
|
||
.insert(orderPayload)
|
||
.execute()
|
||
|
||
console.log('[CreateOrder] insert 完成')
|
||
console.log('[CreateOrder] orderResponse.error:', orderResponse.error)
|
||
|
||
if (orderResponse.error != null) {
|
||
console.error('[CreateOrder] 创建订单失败:', orderResponse.error)
|
||
return null
|
||
}
|
||
|
||
console.log('[CreateOrder] 开始查询新创建的订单, order_no:', orderNo)
|
||
|
||
const queryResponse = await supa
|
||
.from('ml_orders')
|
||
.select('id, order_no')
|
||
.eq('order_no', orderNo)
|
||
.execute()
|
||
|
||
console.log('[CreateOrder] queryResponse.error:', queryResponse.error)
|
||
console.log('[CreateOrder] queryResponse.data:', JSON.stringify(queryResponse.data))
|
||
|
||
if (queryResponse.error != null) {
|
||
console.error('[CreateOrder] 查询订单失败:', queryResponse.error)
|
||
return null
|
||
}
|
||
|
||
const queryData = queryResponse.data as any
|
||
let orderId = ''
|
||
|
||
console.log('[CreateOrder] queryData 类型:', typeof queryData, '是否数组:', Array.isArray(queryData))
|
||
|
||
if (Array.isArray(queryData) && queryData.length > 0) {
|
||
console.log('[CreateOrder] queryData 长度:', queryData.length)
|
||
const firstItemRaw = queryData[0]
|
||
console.log('[CreateOrder] firstItemRaw 类型:', typeof firstItemRaw)
|
||
|
||
// 将 firstItemRaw 转换为可访问的对象
|
||
const firstItemStr = JSON.stringify(firstItemRaw)
|
||
const firstItemParsed = JSON.parse(firstItemStr)
|
||
if (firstItemParsed == null) {
|
||
console.error('[CreateOrder] 解析订单数据失败')
|
||
return null
|
||
}
|
||
const firstItem = firstItemParsed as UTSJSONObject
|
||
orderId = (firstItem.getString('id') ?? '') as string
|
||
console.log('[CreateOrder] 找到新创建的订单, id:', orderId)
|
||
} else {
|
||
console.error('[CreateOrder] 未找到新创建的订单,插入可能失败')
|
||
return null
|
||
}
|
||
|
||
console.log('[CreateOrder] 订单创建成功, orderId:', orderId)
|
||
|
||
const orderItems: UTSJSONObject[] = []
|
||
console.log('[CreateOrder] orderData.items 类型:', typeof orderData.items, '是否数组:', Array.isArray(orderData.items))
|
||
|
||
if (orderData.items == null) {
|
||
console.error('[CreateOrder] orderData.items 为 null!')
|
||
return orderId
|
||
}
|
||
|
||
const rawItems = orderData.items as any[]
|
||
console.log('[CreateOrder] rawItems 长度:', rawItems.length)
|
||
|
||
if (rawItems.length === 0) {
|
||
console.warn('[CreateOrder] rawItems 为空数组,没有商品项需要插入')
|
||
return orderId
|
||
}
|
||
|
||
for(let i = 0; i < rawItems.length; i++) {
|
||
const rawItem = rawItems[i]
|
||
const itemStr = JSON.stringify(rawItem)
|
||
const itemParsed = JSON.parse(itemStr)
|
||
if (itemParsed == null) {
|
||
console.error('[CreateOrder] 商品项解析失败')
|
||
continue
|
||
}
|
||
const item = itemParsed as UTSJSONObject
|
||
|
||
const itemJson = new UTSJSONObject()
|
||
|
||
let pId = item.get('product_id')
|
||
if (pId == null) {
|
||
pId = item.get('id')
|
||
}
|
||
const productId = (pId ?? '') as string
|
||
|
||
itemJson.set('order_id', orderId)
|
||
itemJson.set('product_id', productId)
|
||
|
||
const skuIdVal = item.get('sku_id')
|
||
if (skuIdVal != null && skuIdVal !== '') {
|
||
itemJson.set('sku_id', skuIdVal as string)
|
||
}
|
||
|
||
const productName = (item.get('product_name') ?? '') as string
|
||
itemJson.set('product_name', productName)
|
||
|
||
const sName = item.get('sku_name')
|
||
itemJson.set('sku_name', (sName ?? '') as string)
|
||
|
||
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)
|
||
|
||
for (let j: number = 0; j < orderItems.length; j++) {
|
||
console.log('[CreateOrder] 开始插入订单项', j)
|
||
const itemJson = orderItems[j]
|
||
// 将 UTSJSONObject 转换为普通对象
|
||
console.log('[CreateOrder] 序列化订单项...')
|
||
const itemObjStr = JSON.stringify(itemJson)
|
||
console.log('[CreateOrder] 订单项 JSON:', itemObjStr)
|
||
const itemObjParsed = JSON.parse(itemObjStr)
|
||
console.log('[CreateOrder] itemObjParsed 类型:', typeof itemObjParsed)
|
||
if (itemObjParsed == null) {
|
||
console.error('[CreateOrder] 订单项转换失败')
|
||
continue
|
||
}
|
||
|
||
// 使用 UTSJSONObject 而不是 Record<string, any>
|
||
const itemObj = itemObjParsed as UTSJSONObject
|
||
console.log('[CreateOrder] 执行 insert...')
|
||
|
||
const itemsResponse = await supa
|
||
.from('ml_order_items')
|
||
.insert(itemObj)
|
||
.execute()
|
||
|
||
console.log('[CreateOrder] insert 完成, error:', itemsResponse.error)
|
||
if (itemsResponse.error != null) {
|
||
console.error('[CreateOrder] 创建订单项失败:', itemsResponse.error)
|
||
}
|
||
}
|
||
|
||
console.log('[CreateOrder] 订单项创建成功')
|
||
|
||
const cartItemIds: string[] = []
|
||
for(let i = 0; i < rawItems.length; i++) {
|
||
const rawItem = rawItems[i]
|
||
const itemParsed = JSON.parse(JSON.stringify(rawItem))
|
||
if (itemParsed == null) continue
|
||
const item = itemParsed 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 = JSON.parse(JSON.stringify(groups[k])) as UTSJSONObject
|
||
const gItemsRaw = g.get('items')
|
||
if (gItemsRaw == null) continue
|
||
const gItems = gItemsRaw as any[]
|
||
|
||
for(let gi = 0; gi < gItems.length; gi++) {
|
||
const it = JSON.parse(JSON.stringify(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 = JSON.parse(JSON.stringify(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 = JSON.parse(JSON.stringify(shopItems[j])) as UTSJSONObject
|
||
const siPrice = sItem.getNumber('price') ?? 0
|
||
const siQty = sItem.getNumber('quantity') ?? 1
|
||
productAmount += siPrice * siQty
|
||
}
|
||
|
||
// 简单平摊运费和优惠 (实际逻辑可能更复杂)
|
||
const ratio = grandTotal > 0 ? (productAmount / grandTotal) : 0
|
||
const shopShippingFee = params.deliveryFee * ratio
|
||
const shopDiscount = params.discountAmount * ratio
|
||
const shopTotal = productAmount + shopShippingFee - shopDiscount
|
||
|
||
const mId = group.getString('merchant_id')
|
||
const sId = group.getString('shopId')
|
||
const shopName = group.getString('shopName')
|
||
|
||
console.log('[createOrdersByShop] 店铺组信息:', {
|
||
merchant_id: mId,
|
||
shopId: sId,
|
||
shopName: shopName
|
||
})
|
||
|
||
const finalMerchantId = (mId != null && mId != '') ? mId : (sId ?? '')
|
||
console.log('[createOrdersByShop] 最终使用的 merchant_id:', finalMerchantId)
|
||
|
||
// 将 shopItems 转换为普通对象数组
|
||
const plainItems: any[] = []
|
||
for(let k = 0; k < shopItems.length; k++) {
|
||
const plainItemRaw = JSON.parse(JSON.stringify(shopItems[k]))
|
||
if (plainItemRaw != null) {
|
||
plainItems.push(plainItemRaw as any)
|
||
}
|
||
}
|
||
console.log('[createOrdersByShop] plainItems 数量:', plainItems.length)
|
||
|
||
const orderId = await this.createOrder({
|
||
merchant_id: finalMerchantId,
|
||
product_amount: productAmount,
|
||
shipping_fee: shopShippingFee,
|
||
total_amount: shopTotal,
|
||
shipping_address: params.shipping_address,
|
||
items: plainItems
|
||
})
|
||
|
||
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(*), ml_shops(shop_name)')
|
||
.eq('user_id', userId)
|
||
.order('created_at', { ascending: false })
|
||
|
||
if (status > 0) {
|
||
query = query.eq('order_status', status)
|
||
}
|
||
|
||
const response = await query.execute()
|
||
|
||
console.log('[getOrders] response.error:', response.error)
|
||
if (response.data != null && Array.isArray(response.data)) {
|
||
console.log('[getOrders] 订单数量:', response.data.length)
|
||
}
|
||
|
||
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
|
||
}
|
||
|
||
// 修复订单项中的图片URL
|
||
const orders = data as any[]
|
||
for (let i = 0; i < orders.length; i++) {
|
||
const order = orders[i]
|
||
const orderStr = JSON.stringify(order)
|
||
const orderObj = JSON.parse(orderStr) as UTSJSONObject
|
||
const itemsRaw = orderObj.get('ml_order_items')
|
||
if (itemsRaw != null && Array.isArray(itemsRaw)) {
|
||
const items = itemsRaw as any[]
|
||
for (let j = 0; j < items.length; j++) {
|
||
const item = items[j]
|
||
const itemStr = JSON.stringify(item)
|
||
const itemObj = JSON.parse(itemStr) as UTSJSONObject
|
||
const imgUrl = itemObj.getString('image_url')
|
||
if (imgUrl != null) {
|
||
itemObj['image_url'] = fixImageUrl(imgUrl)
|
||
}
|
||
const prodImg = itemObj.getString('product_image')
|
||
if (prodImg != null) {
|
||
itemObj['product_image'] = fixImageUrl(prodImg)
|
||
}
|
||
items[j] = itemObj
|
||
}
|
||
orderObj['ml_order_items'] = items
|
||
orders[i] = orderObj
|
||
}
|
||
}
|
||
|
||
return orders
|
||
} catch (error) {
|
||
console.error('获取订单列表异常:', error)
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
}
|
||
|
||
// 获取订单详情
|
||
async getOrderDetail(orderId: string): Promise<any | null> {
|
||
try {
|
||
console.log('[getOrderDetail] 开始获取订单详情,orderId:', orderId)
|
||
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!)
|
||
.limit(1)
|
||
.execute()
|
||
|
||
console.log('[getOrderDetail] response.error:', response.error)
|
||
console.log('[getOrderDetail] response.data:', JSON.stringify(response.data))
|
||
|
||
if (response.error != null) {
|
||
console.error('[getOrderDetail] 获取订单详情失败:', response.error)
|
||
return null
|
||
}
|
||
|
||
const rawData = response.data
|
||
if (rawData == null) {
|
||
console.log('[getOrderDetail] 数据为空')
|
||
return null
|
||
}
|
||
|
||
const rawList = rawData as any[]
|
||
if (rawList.length == 0) {
|
||
console.log('[getOrderDetail] 未找到订单')
|
||
return null
|
||
}
|
||
|
||
const orderData = rawList[0]
|
||
console.log('[getOrderDetail] 成功获取订单')
|
||
|
||
const orderObj = JSON.parse(JSON.stringify(orderData)) as UTSJSONObject
|
||
|
||
const result = new UTSJSONObject()
|
||
result.set('id', orderObj.get('id') ?? '')
|
||
result.set('order_no', orderObj.get('order_no') ?? '')
|
||
result.set('order_status', orderObj.get('order_status') ?? 1)
|
||
result.set('user_id', orderObj.get('user_id') ?? '')
|
||
result.set('merchant_id', orderObj.get('merchant_id') ?? '')
|
||
result.set('product_amount', orderObj.get('product_amount') ?? 0)
|
||
result.set('shipping_fee', orderObj.get('shipping_fee') ?? 0)
|
||
result.set('total_amount', orderObj.get('total_amount') ?? 0)
|
||
result.set('paid_amount', orderObj.get('paid_amount') ?? 0)
|
||
result.set('discount_amount', orderObj.get('discount_amount') ?? 0)
|
||
result.set('payment_method', orderObj.get('payment_method') ?? '')
|
||
result.set('payment_status', orderObj.get('payment_status') ?? 1)
|
||
result.set('shipping_status', orderObj.get('shipping_status') ?? 1)
|
||
result.set('created_at', orderObj.get('created_at') ?? '')
|
||
result.set('paid_at', orderObj.get('paid_at') ?? '')
|
||
result.set('shipped_at', orderObj.get('shipped_at') ?? '')
|
||
result.set('completed_at', orderObj.get('completed_at') ?? '')
|
||
result.set('shipping_address', orderObj.get('shipping_address'))
|
||
result.set('ml_order_items', orderObj.get('ml_order_items'))
|
||
// 添加物流信息
|
||
result.set('tracking_no', orderObj.get('tracking_no') ?? '')
|
||
result.set('carrier_name', orderObj.get('carrier_name') ?? '')
|
||
result.set('delivered_at', orderObj.get('delivered_at') ?? '')
|
||
|
||
return result
|
||
} catch (e) {
|
||
console.error('[getOrderDetail] 获取订单详情异常:', 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 {
|
||
console.log('[createRefund] 开始处理退款申请')
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
console.log('[createRefund] 用户未登录')
|
||
return { success: false, message: '请先登录' }
|
||
}
|
||
|
||
const d = JSON.parse(JSON.stringify(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')
|
||
|
||
console.log('[createRefund] orderId:', orderId)
|
||
console.log('[createRefund] refundType:', refundType)
|
||
console.log('[createRefund] refundReason:', refundReason)
|
||
console.log('[createRefund] refundAmount:', refundAmount)
|
||
|
||
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
|
||
}
|
||
|
||
console.log('[createRefund] 准备插入 ml_refunds')
|
||
const response = await supa
|
||
.from('ml_refunds')
|
||
.insert(payload)
|
||
.execute()
|
||
|
||
console.log('[createRefund] insert response.error:', response.error)
|
||
|
||
if (response.error != null) {
|
||
console.error('提交售后失败:', response.error)
|
||
return { success: false, message: '提交失败: ' + (response.error.message ?? '未知错误') }
|
||
}
|
||
|
||
console.log('[createRefund] 插入成功,更新订单状态')
|
||
// 更新订单状态为退款中
|
||
const updateResponse = await supa
|
||
.from('ml_orders')
|
||
.update({
|
||
order_status: 6, // 退款中
|
||
updated_at: new Date().toISOString()
|
||
})
|
||
.eq('id', orderId)
|
||
.execute()
|
||
|
||
console.log('[createRefund] update response.error:', updateResponse.error)
|
||
|
||
if (updateResponse.error != null) {
|
||
console.error('更新订单状态失败:', updateResponse.error)
|
||
// 不影响退款申请结果,只记录错误
|
||
}
|
||
|
||
console.log('[createRefund] 完成,返回成功')
|
||
return { success: true, message: '申请提交成功' }
|
||
} catch (e) {
|
||
console.error('提交售后异常:', e)
|
||
return { success: false, message: '系统异常' }
|
||
}
|
||
}
|
||
|
||
// 取消退款申请
|
||
async cancelRefund(orderId: string): Promise<RefundResponse> {
|
||
try {
|
||
console.log('[cancelRefund] 开始取消退款申请, orderId:', orderId)
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
return { success: false, message: '请先登录' }
|
||
}
|
||
|
||
// 更新退款记录状态为已取消
|
||
const refundUpdateResponse = await supa
|
||
.from('ml_refunds')
|
||
.update({
|
||
status: 4, // 已取消
|
||
updated_at: new Date().toISOString()
|
||
})
|
||
.eq('order_id', orderId)
|
||
.eq('user_id', userId)
|
||
.eq('status', 1) // 只能取消待处理的退款
|
||
.execute()
|
||
|
||
if (refundUpdateResponse.error != null) {
|
||
console.error('取消退款记录失败:', refundUpdateResponse.error)
|
||
return { success: false, message: '取消失败: ' + (refundUpdateResponse.error.message ?? '未知错误') }
|
||
}
|
||
|
||
// 恢复订单状态为已完成(假设之前是已完成状态)
|
||
const orderUpdateResponse = await supa
|
||
.from('ml_orders')
|
||
.update({
|
||
order_status: 4, // 已完成
|
||
updated_at: new Date().toISOString()
|
||
})
|
||
.eq('id', orderId)
|
||
.execute()
|
||
|
||
if (orderUpdateResponse.error != null) {
|
||
console.error('恢复订单状态失败:', orderUpdateResponse.error)
|
||
// 不影响取消退款结果,只记录错误
|
||
}
|
||
|
||
return { success: true, message: '已取消退款申请' }
|
||
} catch (e) {
|
||
console.error('取消退款异常:', e)
|
||
return { success: false, message: '系统异常' }
|
||
}
|
||
}
|
||
|
||
// 再次购买
|
||
async rePurchase(order: any): Promise<boolean> {
|
||
try {
|
||
// 将 order 转换为 UTSJSONObject 以安全访问属性
|
||
const orderObj = JSON.parse(JSON.stringify(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 = JSON.parse(JSON.stringify(items[i])) as UTSJSONObject
|
||
const productId = item.getString('product_id')
|
||
const skuId = item.getString('sku_id')
|
||
// 数量可能是数字或字符串
|
||
const quantity = item.getNumber('quantity') ?? 1
|
||
|
||
if (productId != null) {
|
||
await this.addToCart(productId, quantity, skuId ?? '', '')
|
||
}
|
||
}
|
||
return true
|
||
} catch (e) {
|
||
console.error('rePurchase error', e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 申请售后 (Legacy/Simple update)
|
||
async applyRefund(orderId: string, reason: string): Promise<boolean> {
|
||
try {
|
||
// 更新订单状态为 退款中 (6)
|
||
const response = await supa
|
||
.from('ml_orders')
|
||
.update({
|
||
order_status: 6,
|
||
cancel_reason: reason,
|
||
updated_at: new Date().toISOString()
|
||
})
|
||
.eq('id', orderId)
|
||
.execute()
|
||
|
||
return response.error === null
|
||
} catch (e) {
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 获取售后记录列表
|
||
async getRefunds(statusList: number[] = [], page: number = 1, pageSize: number = 10): Promise<any[]> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
let query = supa
|
||
.from('ml_refunds')
|
||
.select(`
|
||
*,
|
||
order:ml_orders!inner (
|
||
order_no,
|
||
created_at,
|
||
ml_order_items (
|
||
product_id,
|
||
product_name,
|
||
image_url
|
||
)
|
||
)
|
||
`)
|
||
.eq('user_id', userId)
|
||
.order('created_at', { ascending: false })
|
||
|
||
if (statusList.length > 0) {
|
||
// 显式转换为 any[] 以匹配 .in 方法的参数要求
|
||
const anyList = statusList as any[]
|
||
query = query.in('status', anyList)
|
||
}
|
||
|
||
query = query.range((page - 1) * pageSize, page * pageSize - 1)
|
||
|
||
const response = await query.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('获取售后列表失败:', response.error)
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
const data = response.data
|
||
if (data == null) {
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
return data
|
||
} catch (e) {
|
||
console.error('获取售后列表异常:', e)
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
}
|
||
|
||
async deleteRefund(refundId: string): Promise<boolean> {
|
||
try {
|
||
const response = await supa
|
||
.from('ml_refunds')
|
||
.delete()
|
||
.eq('id', refundId)
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('删除退款记录失败:', response.error)
|
||
return false
|
||
}
|
||
|
||
return true
|
||
} catch (e) {
|
||
console.error('删除退款记录异常:', e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
async getUserBalanceNumber(): 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'
|
||
.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) {
|
||
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 {
|
||
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 itemObj: UTSJSONObject
|
||
if (item instanceof UTSJSONObject) {
|
||
itemObj = item as UTSJSONObject
|
||
} else {
|
||
itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
}
|
||
|
||
// target_id 可能是 Integer 或 String 类型,需要安全转换
|
||
const targetIdRaw = itemObj.get('target_id')
|
||
let pid = ''
|
||
if (targetIdRaw != null) {
|
||
if (typeof targetIdRaw === 'string') {
|
||
pid = targetIdRaw as string
|
||
} else if (typeof targetIdRaw === 'number') {
|
||
pid = (targetIdRaw as number).toString()
|
||
}
|
||
}
|
||
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) {
|
||
newItem = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
} else {
|
||
newItem = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
}
|
||
|
||
// target_id 可能是 Integer 或 String 类型,需要安全转换
|
||
const targetIdRaw = newItem.get('target_id')
|
||
let targetId = ''
|
||
if (targetIdRaw != null) {
|
||
if (typeof targetIdRaw === 'string') {
|
||
targetId = targetIdRaw as string
|
||
} else if (typeof targetIdRaw === 'number') {
|
||
targetId = (targetIdRaw as number).toString()
|
||
}
|
||
}
|
||
|
||
if (targetId !== '') {
|
||
const product = productMap.get(targetId)
|
||
if (product != null) {
|
||
newItem.set('ml_products', product)
|
||
result.push(newItem)
|
||
}
|
||
}
|
||
}
|
||
|
||
return result
|
||
} catch (e) {
|
||
console.error('获取收藏列表异常:', e)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 获取足迹列表
|
||
async getFootprints(): Promise<any[]> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
console.log('[getFootprints] 用户未登录')
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
console.log('[getFootprints] 查询足迹, userId:', userId)
|
||
|
||
// 1. 获取足迹记录
|
||
const response = await supa
|
||
.from('ml_user_footprints')
|
||
.select('*')
|
||
.eq('user_id', userId!)
|
||
.order('updated_at', { ascending: false })
|
||
.limit(50)
|
||
.execute()
|
||
|
||
console.log('[getFootprints] 足迹查询 error:', response.error)
|
||
console.log('[getFootprints] 足迹查询 data:', JSON.stringify(response.data))
|
||
|
||
if (response.error != null) {
|
||
console.error('[getFootprints] 获取足迹失败:', response.error)
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
const footprints = response.data as any[]
|
||
if (footprints == null || footprints.length === 0) {
|
||
console.log('[getFootprints] 没有足迹记录')
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
console.log('[getFootprints] 足迹记录数量:', footprints.length)
|
||
|
||
// 2. 收集商品ID
|
||
const productIds: string[] = []
|
||
for (let i = 0; i < footprints.length; i++) {
|
||
let item = footprints[i]
|
||
let pid = ''
|
||
if (item instanceof UTSJSONObject) {
|
||
pid = item.getString('product_id') ?? ''
|
||
} else {
|
||
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
pid = itemObj.getString('product_id') ?? ''
|
||
}
|
||
if (pid !== '' && !productIds.includes(pid)) productIds.push(pid)
|
||
}
|
||
|
||
if (productIds.length === 0) return []
|
||
|
||
const productIdsAny: any[] = []
|
||
for(let i=0; i<productIds.length; i++) {
|
||
productIdsAny.push(productIds[i])
|
||
}
|
||
|
||
// 3. 批量查询商品详情
|
||
const productRes = await supa
|
||
.from('ml_products_detail_view')
|
||
.select('id, name, main_image_url, base_price, market_price, sale_count, merchant_id, shop_name')
|
||
.in('id', productIdsAny)
|
||
.execute()
|
||
|
||
// 如果视图失败,回退查基础表
|
||
let products: any[] = []
|
||
if (productRes.error == null && productRes.data != null) {
|
||
products = productRes.data as any[]
|
||
} else {
|
||
console.warn('View查询失败,尝试查询基础表')
|
||
const baseRes = await supa
|
||
.from('ml_products')
|
||
.select('id, name, main_image_url, base_price, market_price, sale_count, merchant_id')
|
||
.in('id', productIdsAny)
|
||
.execute()
|
||
if (baseRes.error == null) {
|
||
products = baseRes.data as any[]
|
||
}
|
||
}
|
||
|
||
const productMap = new Map<string, any>()
|
||
for(let i=0; i<products.length; i++) {
|
||
let p = products[i]
|
||
let pid = ''
|
||
if (p instanceof UTSJSONObject) {
|
||
pid = p.getString('id') ?? ''
|
||
} else {
|
||
const pObj = JSON.parse(JSON.stringify(p)) as UTSJSONObject
|
||
pid = pObj.getString('id') ?? ''
|
||
}
|
||
if (pid !== '') productMap.set(pid, p)
|
||
}
|
||
|
||
// 4. 组合结果
|
||
const result: any[] = []
|
||
for (let i = 0; i < footprints.length; i++) {
|
||
let fp = footprints[i]
|
||
let pid = ''
|
||
let viewTime = 0
|
||
|
||
if (fp instanceof UTSJSONObject) {
|
||
pid = fp.getString('product_id') ?? ''
|
||
const dateStr = fp.getString('updated_at')
|
||
if (dateStr != null) viewTime = new Date(dateStr).getTime()
|
||
} else {
|
||
const fpObj = JSON.parse(JSON.stringify(fp)) as UTSJSONObject
|
||
pid = fpObj.getString('product_id') ?? ''
|
||
const dateStr = fpObj.getString('updated_at')
|
||
if (dateStr != null) viewTime = new Date(dateStr).getTime()
|
||
}
|
||
|
||
const product = productMap.get(pid)
|
||
if (product != null) {
|
||
let pName = ''
|
||
let pImage = ''
|
||
let pPrice = 0
|
||
let pOriginalPrice = 0
|
||
let pSales = 0
|
||
let pShopId = ''
|
||
let pShopName = ''
|
||
|
||
if (product instanceof UTSJSONObject) {
|
||
pName = product.getString('name') ?? ''
|
||
pImage = product.getString('main_image_url') ?? ''
|
||
pPrice = product.getNumber('base_price') ?? 0
|
||
pOriginalPrice = product.getNumber('market_price') ?? 0
|
||
pSales = product.getNumber('sale_count') ?? 0
|
||
pShopId = product.getString('merchant_id') ?? ''
|
||
pShopName = product.getString('shop_name') ?? ''
|
||
} else {
|
||
const pObj = JSON.parse(JSON.stringify(product)) as UTSJSONObject
|
||
pName = pObj.getString('name') ?? ''
|
||
pImage = pObj.getString('main_image_url') ?? ''
|
||
pPrice = pObj.getNumber('base_price') ?? 0
|
||
pOriginalPrice = pObj.getNumber('market_price') ?? 0
|
||
pSales = pObj.getNumber('sale_count') ?? 0
|
||
pShopId = pObj.getString('merchant_id') ?? ''
|
||
pShopName = pObj.getString('shop_name') ?? ''
|
||
}
|
||
|
||
const fpObj = new UTSJSONObject()
|
||
fpObj.set('id', pid)
|
||
fpObj.set('name', pName)
|
||
fpObj.set('price', pPrice)
|
||
fpObj.set('original_price', pOriginalPrice)
|
||
fpObj.set('image', pImage)
|
||
fpObj.set('sales', pSales)
|
||
fpObj.set('shopId', pShopId)
|
||
fpObj.set('shopName', pShopName)
|
||
fpObj.set('merchant_id', pShopId)
|
||
fpObj.set('viewTime', viewTime)
|
||
result.push(fpObj)
|
||
}
|
||
}
|
||
|
||
return result
|
||
} catch (error) {
|
||
console.error('获取足迹异常:', error)
|
||
return []
|
||
}
|
||
}
|
||
|
||
// 添加/更新足迹
|
||
async addFootprint(productId: string): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
console.log('[addFootprint] 用户未登录')
|
||
return false
|
||
}
|
||
|
||
console.log('[addFootprint] 添加足迹, userId:', userId, 'productId:', productId)
|
||
|
||
// 检查是否已存在
|
||
const checkRes = await supa
|
||
.from('ml_user_footprints')
|
||
.select('id')
|
||
.eq('user_id', userId!)
|
||
.eq('product_id', productId)
|
||
.execute()
|
||
|
||
console.log('[addFootprint] 检查结果 error:', checkRes.error)
|
||
console.log('[addFootprint] 检查结果 data:', JSON.stringify(checkRes.data))
|
||
|
||
const checkData = checkRes.data as any[]
|
||
const exists = checkData != null && Array.isArray(checkData) && checkData.length > 0
|
||
|
||
if (checkRes.error == null && exists) {
|
||
console.log('[addFootprint] 足迹已存在,更新时间')
|
||
// 更新时间
|
||
const updateRes = await supa
|
||
.from('ml_user_footprints')
|
||
.update({ updated_at: new Date().toISOString() })
|
||
.eq('user_id', userId!)
|
||
.eq('product_id', productId)
|
||
.execute()
|
||
console.log('[addFootprint] 更新结果 error:', updateRes.error)
|
||
} else {
|
||
console.log('[addFootprint] 足迹不存在,插入新记录')
|
||
// 插入新记录
|
||
const insertPayload = new UTSJSONObject()
|
||
insertPayload.set('user_id', userId!)
|
||
insertPayload.set('product_id', productId)
|
||
insertPayload.set('created_at', new Date().toISOString())
|
||
insertPayload.set('updated_at', new Date().toISOString())
|
||
|
||
const insertRes = await supa
|
||
.from('ml_user_footprints')
|
||
.insert(insertPayload)
|
||
.execute()
|
||
console.log('[addFootprint] 插入结果 error:', insertRes.error)
|
||
}
|
||
return true
|
||
} catch (e) {
|
||
console.error('[addFootprint] 添加足迹异常:', e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 删除单个足迹
|
||
async deleteFootprint(productId: string): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
console.log('[deleteFootprint] 用户未登录')
|
||
return false
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_user_footprints')
|
||
.eq('user_id', userId)
|
||
.eq('product_id', productId)
|
||
.delete()
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('[deleteFootprint] 删除足迹失败:', response.error)
|
||
return false
|
||
}
|
||
|
||
console.log('[deleteFootprint] 删除足迹成功')
|
||
return true
|
||
} catch (e) {
|
||
console.error('[deleteFootprint] 删除足迹异常:', e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 批量删除足迹
|
||
async deleteFootprints(productIds: string[]): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
console.log('[deleteFootprints] 用户未登录')
|
||
return false
|
||
}
|
||
|
||
const idsAny: any[] = []
|
||
for (let i = 0; i < productIds.length; i++) {
|
||
idsAny.push(productIds[i])
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_user_footprints')
|
||
.eq('user_id', userId)
|
||
.in('product_id', idsAny)
|
||
.delete()
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('[deleteFootprints] 批量删除足迹失败:', response.error)
|
||
return false
|
||
}
|
||
|
||
console.log('[deleteFootprints] 批量删除足迹成功')
|
||
return true
|
||
} catch (e) {
|
||
console.error('[deleteFootprints] 批量删除足迹异常:', e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 清空所有足迹
|
||
async clearFootprints(): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
console.log('[clearFootprints] 用户未登录')
|
||
return false
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_user_footprints')
|
||
.eq('user_id', userId)
|
||
.delete()
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('[clearFootprints] 清空足迹失败:', response.error)
|
||
return false
|
||
}
|
||
|
||
console.log('[clearFootprints] 清空足迹成功')
|
||
return true
|
||
} catch (e) {
|
||
console.error('[clearFootprints] 清空足迹异常:', e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
async getAddressList(): Promise<UserAddress[]> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
const empty: UserAddress[] = []
|
||
return empty
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_user_addresses')
|
||
.select('*, recipient_name:receiver_name, phone:receiver_phone, detail_address:address_detail')
|
||
.eq('user_id', userId!)
|
||
.order('is_default', { ascending: false })
|
||
.order('created_at', { ascending: false })
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('获取地址列表失败:', response.error)
|
||
const empty: UserAddress[] = []
|
||
return empty
|
||
}
|
||
return response.data as UserAddress[]
|
||
} catch (e) {
|
||
console.error('获取地址列表异常:', e)
|
||
const empty: UserAddress[] = []
|
||
return empty
|
||
}
|
||
}
|
||
|
||
// 设置默认地址
|
||
async setDefaultAddress(addressId: string): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
console.error('用户未登录,无法设置默认地址')
|
||
return false
|
||
}
|
||
|
||
// 先取消所有默认地址
|
||
await this.clearDefaultAddress(userId!)
|
||
|
||
// 设置新的默认地址
|
||
const response = await supa
|
||
.from('ml_user_addresses')
|
||
.update({
|
||
is_default: true,
|
||
updated_at: new Date().toISOString()
|
||
})
|
||
.eq('id', addressId)
|
||
.eq('user_id', userId!)
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('设置默认地址失败:', response.error)
|
||
return false
|
||
}
|
||
|
||
return true
|
||
} catch (error) {
|
||
console.error('设置默认地址异常:', error)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 获取用户优惠券列表
|
||
async getUserCoupons(status: number = 1): Promise<UserCoupon[]> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
const empty: UserCoupon[] = []
|
||
return empty
|
||
}
|
||
|
||
// 假设有一个视图或者直接关联 ml_user_coupons 和 ml_coupon_templates
|
||
// 这里简化处理,尝试直接从 ml_user_coupons 读取,并且加入 template 信息
|
||
// 如果没有 view,可能需要改为两个查询或者使用 left join
|
||
const response = await supa
|
||
.from('ml_user_coupons')
|
||
.select('*, template:ml_coupon_templates(name, amount, min_spend)')
|
||
.eq('user_id', userId!)
|
||
.eq('status', status.toString())
|
||
.order('expire_at', { ascending: true })
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('获取优惠券失败:', response.error)
|
||
const empty: UserCoupon[] = []
|
||
return empty
|
||
}
|
||
|
||
// 安全处理返回数据 - 安卓端可能是 UTSJSONObject 或 UTSArray
|
||
const rawData: any[] = []
|
||
const respData = response.data
|
||
console.log('[getUserCoupons] 原始数据类型:', typeof respData, '是否数组:', Array.isArray(respData))
|
||
if (respData != null) {
|
||
if (Array.isArray(respData)) {
|
||
const arr = respData as any[]
|
||
console.log('[getUserCoupons] 数组长度:', arr.length)
|
||
for (let i = 0; i < arr.length; i++) {
|
||
rawData.push(arr[i])
|
||
}
|
||
} else if (respData instanceof UTSJSONObject) {
|
||
// 单个对象情况,包装成数组
|
||
console.log('[getUserCoupons] 单个对象,包装成数组')
|
||
rawData.push(respData)
|
||
} else {
|
||
// 尝试 JSON 转换
|
||
try {
|
||
const parsed = JSON.parse(JSON.stringify(respData))
|
||
console.log('[getUserCoupons] JSON转换后是否数组:', Array.isArray(parsed))
|
||
if (Array.isArray(parsed)) {
|
||
console.log('[getUserCoupons] 转换后数组长度:', parsed.length)
|
||
for (let i = 0; i < parsed.length; i++) {
|
||
rawData.push(parsed[i])
|
||
}
|
||
}
|
||
} catch (parseErr) {
|
||
console.error('解析优惠券数据异常:', parseErr)
|
||
}
|
||
}
|
||
}
|
||
console.log('[getUserCoupons] 最终rawData长度:', rawData.length)
|
||
|
||
// 映射数据,将 template 的字段展平
|
||
const coupons: UserCoupon[] = []
|
||
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
|
||
}
|
||
|
||
// 创建真正的 UserCoupon 对象,而不是 UTSJSONObject
|
||
const couponItem: UserCoupon = {
|
||
id: itemId,
|
||
user_id: itemUserId,
|
||
template_id: itemTmplId,
|
||
coupon_code: itemCode,
|
||
status: itemStatus,
|
||
received_at: itemRecv,
|
||
expire_at: itemExpire,
|
||
template_name: tName,
|
||
amount: tAmount,
|
||
min_spend: tMin
|
||
}
|
||
|
||
coupons.push(couponItem)
|
||
}
|
||
|
||
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')
|
||
.gt('expire_at', new Date().toISOString())
|
||
.limit(1)
|
||
.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 {
|
||
console.log('[fetchShopCoupons] 开始获取优惠券,merchantId:', merchantId)
|
||
// 查询该商家的优惠券 + 平台通用券 (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') // 使用字符串 '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
|
||
}
|
||
console.log('[fetchShopCoupons] 获取到优惠券数量:', (data as any[]).length)
|
||
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 sendMessage(merchantId: string, content: string, msgType: string = 'text'): Promise<boolean> {
|
||
// 确保 session 有效
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
console.error("sendMessage failed: user not logged in or session lost")
|
||
return false
|
||
}
|
||
|
||
try {
|
||
// Debug check
|
||
// const session = supa.getSession()
|
||
// console.log("Sending check: UserID", userId, "AuthID:", session.user?.getString('id'))
|
||
|
||
const msg = {
|
||
sender_id: userId!,
|
||
receiver_id: merchantId,
|
||
content: content,
|
||
msg_type: msgType,
|
||
is_read: false,
|
||
is_from_user: true
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_chat_messages')
|
||
.insert(msg)
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('sendMessage error:', response.error)
|
||
return false
|
||
}
|
||
return true
|
||
} catch (e) {
|
||
console.error('sendMessage exception:', e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 标记会话已读
|
||
async markRead(merchantId: string): Promise<boolean> {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) return false
|
||
try {
|
||
const response = await supa
|
||
.from('ml_chat_messages')
|
||
.update({ is_read: true })
|
||
.eq('sender_id', merchantId)
|
||
.eq('receiver_id', userId)
|
||
.eq('is_read', false)
|
||
.execute()
|
||
|
||
if (response.error != null) return false
|
||
} catch (e) { return false }
|
||
return true
|
||
}
|
||
|
||
// 提交商品评价
|
||
async submitProductReviews(reviews: Array<UTSJSONObject>): Promise<boolean> {
|
||
try {
|
||
for (let i: number = 0; i < reviews.length; i++) {
|
||
const review = reviews[i]
|
||
const response = await supa
|
||
.from('ml_product_reviews')
|
||
.insert(review)
|
||
.execute()
|
||
if (response.error != null) {
|
||
console.error('提交商品评价失败:', response.error)
|
||
return false
|
||
}
|
||
}
|
||
return true
|
||
} catch (e) {
|
||
console.error('提交商品评价失败:', e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 提交店铺评价
|
||
async submitShopReview(review: UTSJSONObject): Promise<boolean> {
|
||
try {
|
||
const response = await supa
|
||
.from('ml_shop_reviews')
|
||
.insert(review)
|
||
.execute()
|
||
return response.error == null
|
||
} catch (e) {
|
||
console.error('提交店铺评价失败:', e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 更新订单状态
|
||
async updateOrderStatus(orderId: string, status: number): Promise<boolean> {
|
||
try {
|
||
const updateData = new UTSJSONObject()
|
||
updateData.set('order_status', status)
|
||
const response = await supa
|
||
.from('ml_orders')
|
||
.update(updateData)
|
||
.eq('id', orderId)
|
||
.execute()
|
||
return response.error == null
|
||
} catch (e) {
|
||
console.error('更新订单状态失败:', e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// ==================== 智能推荐相关API ====================
|
||
|
||
// 获取热搜词(全站搜索频率最高的关键词)
|
||
async getHotKeywords(limit: number = 10): Promise<string[]> {
|
||
try {
|
||
const response = await supa
|
||
.from('ml_search_history')
|
||
.select('keyword')
|
||
.order('created_at', { ascending: false })
|
||
.limit(100)
|
||
.execute()
|
||
|
||
if (response.error != null || response.data == null) {
|
||
return [] as string[]
|
||
}
|
||
|
||
// 统计关键词频率
|
||
const keywordCount = new Map<string, number>()
|
||
const rawList = response.data as any[]
|
||
for (let i = 0; i < rawList.length; i++) {
|
||
const item = rawList[i]
|
||
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
const keyword = safeGetString(itemObj, 'keyword').toLowerCase().trim()
|
||
if (keyword.length > 0) {
|
||
const count = keywordCount.get(keyword) ?? 0
|
||
keywordCount.set(keyword, count + 1)
|
||
}
|
||
}
|
||
|
||
// 按频率排序并返回前N个 - UTS兼容方式
|
||
// 将Map转换为数组进行排序
|
||
type KeywordEntry = {
|
||
keyword: string
|
||
count: number
|
||
}
|
||
const entryArray: KeywordEntry[] = []
|
||
|
||
// 使用forEach遍历Map(UTS支持)
|
||
keywordCount.forEach((value: number, key: string) => {
|
||
entryArray.push({
|
||
keyword: key,
|
||
count: value
|
||
})
|
||
})
|
||
|
||
// 按count降序排序
|
||
entryArray.sort((a: KeywordEntry, b: KeywordEntry): number => {
|
||
return b.count - a.count
|
||
})
|
||
|
||
// 取前limit个并提取关键词
|
||
const sortedKeywords: string[] = []
|
||
const maxCount = Math.min(entryArray.length, limit)
|
||
for (let i = 0; i < maxCount; i++) {
|
||
sortedKeywords.push(entryArray[i].keyword)
|
||
}
|
||
|
||
return sortedKeywords
|
||
} catch (e) {
|
||
console.error('获取热搜词失败:', e)
|
||
return [] as string[]
|
||
}
|
||
}
|
||
|
||
// 获取用户搜索历史
|
||
async getUserSearchHistory(limit: number = 10): Promise<string[]> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
return [] as string[]
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_search_history')
|
||
.select('keyword')
|
||
.order('created_at', { ascending: false })
|
||
.limit(limit * 2)
|
||
.execute()
|
||
|
||
if (response.error != null || response.data == null) {
|
||
return [] as string[]
|
||
}
|
||
|
||
const keywords: string[] = []
|
||
const rawList = response.data as any[]
|
||
const seen = new Set<string>()
|
||
|
||
for (let i = 0; i < rawList.length; i++) {
|
||
const item = rawList[i]
|
||
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
const rawUserId = itemObj.get('user_id')
|
||
const itemUserId = (typeof rawUserId == 'string') ? (rawUserId as string) : ''
|
||
|
||
// 只获取当前用户的搜索历史
|
||
if (itemUserId !== userId) continue
|
||
|
||
const keyword = safeGetString(itemObj, 'keyword').trim()
|
||
if (keyword.length > 0 && !seen.has(keyword)) {
|
||
keywords.push(keyword)
|
||
seen.add(keyword)
|
||
if (keywords.length >= limit) break
|
||
}
|
||
}
|
||
|
||
return keywords
|
||
} catch (e) {
|
||
console.error('获取用户搜索历史失败:', e)
|
||
return [] as string[]
|
||
}
|
||
}
|
||
|
||
// 获取用户浏览历史中的商品分类
|
||
async getUserBrowseCategories(limit: number = 5): Promise<string[]> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
return [] as string[]
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_browse_history')
|
||
.select('product_id')
|
||
.order('created_at', { ascending: false })
|
||
.limit(20)
|
||
.execute()
|
||
|
||
if (response.error != null || response.data == null) {
|
||
return [] as string[]
|
||
}
|
||
|
||
// 获取浏览过的商品ID
|
||
const productIds: string[] = []
|
||
const rawList = response.data as any[]
|
||
for (let i = 0; i < rawList.length; i++) {
|
||
const item = rawList[i]
|
||
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
|
||
// 手动过滤 user_id
|
||
const rawUserId = itemObj.get('user_id')
|
||
const itemUserId = (typeof rawUserId == 'string') ? (rawUserId as string) : ''
|
||
if (itemUserId !== userId) continue
|
||
|
||
const productId = safeGetString(itemObj, 'product_id')
|
||
if (productId.length > 0) {
|
||
productIds.push(productId)
|
||
}
|
||
}
|
||
|
||
if (productIds.length === 0) {
|
||
return [] as string[]
|
||
}
|
||
|
||
// 查询这些商品的分类
|
||
const prodResponse = await supa
|
||
.from('ml_products')
|
||
.select('category_id')
|
||
.limit(50)
|
||
.execute()
|
||
|
||
if (prodResponse.error != null || prodResponse.data == null) {
|
||
return [] as string[]
|
||
}
|
||
|
||
const categoryIds: string[] = []
|
||
const prodList = prodResponse.data as any[]
|
||
for (let i = 0; i < prodList.length; i++) {
|
||
const prodItem = prodList[i]
|
||
const prodObj = JSON.parse(JSON.stringify(prodItem)) as UTSJSONObject
|
||
const prodId = safeGetString(prodObj, 'id')
|
||
|
||
// 只统计浏览过的商品
|
||
let found = false
|
||
for (let j = 0; j < productIds.length; j++) {
|
||
if (productIds[j] == prodId) {
|
||
found = true
|
||
break
|
||
}
|
||
}
|
||
if (!found) continue
|
||
|
||
const catId = safeGetString(prodObj, 'category_id')
|
||
if (catId.length > 0 && categoryIds.indexOf(catId) < 0) {
|
||
categoryIds.push(catId)
|
||
if (categoryIds.length >= limit) break
|
||
}
|
||
}
|
||
|
||
return categoryIds
|
||
} catch (e) {
|
||
console.error('获取用户浏览分类失败:', e)
|
||
return [] as string[]
|
||
}
|
||
}
|
||
|
||
// 智能推荐:综合用户搜索历史、浏览历史、热销商品
|
||
async getSmartRecommendations(limit: number = 10): Promise<Product[]> {
|
||
try {
|
||
console.log('[getSmartRecommendations] 开始获取智能推荐...')
|
||
|
||
const products: Product[] = []
|
||
const addedIds = new Set<string>()
|
||
|
||
// 1. 根据用户搜索历史推荐商品(权重最高)
|
||
const searchHistory = await this.getUserSearchHistory(5)
|
||
console.log('[getSmartRecommendations] 用户搜索历史:', searchHistory)
|
||
|
||
if (searchHistory.length > 0) {
|
||
// 根据搜索关键词查找商品
|
||
const keywordProducts = await this.searchProductsByKeywords(searchHistory, limit)
|
||
for (let i = 0; i < keywordProducts.length; i++) {
|
||
const prod = keywordProducts[i]
|
||
if (!addedIds.has(prod.id)) {
|
||
products.push(prod)
|
||
addedIds.add(prod.id)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 2. 根据用户浏览历史推荐相似分类商品
|
||
if (products.length < limit) {
|
||
const browseCategories = await this.getUserBrowseCategories(3)
|
||
console.log('[getSmartRecommendations] 用户浏览分类:', browseCategories)
|
||
|
||
if (browseCategories.length > 0) {
|
||
const categoryProducts = await this.getProductsByCategories(browseCategories, limit - products.length)
|
||
for (let i = 0; i < categoryProducts.length; i++) {
|
||
const prod = categoryProducts[i]
|
||
if (!addedIds.has(prod.id)) {
|
||
products.push(prod)
|
||
addedIds.add(prod.id)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 3. 补充热销商品
|
||
if (products.length < limit) {
|
||
const hotProducts = await this.getHotProducts(limit - products.length + 5)
|
||
for (let i = 0; i < hotProducts.length; i++) {
|
||
const prod = hotProducts[i]
|
||
if (!addedIds.has(prod.id)) {
|
||
products.push(prod)
|
||
addedIds.add(prod.id)
|
||
if (products.length >= limit) break
|
||
}
|
||
}
|
||
}
|
||
|
||
// 4. 如果还不够,用普通商品补充
|
||
if (products.length < limit) {
|
||
const moreProducts = await this.getProductsByPrice(limit - products.length + 5, false)
|
||
for (let i = 0; i < moreProducts.length; i++) {
|
||
const prod = moreProducts[i]
|
||
if (!addedIds.has(prod.id)) {
|
||
products.push(prod)
|
||
addedIds.add(prod.id)
|
||
if (products.length >= limit) break
|
||
}
|
||
}
|
||
}
|
||
|
||
console.log('[getSmartRecommendations] 返回商品数量:', products.length)
|
||
return products.slice(0, limit)
|
||
} catch (e) {
|
||
console.error('获取智能推荐失败:', e)
|
||
return [] as Product[]
|
||
}
|
||
}
|
||
|
||
// 根据关键词列表搜索商品
|
||
async searchProductsByKeywords(keywords: string[], limit: number): Promise<Product[]> {
|
||
try {
|
||
const response = await supa
|
||
.from('ml_products_detail_view')
|
||
.select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')
|
||
.order('sale_count', { ascending: false })
|
||
.limit(limit * 2)
|
||
.execute()
|
||
|
||
if (response.error != null || response.data == null) {
|
||
return [] as Product[]
|
||
}
|
||
|
||
const products: Product[] = []
|
||
const rawList = response.data as any[]
|
||
|
||
for (let i = 0; i < rawList.length; i++) {
|
||
const item = rawList[i]
|
||
const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
|
||
// 手动过滤 status
|
||
const rawStatus = prodObj.get('status')
|
||
let statusNum: number = 0
|
||
if (typeof rawStatus == 'number') {
|
||
statusNum = rawStatus as number
|
||
}
|
||
if (statusNum !== 1) continue
|
||
|
||
// 检查是否匹配任何关键词
|
||
const name = safeGetString(prodObj, 'name').toLowerCase()
|
||
const desc = safeGetString(prodObj, 'description').toLowerCase()
|
||
|
||
let matched = false
|
||
for (let j = 0; j < keywords.length; j++) {
|
||
const keyword = keywords[j].toLowerCase()
|
||
if (name.indexOf(keyword) >= 0 || desc.indexOf(keyword) >= 0) {
|
||
matched = true
|
||
break
|
||
}
|
||
}
|
||
|
||
if (!matched) continue
|
||
|
||
products.push(parseProductFromRaw(item))
|
||
if (products.length >= limit) break
|
||
}
|
||
|
||
return products
|
||
} catch (e) {
|
||
console.error('根据关键词搜索商品失败:', e)
|
||
return [] as Product[]
|
||
}
|
||
}
|
||
|
||
// 根据分类列表获取商品
|
||
async getProductsByCategories(categoryIds: string[], limit: number): Promise<Product[]> {
|
||
try {
|
||
const response = await supa
|
||
.from('ml_products_detail_view')
|
||
.select('id, name, description, base_price, market_price, main_image_url, image_urls, category_id, brand_id, merchant_id, total_stock, sale_count, status, is_featured, is_new, is_hot')
|
||
.order('sale_count', { ascending: false })
|
||
.limit(limit * 2)
|
||
.execute()
|
||
|
||
if (response.error != null || response.data == null) {
|
||
return [] as Product[]
|
||
}
|
||
|
||
const products: Product[] = []
|
||
const rawList = response.data as any[]
|
||
|
||
for (let i = 0; i < rawList.length; i++) {
|
||
const item = rawList[i]
|
||
const prodObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
|
||
// 手动过滤 status
|
||
const rawStatus = prodObj.get('status')
|
||
let statusNum: number = 0
|
||
if (typeof rawStatus == 'number') {
|
||
statusNum = rawStatus as number
|
||
}
|
||
if (statusNum !== 1) continue
|
||
|
||
// 手动过滤 category_id
|
||
const rawCatId = prodObj.get('category_id')
|
||
const itemCatId = (typeof rawCatId == 'string') ? (rawCatId as string) : ''
|
||
|
||
let matched = false
|
||
for (let j = 0; j < categoryIds.length; j++) {
|
||
if (itemCatId == categoryIds[j]) {
|
||
matched = true
|
||
break
|
||
}
|
||
}
|
||
|
||
if (!matched) continue
|
||
|
||
products.push(parseProductFromRaw(item))
|
||
if (products.length >= limit) break
|
||
}
|
||
|
||
return products
|
||
} catch (e) {
|
||
console.error('根据分类获取商品失败:', e)
|
||
return [] as Product[]
|
||
}
|
||
}
|
||
|
||
// 记录用户搜索行为
|
||
async recordSearch(keyword: string, resultCount: number): Promise<void> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
const searchRecord = new UTSJSONObject()
|
||
searchRecord.set('keyword', keyword)
|
||
searchRecord.set('result_count', resultCount)
|
||
if (userId != null) {
|
||
searchRecord.set('user_id', userId)
|
||
}
|
||
|
||
await supa
|
||
.from('ml_search_history')
|
||
.insert(searchRecord)
|
||
.execute()
|
||
} catch (e) {
|
||
console.error('记录搜索失败:', e)
|
||
}
|
||
}
|
||
|
||
// 记录用户浏览行为
|
||
async recordBrowse(productId: string, duration: number = 0): Promise<void> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) return
|
||
|
||
const browseRecord = new UTSJSONObject()
|
||
browseRecord.set('user_id', userId)
|
||
browseRecord.set('product_id', productId)
|
||
browseRecord.set('browse_duration', duration)
|
||
browseRecord.set('created_at', new Date().toISOString())
|
||
|
||
// UTS Android不支持upsert,使用insert
|
||
await supa
|
||
.from('ml_browse_history')
|
||
.insert(browseRecord)
|
||
.execute()
|
||
} catch (e) {
|
||
console.error('记录浏览失败:', e)
|
||
}
|
||
}
|
||
|
||
// ==================== 签到相关API ====================
|
||
|
||
// 用户签到
|
||
async signin(): Promise<UTSJSONObject> {
|
||
const result = new UTSJSONObject()
|
||
result.set('success', false)
|
||
result.set('points', 0)
|
||
result.set('continuous_days', 0)
|
||
result.set('bonus_points', 0)
|
||
result.set('total_points', 0)
|
||
result.set('message', '')
|
||
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
result.set('message', '请先登录')
|
||
return result
|
||
}
|
||
|
||
const today = new Date()
|
||
const todayStr = today.toISOString().split('T')[0]
|
||
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000)
|
||
const yesterdayStr = yesterday.toISOString().split('T')[0]
|
||
|
||
// 检查今天是否已签到
|
||
const checkRes = await supa
|
||
.from('ml_signin_records')
|
||
.select('*')
|
||
.eq('user_id', userId!)
|
||
.eq('signin_date', todayStr)
|
||
.execute()
|
||
|
||
if (checkRes.error != null) {
|
||
result.set('message', '查询签到状态失败')
|
||
return result
|
||
}
|
||
|
||
const checkData = checkRes.data as any[]
|
||
if (checkData != null && checkData.length > 0) {
|
||
result.set('message', '今天已签到')
|
||
return result
|
||
}
|
||
|
||
// 查询昨天是否签到,计算连续天数
|
||
const yesterdayRes = await supa
|
||
.from('ml_signin_records')
|
||
.select('continuous_days')
|
||
.eq('user_id', userId!)
|
||
.eq('signin_date', yesterdayStr)
|
||
.execute()
|
||
|
||
let continuousDays = 1
|
||
if (yesterdayRes.error == null && yesterdayRes.data != null) {
|
||
const yData = yesterdayRes.data as any[]
|
||
if (yData.length > 0) {
|
||
const yItem = yData[0]
|
||
let yDays = 0
|
||
if (yItem instanceof UTSJSONObject) {
|
||
yDays = yItem.getNumber('continuous_days') ?? 0
|
||
} else {
|
||
const yObj = JSON.parse(JSON.stringify(yItem)) as UTSJSONObject
|
||
yDays = yObj.getNumber('continuous_days') ?? 0
|
||
}
|
||
continuousDays = yDays + 1
|
||
}
|
||
}
|
||
|
||
// 计算积分
|
||
let pointsEarned = 5 // 每日签到基础积分
|
||
let bonusPoints = 0
|
||
|
||
if (continuousDays >= 30) {
|
||
bonusPoints = 100
|
||
} else if (continuousDays >= 7) {
|
||
bonusPoints = 20
|
||
}
|
||
|
||
const totalPointsEarned = pointsEarned + bonusPoints
|
||
|
||
// 插入签到记录
|
||
const signinRecord = new UTSJSONObject()
|
||
signinRecord.set('user_id', userId!)
|
||
signinRecord.set('signin_date', todayStr)
|
||
signinRecord.set('points_earned', pointsEarned)
|
||
signinRecord.set('bonus_points', bonusPoints)
|
||
signinRecord.set('continuous_days', continuousDays)
|
||
|
||
const insertRes = await supa
|
||
.from('ml_signin_records')
|
||
.insert(signinRecord)
|
||
.execute()
|
||
|
||
if (insertRes.error != null) {
|
||
result.set('message', '签到失败')
|
||
return result
|
||
}
|
||
|
||
// 更新用户积分
|
||
await this.addPoints(userId!, totalPointsEarned, 'signin', '每日签到')
|
||
|
||
// 获取最新积分
|
||
const newPoints = await this.getUserPoints()
|
||
|
||
result.set('success', true)
|
||
result.set('points', pointsEarned)
|
||
result.set('continuous_days', continuousDays)
|
||
result.set('bonus_points', bonusPoints)
|
||
result.set('total_points', newPoints)
|
||
result.set('message', '签到成功')
|
||
|
||
return result
|
||
} catch (e) {
|
||
console.error('签到异常:', e)
|
||
result.set('message', '签到异常')
|
||
return result
|
||
}
|
||
}
|
||
|
||
// 获取签到记录(当月)
|
||
async getSigninRecords(year: number, month: number): Promise<any[]> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
const startDate = `${year}-${month.toString().padStart(2, '0')}-01`
|
||
const endDate = month === 12
|
||
? `${year + 1}-01-01`
|
||
: `${year}-${(month + 1).toString().padStart(2, '0')}-01`
|
||
|
||
const response = await supa
|
||
.from('ml_signin_records')
|
||
.select('*')
|
||
.eq('user_id', userId!)
|
||
.gte('signin_date', startDate)
|
||
.lt('signin_date', endDate)
|
||
.order('signin_date', { ascending: true })
|
||
.execute()
|
||
|
||
if (response.error != null || response.data == null) {
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
return response.data as any[]
|
||
} catch (e) {
|
||
console.error('获取签到记录失败:', e)
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
}
|
||
|
||
// 获取今日签到状态
|
||
async getTodaySigninStatus(): Promise<UTSJSONObject> {
|
||
const result = new UTSJSONObject()
|
||
result.set('signed', false)
|
||
result.set('continuous_days', 0)
|
||
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) return result
|
||
|
||
const today = new Date().toISOString().split('T')[0]
|
||
|
||
// 检查今天是否签到
|
||
const todayRes = await supa
|
||
.from('ml_signin_records')
|
||
.select('*')
|
||
.eq('user_id', userId!)
|
||
.eq('signin_date', today)
|
||
.execute()
|
||
|
||
if (todayRes.error == null && todayRes.data != null) {
|
||
const tData = todayRes.data as any[]
|
||
if (tData.length > 0) {
|
||
const tItem = tData[0]
|
||
let cDays = 0
|
||
if (tItem instanceof UTSJSONObject) {
|
||
cDays = tItem.getNumber('continuous_days') ?? 0
|
||
} else {
|
||
const tObj = JSON.parse(JSON.stringify(tItem)) as UTSJSONObject
|
||
cDays = tObj.getNumber('continuous_days') ?? 0
|
||
}
|
||
result.set('signed', true)
|
||
result.set('continuous_days', cDays)
|
||
return result
|
||
}
|
||
}
|
||
|
||
// 今天未签到,获取最近的连续签到天数
|
||
const lastRes = await supa
|
||
.from('ml_signin_records')
|
||
.select('continuous_days, signin_date')
|
||
.eq('user_id', userId!)
|
||
.order('signin_date', { ascending: false })
|
||
.limit(1)
|
||
.execute()
|
||
|
||
if (lastRes.error == null && lastRes.data != null) {
|
||
const lData = lastRes.data as any[]
|
||
if (lData.length > 0) {
|
||
const lItem = lData[0]
|
||
let lastDate = ''
|
||
let lastDays = 0
|
||
if (lItem instanceof UTSJSONObject) {
|
||
lastDate = lItem.getString('signin_date') ?? ''
|
||
lastDays = lItem.getNumber('continuous_days') ?? 0
|
||
} else {
|
||
const lObj = JSON.parse(JSON.stringify(lItem)) as UTSJSONObject
|
||
lastDate = lObj.getString('signin_date') ?? ''
|
||
lastDays = lObj.getNumber('continuous_days') ?? 0
|
||
}
|
||
|
||
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().split('T')[0]
|
||
if (lastDate === yesterday) {
|
||
result.set('continuous_days', lastDays)
|
||
}
|
||
}
|
||
}
|
||
|
||
return result
|
||
} catch (e) {
|
||
console.error('获取签到状态失败:', e)
|
||
return result
|
||
}
|
||
}
|
||
|
||
// ==================== 积分兑换相关API ====================
|
||
|
||
// 获取积分兑换商品列表
|
||
async getPointProducts(): Promise<any[]> {
|
||
try {
|
||
const response = await supa
|
||
.from('ml_point_products')
|
||
.select('*')
|
||
.eq('status', 1)
|
||
.gt('stock', 0)
|
||
.order('sort_order', { ascending: true })
|
||
.execute()
|
||
|
||
if (response.error != null || response.data == null) {
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
return response.data as any[]
|
||
} catch (e) {
|
||
console.error('获取积分商品失败:', e)
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
}
|
||
|
||
// 积分兑换
|
||
async exchangeProduct(productId: string, quantity: number, addressSnapshot: UTSJSONObject | null): Promise<UTSJSONObject> {
|
||
const result = new UTSJSONObject()
|
||
result.set('success', false)
|
||
result.set('message', '')
|
||
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
result.set('message', '请先登录')
|
||
return result
|
||
}
|
||
|
||
// 获取商品信息
|
||
const productRes = await supa
|
||
.from('ml_point_products')
|
||
.select('*')
|
||
.eq('id', productId)
|
||
.single()
|
||
.execute()
|
||
|
||
if (productRes.error != null || productRes.data == null) {
|
||
result.set('message', '商品不存在')
|
||
return result
|
||
}
|
||
|
||
const productRaw = productRes.data
|
||
let pointsRequired = 0
|
||
let stock = 0
|
||
let productType = ''
|
||
|
||
// 检查是否是数组,如果是则取第一个元素
|
||
let productObj: UTSJSONObject | null = null
|
||
if (Array.isArray(productRaw)) {
|
||
const arr = productRaw as any[]
|
||
if (arr.length > 0) {
|
||
const firstItem = arr[0]
|
||
if (firstItem instanceof UTSJSONObject) {
|
||
productObj = firstItem
|
||
} else {
|
||
productObj = JSON.parse(JSON.stringify(firstItem)) as UTSJSONObject
|
||
}
|
||
}
|
||
} else {
|
||
if (productRaw instanceof UTSJSONObject) {
|
||
productObj = productRaw
|
||
} else {
|
||
productObj = JSON.parse(JSON.stringify(productRaw)) as UTSJSONObject
|
||
}
|
||
}
|
||
|
||
// 使用 UTSJSONObject 方法访问属性
|
||
if (productObj != null) {
|
||
pointsRequired = productObj.getNumber('points_required') ?? 0
|
||
stock = productObj.getNumber('stock') ?? 0
|
||
productType = productObj.getString('product_type') ?? ''
|
||
}
|
||
|
||
const totalPoints = pointsRequired * quantity
|
||
|
||
// 检查库存
|
||
if (stock < quantity) {
|
||
result.set('message', '库存不足')
|
||
return result
|
||
}
|
||
|
||
// 检查积分
|
||
const userPoints = await this.getUserPoints()
|
||
if (userPoints < totalPoints) {
|
||
result.set('message', '积分不足')
|
||
return result
|
||
}
|
||
|
||
// 创建兑换记录
|
||
const exchangeRecord = new UTSJSONObject()
|
||
exchangeRecord.set('user_id', userId!)
|
||
exchangeRecord.set('product_id', productId)
|
||
exchangeRecord.set('quantity', quantity)
|
||
exchangeRecord.set('points_used', totalPoints)
|
||
exchangeRecord.set('status', 0)
|
||
if (addressSnapshot != null && productType === 'physical') {
|
||
exchangeRecord.set('address_snapshot', JSON.stringify(addressSnapshot))
|
||
}
|
||
|
||
const insertRes = await supa
|
||
.from('ml_point_exchanges')
|
||
.insert(exchangeRecord)
|
||
.execute()
|
||
|
||
if (insertRes.error != null) {
|
||
console.error('[exchangeProduct] 创建兑换记录失败:', insertRes.error)
|
||
result.set('message', '兑换失败')
|
||
return result
|
||
}
|
||
|
||
console.log('[exchangeProduct] 兑换记录创建成功')
|
||
|
||
// 扣减库存
|
||
console.log('[exchangeProduct] 准备扣减库存')
|
||
console.log('[exchangeProduct] productId 类型:', typeof productId)
|
||
console.log('[exchangeProduct] productId 值:', productId)
|
||
console.log('[exchangeProduct] 当前库存:', stock, ', 扣减数量:', quantity)
|
||
|
||
// 使用 UTSJSONObject 替代 Record<string, any>
|
||
const stockUpdateData = new UTSJSONObject()
|
||
stockUpdateData.set('stock', stock - quantity)
|
||
|
||
console.log('[exchangeProduct] stockUpdateData:', stockUpdateData)
|
||
console.log('[exchangeProduct] stockUpdateData 类型:', typeof stockUpdateData)
|
||
|
||
// 先查询确认商品存在
|
||
const checkProduct = await supa
|
||
.from('ml_point_products')
|
||
.select('id, stock')
|
||
.eq('id', productId)
|
||
.execute()
|
||
console.log('[exchangeProduct] 查询商品结果:', checkProduct.data, 'error:', checkProduct.error)
|
||
|
||
const stockUpdateRes = await supa
|
||
.from('ml_point_products')
|
||
.update(stockUpdateData)
|
||
.eq('id', productId)
|
||
.execute()
|
||
|
||
console.log('[exchangeProduct] 库存更新结果 error:', stockUpdateRes.error)
|
||
console.log('[exchangeProduct] 库存更新结果 data:', stockUpdateRes.data)
|
||
|
||
if (stockUpdateRes.error != null) {
|
||
console.error('[exchangeProduct] 扣减库存失败:', stockUpdateRes.error)
|
||
}
|
||
|
||
// 扣减积分
|
||
console.log('[exchangeProduct] 准备扣减积分, userId:', userId, ', 积分:', totalPoints)
|
||
const deductResult = await this.deductPoints(userId!, totalPoints, 'redeem', '积分兑换商品')
|
||
console.log('[exchangeProduct] 积分扣减结果:', deductResult)
|
||
|
||
if (!deductResult) {
|
||
console.error('[exchangeProduct] 扣减积分失败')
|
||
}
|
||
|
||
console.log('[exchangeProduct] 兑换流程完成')
|
||
result.set('success', true)
|
||
result.set('message', '兑换成功')
|
||
return result
|
||
} catch (e) {
|
||
console.error('积分兑换异常:', e)
|
||
result.set('message', '兑换异常')
|
||
return result
|
||
}
|
||
}
|
||
|
||
// 获取兑换记录
|
||
async getExchangeRecords(): Promise<any[]> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_point_exchanges')
|
||
.select('*, product:ml_point_products(name, image_url, product_type)')
|
||
.eq('user_id', userId!)
|
||
.order('created_at', { ascending: false })
|
||
.execute()
|
||
|
||
if (response.error != null || response.data == null) {
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
return response.data as any[]
|
||
} catch (e) {
|
||
console.error('获取兑换记录失败:', e)
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
}
|
||
|
||
// ==================== 评价相关API ====================
|
||
|
||
// 获取商品评价列表
|
||
async getProductReviews(productId: string, page: number = 1, limit: number = 10, rating: number = 0, hasImage: boolean = false): Promise<UTSJSONObject> {
|
||
const result = new UTSJSONObject()
|
||
result.set('total', 0)
|
||
result.set('page', page)
|
||
result.set('limit', limit)
|
||
result.set('data', [] as any[])
|
||
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
|
||
let query = supa
|
||
.from('ml_product_reviews')
|
||
.select('*, user:auth.users!ml_product_reviews_user_id_fkey(raw_user_meta_data)', { count: 'exact' })
|
||
.eq('product_id', productId)
|
||
|
||
if (rating > 0) {
|
||
query = query.eq('rating', rating)
|
||
}
|
||
|
||
if (hasImage) {
|
||
query = query.neq('images', '[]')
|
||
}
|
||
|
||
const offset = (page - 1) * limit
|
||
const response = await query
|
||
.order('created_at', { ascending: false })
|
||
.range(offset, offset + limit - 1)
|
||
.execute()
|
||
|
||
if (response.error != null) {
|
||
console.error('获取评价列表失败:', response.error)
|
||
return result
|
||
}
|
||
|
||
const total = response.total ?? 0
|
||
const reviews = response.data as any[]
|
||
|
||
// 处理评价数据
|
||
const processedReviews: any[] = []
|
||
for (let i = 0; i < reviews.length; i++) {
|
||
const review = reviews[i]
|
||
const processed = JSON.parse(JSON.stringify(review)) as UTSJSONObject
|
||
|
||
// 处理用户信息
|
||
const userRaw = processed.get('user')
|
||
let userName = '匿名用户'
|
||
let userAvatar = ''
|
||
|
||
if (userRaw != null) {
|
||
let userData: UTSJSONObject
|
||
if (userRaw instanceof UTSJSONObject) {
|
||
userData = userRaw as UTSJSONObject
|
||
} else {
|
||
userData = JSON.parse(JSON.stringify(userRaw)) as UTSJSONObject
|
||
}
|
||
const metaData = userData.get('raw_user_meta_data')
|
||
if (metaData != null) {
|
||
let metaObj: UTSJSONObject
|
||
if (metaData instanceof UTSJSONObject) {
|
||
metaObj = metaData as UTSJSONObject
|
||
} else {
|
||
metaObj = JSON.parse(JSON.stringify(metaData)) as UTSJSONObject
|
||
}
|
||
userName = metaObj.getString('nickname') ?? metaObj.getString('name') ?? '匿名用户'
|
||
userAvatar = metaObj.getString('avatar_url') ?? ''
|
||
}
|
||
}
|
||
|
||
// 检查是否匿名
|
||
const isAnonymous = processed.getBoolean('is_anonymous') ?? false
|
||
if (isAnonymous) {
|
||
userName = '匿名用户'
|
||
userAvatar = ''
|
||
}
|
||
|
||
processed.set('user_name', userName)
|
||
processed.set('user_avatar', userAvatar)
|
||
|
||
// 检查当前用户是否点赞
|
||
let isLiked = false
|
||
if (userId != null) {
|
||
const likeRes = await supa
|
||
.from('ml_review_likes')
|
||
.select('id')
|
||
.eq('review_id', processed.getString('id') ?? '')
|
||
.eq('user_id', userId!)
|
||
.limit(1)
|
||
.execute()
|
||
if (likeRes.error == null && likeRes.data != null) {
|
||
const likeData = likeRes.data as any[]
|
||
isLiked = likeData.length > 0
|
||
}
|
||
}
|
||
processed.set('is_liked', isLiked)
|
||
|
||
processedReviews.push(processed)
|
||
}
|
||
|
||
result.set('total', total)
|
||
result.set('data', processedReviews)
|
||
return result
|
||
} catch (e) {
|
||
console.error('获取评价列表异常:', e)
|
||
return result
|
||
}
|
||
}
|
||
|
||
// 获取商品评价统计
|
||
async getReviewStats(productId: string): Promise<UTSJSONObject> {
|
||
const result = new UTSJSONObject()
|
||
result.set('total_count', 0)
|
||
result.set('avg_rating', 0)
|
||
result.set('good_rate', 0)
|
||
result.set('rating_distribution', new UTSJSONObject())
|
||
result.set('tags', [] as any[])
|
||
|
||
try {
|
||
const response = await supa
|
||
.from('ml_product_reviews')
|
||
.select('rating')
|
||
.eq('product_id', productId)
|
||
.execute()
|
||
|
||
if (response.error != null || response.data == null) {
|
||
return result
|
||
}
|
||
|
||
const reviews = response.data as any[]
|
||
const totalCount = reviews.length
|
||
|
||
if (totalCount === 0) return result
|
||
|
||
let totalRating = 0
|
||
let goodCount = 0
|
||
const distribution: Map<number, number> = new Map()
|
||
distribution.set(1, 0)
|
||
distribution.set(2, 0)
|
||
distribution.set(3, 0)
|
||
distribution.set(4, 0)
|
||
distribution.set(5, 0)
|
||
|
||
for (let i = 0; i < reviews.length; i++) {
|
||
const review = reviews[i]
|
||
let rating = 0
|
||
if (review instanceof UTSJSONObject) {
|
||
rating = review.getNumber('rating') ?? 0
|
||
} else {
|
||
const rObj = JSON.parse(JSON.stringify(review)) as UTSJSONObject
|
||
rating = rObj.getNumber('rating') ?? 0
|
||
}
|
||
|
||
totalRating += rating
|
||
if (rating >= 4) goodCount++
|
||
|
||
const currentCount = distribution.get(rating) ?? 0
|
||
distribution.set(rating, currentCount + 1)
|
||
}
|
||
|
||
const avgRating = Math.round((totalRating / totalCount) * 10) / 10
|
||
const goodRate = Math.round((goodCount / totalCount) * 100)
|
||
|
||
const distObj = new UTSJSONObject()
|
||
distribution.forEach((value: number, key: number) => {
|
||
distObj.set(key.toString(), value)
|
||
})
|
||
|
||
result.set('total_count', totalCount)
|
||
result.set('avg_rating', avgRating)
|
||
result.set('good_rate', goodRate)
|
||
result.set('rating_distribution', distObj)
|
||
|
||
return result
|
||
} catch (e) {
|
||
console.error('获取评价统计异常:', e)
|
||
return result
|
||
}
|
||
}
|
||
|
||
// 评价点赞
|
||
async toggleReviewLike(reviewId: string): Promise<UTSJSONObject> {
|
||
const result = new UTSJSONObject()
|
||
result.set('success', false)
|
||
result.set('is_liked', false)
|
||
result.set('like_count', 0)
|
||
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
return result
|
||
}
|
||
|
||
// 检查是否已点赞
|
||
const checkRes = await supa
|
||
.from('ml_review_likes')
|
||
.select('id')
|
||
.eq('review_id', reviewId)
|
||
.eq('user_id', userId!)
|
||
.limit(1)
|
||
.execute()
|
||
|
||
let isLiked = false
|
||
if (checkRes.error == null && checkRes.data != null) {
|
||
const checkData = checkRes.data as any[]
|
||
isLiked = checkData.length > 0
|
||
}
|
||
|
||
if (isLiked) {
|
||
// 取消点赞
|
||
await supa
|
||
.from('ml_review_likes')
|
||
.eq('review_id', reviewId)
|
||
.eq('user_id', userId!)
|
||
.delete()
|
||
.execute()
|
||
|
||
// 更新点赞数 - 直接查询并更新
|
||
const currentCountRes = await supa
|
||
.from('ml_product_reviews')
|
||
.select('like_count')
|
||
.eq('id', reviewId)
|
||
.single()
|
||
.execute()
|
||
|
||
if (currentCountRes.error == null && currentCountRes.data != null) {
|
||
let currentCount = 0
|
||
if (currentCountRes.data instanceof UTSJSONObject) {
|
||
currentCount = currentCountRes.data.getNumber('like_count') ?? 0
|
||
} else {
|
||
const countObj = JSON.parse(JSON.stringify(currentCountRes.data)) as UTSJSONObject
|
||
currentCount = countObj.getNumber('like_count') ?? 0
|
||
}
|
||
|
||
const updateData = new UTSJSONObject()
|
||
updateData.set('like_count', Math.max(0, currentCount - 1))
|
||
await supa
|
||
.from('ml_product_reviews')
|
||
.update(updateData)
|
||
.eq('id', reviewId)
|
||
.execute()
|
||
}
|
||
|
||
result.set('is_liked', false)
|
||
} else {
|
||
// 添加点赞
|
||
const likeRecord = new UTSJSONObject()
|
||
likeRecord.set('review_id', reviewId)
|
||
likeRecord.set('user_id', userId!)
|
||
|
||
await supa
|
||
.from('ml_review_likes')
|
||
.insert(likeRecord)
|
||
.execute()
|
||
|
||
// 更新点赞数 - 直接查询并更新
|
||
const currentCountRes = await supa
|
||
.from('ml_product_reviews')
|
||
.select('like_count')
|
||
.eq('id', reviewId)
|
||
.single()
|
||
.execute()
|
||
|
||
if (currentCountRes.error == null && currentCountRes.data != null) {
|
||
let currentCount = 0
|
||
if (currentCountRes.data instanceof UTSJSONObject) {
|
||
currentCount = currentCountRes.data.getNumber('like_count') ?? 0
|
||
} else {
|
||
const countObj = JSON.parse(JSON.stringify(currentCountRes.data)) as UTSJSONObject
|
||
currentCount = countObj.getNumber('like_count') ?? 0
|
||
}
|
||
|
||
const updateData = new UTSJSONObject()
|
||
updateData.set('like_count', currentCount + 1)
|
||
await supa
|
||
.from('ml_product_reviews')
|
||
.update(updateData)
|
||
.eq('id', reviewId)
|
||
.execute()
|
||
}
|
||
|
||
result.set('is_liked', true)
|
||
}
|
||
|
||
// 获取最新点赞数
|
||
const reviewRes = await supa
|
||
.from('ml_product_reviews')
|
||
.select('like_count')
|
||
.eq('id', reviewId)
|
||
.single()
|
||
.execute()
|
||
|
||
if (reviewRes.error == null && reviewRes.data != null) {
|
||
let likeCount = 0
|
||
if (reviewRes.data instanceof UTSJSONObject) {
|
||
likeCount = reviewRes.data.getNumber('like_count') ?? 0
|
||
} else {
|
||
const rObj = JSON.parse(JSON.stringify(reviewRes.data)) as UTSJSONObject
|
||
likeCount = rObj.getNumber('like_count') ?? 0
|
||
}
|
||
result.set('like_count', likeCount)
|
||
}
|
||
|
||
result.set('success', true)
|
||
return result
|
||
} catch (e) {
|
||
console.error('评价点赞异常:', e)
|
||
return result
|
||
}
|
||
}
|
||
|
||
// 获取我的评价列表
|
||
async getMyReviews(): Promise<any[]> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
const response = await supa
|
||
.from('ml_product_reviews')
|
||
.select(`
|
||
*,
|
||
product:ml_products!ml_product_reviews_product_id_fkey(name, main_image_url)
|
||
`)
|
||
.eq('user_id', userId!)
|
||
.order('created_at', { ascending: false })
|
||
.execute()
|
||
|
||
if (response.error != null || response.data == null) {
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
const reviews = response.data as any[]
|
||
const result: any[] = []
|
||
|
||
for (let i = 0; i < reviews.length; i++) {
|
||
const review = reviews[i]
|
||
const processed = JSON.parse(JSON.stringify(review)) as UTSJSONObject
|
||
|
||
// 处理商品信息
|
||
const productRaw = processed.get('product')
|
||
let productName = ''
|
||
let productImage = ''
|
||
if (productRaw != null) {
|
||
let productObj: UTSJSONObject
|
||
if (productRaw instanceof UTSJSONObject) {
|
||
productObj = productRaw as UTSJSONObject
|
||
} else {
|
||
productObj = JSON.parse(JSON.stringify(productRaw)) as UTSJSONObject
|
||
}
|
||
productName = productObj.getString('name') ?? ''
|
||
productImage = productObj.getString('main_image_url') ?? ''
|
||
}
|
||
processed.set('product_name', productName)
|
||
processed.set('product_image', productImage)
|
||
|
||
// 计算是否可追加评价(7天内)
|
||
const createdAt = processed.getString('created_at') ?? ''
|
||
const createdTime = new Date(createdAt).getTime()
|
||
const now = Date.now()
|
||
const sevenDays = 7 * 24 * 60 * 60 * 1000
|
||
const canAppend = (now - createdTime) < sevenDays && (processed.getString('append_content') ?? '') === ''
|
||
processed.set('can_append', canAppend)
|
||
|
||
// 计算是否可编辑(24小时内)
|
||
const oneDay = 24 * 60 * 60 * 1000
|
||
const canEdit = (now - createdTime) < oneDay
|
||
processed.set('can_edit', canEdit)
|
||
|
||
result.push(processed)
|
||
}
|
||
|
||
return result
|
||
} catch (e) {
|
||
console.error('获取我的评价失败:', e)
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
}
|
||
|
||
// 追加评价
|
||
async appendReview(reviewId: string, content: string, images: string[]): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) return false
|
||
|
||
const updateData = new UTSJSONObject()
|
||
updateData.set('append_content', content)
|
||
updateData.set('append_images', JSON.stringify(images))
|
||
updateData.set('append_at', new Date().toISOString())
|
||
|
||
const response = await supa
|
||
.from('ml_product_reviews')
|
||
.update(updateData)
|
||
.eq('id', reviewId)
|
||
.eq('user_id', userId!)
|
||
.execute()
|
||
|
||
return response.error == null
|
||
} catch (e) {
|
||
console.error('追加评价失败:', e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 删除评价
|
||
async deleteReview(reviewId: string): Promise<boolean> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) return false
|
||
|
||
const response = await supa
|
||
.from('ml_product_reviews')
|
||
.delete()
|
||
.eq('id', reviewId)
|
||
.eq('user_id', userId!)
|
||
.execute()
|
||
|
||
return response.error == null
|
||
} catch (e) {
|
||
console.error('删除评价失败:', e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// ==================== 积分辅助方法 ====================
|
||
|
||
// 增加积分
|
||
private async addPoints(userId: string, points: number, type: string, description: string): Promise<boolean> {
|
||
try {
|
||
// 获取当前积分
|
||
const currentPoints = await this.getUserPoints()
|
||
const newPoints = currentPoints + points
|
||
const totalEarned = await this.getTotalEarned()
|
||
|
||
// 检查用户积分记录是否存在
|
||
const checkRes = await supa
|
||
.from('ml_user_points')
|
||
.select('user_id')
|
||
.eq('user_id', userId)
|
||
.limit(1)
|
||
.execute()
|
||
|
||
const exists = checkRes.error == null && checkRes.data != null && (checkRes.data as any[]).length > 0
|
||
|
||
if (exists) {
|
||
// 更新现有记录
|
||
const updateData = new UTSJSONObject()
|
||
updateData.set('points', newPoints)
|
||
updateData.set('total_earned', totalEarned + points)
|
||
updateData.set('updated_at', new Date().toISOString())
|
||
|
||
await supa
|
||
.from('ml_user_points')
|
||
.update(updateData)
|
||
.eq('user_id', userId)
|
||
.execute()
|
||
} else {
|
||
// 插入新记录
|
||
const insertData = new UTSJSONObject()
|
||
insertData.set('user_id', userId)
|
||
insertData.set('points', newPoints)
|
||
insertData.set('total_earned', points)
|
||
insertData.set('updated_at', new Date().toISOString())
|
||
|
||
await supa
|
||
.from('ml_user_points')
|
||
.insert(insertData)
|
||
.execute()
|
||
}
|
||
|
||
// 记录积分变动
|
||
const record = new UTSJSONObject()
|
||
record.set('user_id', userId)
|
||
record.set('points', points)
|
||
record.set('type', type)
|
||
record.set('description', description)
|
||
|
||
await supa
|
||
.from('ml_point_records')
|
||
.insert(record)
|
||
.execute()
|
||
|
||
return true
|
||
} catch (e) {
|
||
console.error('增加积分失败:', e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 扣减积分
|
||
private async deductPoints(userId: string, points: number, type: string, description: string): Promise<boolean> {
|
||
try {
|
||
const currentPoints = await this.getUserPoints()
|
||
const newPoints = currentPoints - points
|
||
|
||
if (newPoints < 0) return false
|
||
|
||
const updateData = new UTSJSONObject()
|
||
updateData.set('points', newPoints)
|
||
updateData.set('updated_at', new Date().toISOString())
|
||
|
||
await supa
|
||
.from('ml_user_points')
|
||
.update(updateData)
|
||
.eq('user_id', userId)
|
||
.execute()
|
||
|
||
const record = new UTSJSONObject()
|
||
record.set('user_id', userId)
|
||
record.set('points', -points)
|
||
record.set('type', type)
|
||
record.set('description', description)
|
||
|
||
await supa
|
||
.from('ml_point_records')
|
||
.insert(record)
|
||
.execute()
|
||
|
||
return true
|
||
} catch (e) {
|
||
console.error('扣减积分失败:', e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 获取历史累计积分
|
||
private async getTotalEarned(): Promise<number> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) return 0
|
||
|
||
const res = await supa
|
||
.from('ml_user_points')
|
||
.select('total_earned')
|
||
.eq('user_id', userId!)
|
||
.single()
|
||
.execute()
|
||
|
||
if (res.error == null && res.data != null) {
|
||
if (res.data instanceof UTSJSONObject) {
|
||
return res.data.getNumber('total_earned') ?? 0
|
||
} else {
|
||
const obj = JSON.parse(JSON.stringify(res.data)) as UTSJSONObject
|
||
return obj.getNumber('total_earned') ?? 0
|
||
}
|
||
}
|
||
return 0
|
||
} catch (e) {
|
||
return 0
|
||
}
|
||
}
|
||
|
||
// ==================== 积分过期相关API ====================
|
||
|
||
// 获取即将过期积分
|
||
async getExpiringPoints(): Promise<UTSJSONObject> {
|
||
const result = new UTSJSONObject()
|
||
result.set('expiring_points', 0)
|
||
result.set('expiring_date', null)
|
||
result.set('details', [] as any[])
|
||
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) return result
|
||
|
||
// 查询30天内即将过期的积分记录
|
||
const now = new Date()
|
||
const thirtyDaysLater = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000)
|
||
const nowStr = now.toISOString()
|
||
const laterStr = thirtyDaysLater.toISOString()
|
||
|
||
const res = await supa
|
||
.from('ml_point_records')
|
||
.select('points, description, expires_at, created_at')
|
||
.eq('user_id', userId!)
|
||
.gt('points', 0)
|
||
.eq('is_expired', false)
|
||
.not('expires_at', 'is', null)
|
||
.gte('expires_at', nowStr)
|
||
.lte('expires_at', laterStr)
|
||
.order('expires_at', { ascending: true })
|
||
.execute()
|
||
|
||
if (res.error != null) {
|
||
console.error('获取即将过期积分失败:', res.error)
|
||
return result
|
||
}
|
||
|
||
if (res.data != null && Array.isArray(res.data)) {
|
||
const records = res.data as any[]
|
||
let totalExpiring = 0
|
||
let earliestDate: string | null = null
|
||
const details: any[] = []
|
||
|
||
for (let i = 0; i < records.length; i++) {
|
||
const record = records[i]
|
||
let recordObj: UTSJSONObject
|
||
if (record instanceof UTSJSONObject) {
|
||
recordObj = record
|
||
} else {
|
||
recordObj = JSON.parse(JSON.stringify(record)) as UTSJSONObject
|
||
}
|
||
|
||
const points = recordObj.getNumber('points') ?? 0
|
||
const expiresAt = recordObj.getString('expires_at') ?? ''
|
||
|
||
totalExpiring += points
|
||
|
||
if (earliestDate == null || expiresAt < earliestDate) {
|
||
earliestDate = expiresAt
|
||
}
|
||
|
||
details.push({
|
||
points: points,
|
||
description: recordObj.getString('description'),
|
||
expires_at: expiresAt,
|
||
created_at: recordObj.getString('created_at') ?? ''
|
||
})
|
||
}
|
||
|
||
result.set('expiring_points', totalExpiring)
|
||
result.set('expiring_date', earliestDate != null ? earliestDate.split('T')[0] : null)
|
||
result.set('details', details)
|
||
}
|
||
|
||
return result
|
||
} catch (e) {
|
||
console.error('获取即将过期积分异常:', e)
|
||
return result
|
||
}
|
||
}
|
||
|
||
// 获取积分概览(包含即将过期积分)
|
||
async getPointsOverview(): Promise<UTSJSONObject> {
|
||
const result = new UTSJSONObject()
|
||
result.set('current_points', 0)
|
||
result.set('total_earned', 0)
|
||
result.set('expiring_points', 0)
|
||
result.set('expiring_date', null)
|
||
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) return result
|
||
|
||
const res = await supa
|
||
.from('ml_user_points')
|
||
.select('points, total_earned, expiring_points, expiring_date')
|
||
.eq('user_id', userId!)
|
||
.single()
|
||
.execute()
|
||
|
||
if (res.error == null && res.data != null) {
|
||
let data: UTSJSONObject
|
||
if (res.data instanceof UTSJSONObject) {
|
||
data = res.data as UTSJSONObject
|
||
} else {
|
||
data = JSON.parse(JSON.stringify(res.data)) as UTSJSONObject
|
||
}
|
||
|
||
result.set('current_points', data.getNumber('points') ?? 0)
|
||
result.set('total_earned', data.getNumber('total_earned') ?? 0)
|
||
result.set('expiring_points', data.getNumber('expiring_points') ?? 0)
|
||
result.set('expiring_date', data.getString('expiring_date'))
|
||
}
|
||
|
||
return result
|
||
} catch (e) {
|
||
console.error('获取积分概览异常:', e)
|
||
return result
|
||
}
|
||
}
|
||
|
||
// 获取过期提醒通知列表
|
||
async getExpiryNotifications(): Promise<any[]> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
const res = await supa
|
||
.from('ml_point_expiry_notifications')
|
||
.select('*')
|
||
.eq('user_id', userId!)
|
||
.eq('is_sent', false)
|
||
.order('expiry_date', { ascending: true })
|
||
.execute()
|
||
|
||
if (res.error != null || res.data == null) {
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
return res.data as any[]
|
||
} catch (e) {
|
||
console.error('获取过期提醒失败:', e)
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
}
|
||
|
||
// 标记过期提醒为已读
|
||
async markNotificationRead(notificationId: string): Promise<boolean> {
|
||
try {
|
||
const res = await supa
|
||
.from('ml_point_expiry_notifications')
|
||
.update({ is_sent: true, sent_at: new Date().toISOString() })
|
||
.eq('id', notificationId)
|
||
.execute()
|
||
|
||
return res.error == null
|
||
} catch (e) {
|
||
console.error('标记通知失败:', e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 手动触发积分维护任务(管理员功能)
|
||
// 注意:UTS不支持rpc,此功能需要在Supabase后台手动执行或通过其他方式触发
|
||
async triggerPointsMaintenance(): Promise<boolean> {
|
||
console.warn('triggerPointsMaintenance: UTS不支持rpc调用,请在Supabase后台手动执行 daily_points_maintenance()')
|
||
return false
|
||
}
|
||
|
||
// ==================== 推销模式 - 商家配置API ====================
|
||
|
||
// 获取商家推销配置
|
||
async getMerchantPromotionConfig(merchantId: string): Promise<UTSJSONObject> {
|
||
const result = new UTSJSONObject()
|
||
result.set('promotion_enabled', false)
|
||
result.set('share_free_enabled', false)
|
||
result.set('distribution_enabled', false)
|
||
result.set('required_count', 4)
|
||
result.set('reward_type', 'product_price')
|
||
result.set('fixed_reward_amount', 0)
|
||
|
||
try {
|
||
const res = await supa
|
||
.from('ml_merchant_promotion_config')
|
||
.select('*')
|
||
.eq('merchant_id', merchantId)
|
||
.limit(1)
|
||
.execute()
|
||
|
||
if (res.error == null && res.data != null && Array.isArray(res.data)) {
|
||
const arr = res.data as any[]
|
||
if (arr.length > 0) {
|
||
const item = arr[0]
|
||
const itemAny = item as any
|
||
|
||
if (itemAny instanceof UTSJSONObject) {
|
||
result.set('promotion_enabled', itemAny.getBoolean('promotion_enabled') ?? false)
|
||
result.set('share_free_enabled', itemAny.getBoolean('share_free_enabled') ?? false)
|
||
result.set('distribution_enabled', itemAny.getBoolean('distribution_enabled') ?? false)
|
||
result.set('required_count', itemAny.getNumber('required_count') ?? 4)
|
||
result.set('reward_type', itemAny.getString('reward_type') ?? 'product_price')
|
||
result.set('fixed_reward_amount', itemAny.getNumber('fixed_reward_amount') ?? 0)
|
||
}
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.error('获取商家推销配置失败:', e)
|
||
}
|
||
|
||
return result
|
||
}
|
||
|
||
// 检查商家是否开启分享免单
|
||
async isShareFreeEnabled(merchantId: string): Promise<boolean> {
|
||
try {
|
||
const config = await this.getMerchantPromotionConfig(merchantId)
|
||
const promotionEnabled = config.get('promotion_enabled')
|
||
const shareFreeEnabled = config.get('share_free_enabled')
|
||
return (promotionEnabled === true || promotionEnabled === 'true') &&
|
||
(shareFreeEnabled === true || shareFreeEnabled === 'true')
|
||
} catch (e) {
|
||
console.error('检查分享免单状态失败:', e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// ==================== 推销模式 - 余额相关API ====================
|
||
|
||
// 获取用户余额
|
||
async getUserBalance(): Promise<UTSJSONObject> {
|
||
const result = new UTSJSONObject()
|
||
result.set('balance', 0)
|
||
result.set('frozen_balance', 0)
|
||
result.set('total_earned', 0)
|
||
result.set('total_withdrawn', 0)
|
||
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) return result
|
||
|
||
const res = await supa
|
||
.from('ml_user_balance')
|
||
.select('*')
|
||
.eq('user_id', userId!)
|
||
.limit(1)
|
||
.execute()
|
||
|
||
if (res.error == null && res.data != null && Array.isArray(res.data)) {
|
||
const arr = res.data as any[]
|
||
if (arr.length > 0) {
|
||
const item = arr[0]
|
||
const itemAny = item as any
|
||
if (itemAny instanceof UTSJSONObject) {
|
||
result.set('balance', itemAny.getNumber('balance') ?? 0)
|
||
result.set('frozen_balance', itemAny.getNumber('frozen_balance') ?? 0)
|
||
result.set('total_earned', itemAny.getNumber('total_earned') ?? 0)
|
||
result.set('total_withdrawn', itemAny.getNumber('total_withdrawn') ?? 0)
|
||
}
|
||
}
|
||
}
|
||
|
||
return result
|
||
} catch (e) {
|
||
console.error('获取用户余额失败:', e)
|
||
return result
|
||
}
|
||
}
|
||
|
||
// 获取余额变动记录
|
||
async getBalanceRecords(page: number = 1, limit: number = 20): Promise<any[]> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
const offset = (page - 1) * limit
|
||
const res = await supa
|
||
.from('ml_balance_records')
|
||
.select('*')
|
||
.eq('user_id', userId!)
|
||
.order('created_at', { ascending: false })
|
||
.range(offset, offset + limit - 1)
|
||
.execute()
|
||
|
||
if (res.error != null || res.data == null) {
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
return res.data as any[]
|
||
} catch (e) {
|
||
console.error('获取余额记录失败:', e)
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
}
|
||
|
||
// ==================== 推销模式 - 分享免单相关API ====================
|
||
|
||
// 创建分享记录
|
||
async createShareRecord(productId: string, orderId: string, orderItemId: string | null, productName: string, productImage: string | null, productPrice: number): Promise<UTSJSONObject> {
|
||
const result = new UTSJSONObject()
|
||
result.set('success', false)
|
||
result.set('share_code', '')
|
||
result.set('message', '')
|
||
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
result.set('message', '请先登录')
|
||
return result
|
||
}
|
||
|
||
// 生成分享码
|
||
const shareCode = this.generateShareCode()
|
||
|
||
const insertData = new UTSJSONObject()
|
||
insertData.set('user_id', userId)
|
||
insertData.set('product_id', productId)
|
||
insertData.set('order_id', orderId)
|
||
insertData.set('order_item_id', orderItemId)
|
||
insertData.set('share_code', shareCode)
|
||
insertData.set('product_name', productName)
|
||
insertData.set('product_image', productImage)
|
||
insertData.set('product_price', productPrice)
|
||
insertData.set('required_count', 4)
|
||
insertData.set('current_count', 0)
|
||
insertData.set('status', 0)
|
||
|
||
const res = await supa
|
||
.from('ml_share_records')
|
||
.insert(insertData)
|
||
.execute()
|
||
|
||
if (res.error != null) {
|
||
console.error('[createShareRecord] 插入失败:', res.error)
|
||
console.error('[createShareRecord] 插入数据:', JSON.stringify(insertData))
|
||
result.set('message', '创建分享记录失败: ' + (res.error.message ?? '未知错误'))
|
||
return result
|
||
}
|
||
|
||
// 获取插入记录的id
|
||
let insertedId = ''
|
||
if (res.data != null && Array.isArray(res.data) && res.data.length > 0) {
|
||
const inserted = res.data[0]
|
||
let insertedObj: UTSJSONObject | null = null
|
||
if (inserted instanceof UTSJSONObject) {
|
||
insertedObj = inserted
|
||
} else {
|
||
insertedObj = JSON.parse(JSON.stringify(inserted)) as UTSJSONObject
|
||
}
|
||
insertedId = insertedObj.getString('id') ?? ''
|
||
}
|
||
|
||
result.set('success', true)
|
||
result.set('id', insertedId)
|
||
result.set('share_code', shareCode)
|
||
result.set('message', '分享创建成功')
|
||
return result
|
||
} catch (e) {
|
||
console.error('创建分享记录失败:', e)
|
||
result.set('message', '创建分享记录异常')
|
||
return result
|
||
}
|
||
}
|
||
|
||
// 生成分享码
|
||
private generateShareCode(): string {
|
||
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'
|
||
let result = ''
|
||
for (let i = 0; i < 8; i++) {
|
||
const randomIndex = Math.floor(Math.random() * chars.length)
|
||
result += chars.charAt(randomIndex)
|
||
}
|
||
return result
|
||
}
|
||
|
||
// 验证分享码
|
||
async validateShareCode(shareCode: string): Promise<UTSJSONObject> {
|
||
const result = new UTSJSONObject()
|
||
result.set('valid', false)
|
||
result.set('share_record', null)
|
||
|
||
try {
|
||
const res = await supa
|
||
.from('ml_share_records')
|
||
.select('*')
|
||
.eq('share_code', shareCode)
|
||
.eq('status', 0)
|
||
.limit(1)
|
||
.execute()
|
||
|
||
if (res.error == null && res.data != null && Array.isArray(res.data)) {
|
||
const arr = res.data as any[]
|
||
if (arr.length > 0) {
|
||
result.set('valid', true)
|
||
result.set('share_record', arr[0])
|
||
}
|
||
}
|
||
|
||
return result
|
||
} catch (e) {
|
||
console.error('验证分享码失败:', e)
|
||
return result
|
||
}
|
||
}
|
||
|
||
// 获取我的分享记录
|
||
async getMyShareRecords(): Promise<any[]> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
const res = await supa
|
||
.from('ml_share_records')
|
||
.select('*')
|
||
.eq('user_id', userId!)
|
||
.order('created_at', { ascending: false })
|
||
.execute()
|
||
|
||
if (res.error != null || res.data == null) {
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
return res.data as any[]
|
||
} catch (e) {
|
||
console.error('获取分享记录失败:', e)
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
}
|
||
|
||
// 获取分享详情
|
||
async getShareDetail(shareId: string): Promise<UTSJSONObject> {
|
||
const result = new UTSJSONObject()
|
||
result.set('share_record', null)
|
||
result.set('secondary_purchases', [] as any[])
|
||
|
||
try {
|
||
const res = await supa
|
||
.from('ml_share_records')
|
||
.select('*')
|
||
.eq('id', shareId)
|
||
.limit(1)
|
||
.execute()
|
||
|
||
if (res.error == null && res.data != null && Array.isArray(res.data)) {
|
||
const arr = res.data as any[]
|
||
if (arr.length > 0) {
|
||
result.set('share_record', arr[0])
|
||
}
|
||
}
|
||
|
||
// 获取二级购买记录
|
||
const purchasesRes = await supa
|
||
.from('ml_secondary_purchases')
|
||
.select('*')
|
||
.eq('share_record_id', shareId)
|
||
.order('created_at', { ascending: false })
|
||
.execute()
|
||
|
||
if (purchasesRes.error == null && purchasesRes.data != null) {
|
||
result.set('secondary_purchases', purchasesRes.data)
|
||
}
|
||
|
||
return result
|
||
} catch (e) {
|
||
console.error('获取分享详情失败:', e)
|
||
return result
|
||
}
|
||
}
|
||
|
||
// 获取免单奖励记录
|
||
async getFreeOrderRewards(): Promise<any[]> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
const res = await supa
|
||
.from('ml_free_order_rewards')
|
||
.select('*')
|
||
.eq('user_id', userId!)
|
||
.order('created_at', { ascending: false })
|
||
.execute()
|
||
|
||
if (res.error != null || res.data == null) {
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
return res.data as any[]
|
||
} catch (e) {
|
||
console.error('获取免单奖励记录失败:', e)
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
}
|
||
|
||
// ==================== 推销模式 - 会员等级相关API ====================
|
||
|
||
// 获取会员等级列表
|
||
async getMemberLevels(): Promise<any[]> {
|
||
try {
|
||
const res = await supa
|
||
.from('ml_member_levels')
|
||
.select('*')
|
||
.eq('status', 1)
|
||
.order('sort_order', { ascending: true })
|
||
.execute()
|
||
|
||
if (res.error != null || res.data == null) {
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
return res.data as any[]
|
||
} catch (e) {
|
||
console.error('获取会员等级列表失败:', e)
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
}
|
||
|
||
// 获取用户会员信息
|
||
async getUserMemberInfo(): Promise<UTSJSONObject> {
|
||
const result = new UTSJSONObject()
|
||
result.set('member_level', 0)
|
||
result.set('level_name', '普通会员')
|
||
result.set('discount', 1.0)
|
||
result.set('total_spent', 0)
|
||
result.set('next_level', null)
|
||
result.set('progress_percent', 0)
|
||
result.set('manual_level', false)
|
||
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) return result
|
||
|
||
// 获取用户信息
|
||
const userRes = await supa
|
||
.from('ml_user_profiles')
|
||
.select('member_level, total_spent, manual_level')
|
||
.eq('user_id', userId!)
|
||
.limit(1)
|
||
.execute()
|
||
|
||
let memberLevel = 0
|
||
let totalSpent = 0
|
||
let manualLevel = false
|
||
|
||
if (userRes.error == null && userRes.data != null && Array.isArray(userRes.data)) {
|
||
const arr = userRes.data as any[]
|
||
if (arr.length > 0) {
|
||
const item = arr[0]
|
||
const itemAny = item as any
|
||
if (itemAny instanceof UTSJSONObject) {
|
||
memberLevel = itemAny.getNumber('member_level') ?? 0
|
||
totalSpent = itemAny.getNumber('total_spent') ?? 0
|
||
manualLevel = itemAny.getBoolean('manual_level') ?? false
|
||
}
|
||
}
|
||
}
|
||
|
||
// 获取等级信息
|
||
const levels = await this.getMemberLevels()
|
||
let levelName = '普通会员'
|
||
let discount = 1.0
|
||
let nextLevel: UTSJSONObject | null = null
|
||
let progressPercent = 0
|
||
|
||
for (let i = 0; i < levels.length; i++) {
|
||
const level = levels[i]
|
||
const levelAny = level as any
|
||
let levelId = 0
|
||
let levelNameStr = ''
|
||
let levelDiscount = 1.0
|
||
let levelMinAmount = 0
|
||
|
||
if (levelAny instanceof UTSJSONObject) {
|
||
levelId = levelAny.getNumber('id') ?? 0
|
||
levelNameStr = levelAny.getString('name') ?? ''
|
||
levelDiscount = levelAny.getNumber('discount') ?? 1.0
|
||
levelMinAmount = levelAny.getNumber('min_amount') ?? 0
|
||
}
|
||
|
||
if (levelId === memberLevel) {
|
||
levelName = levelNameStr
|
||
discount = levelDiscount
|
||
}
|
||
|
||
// 找下一等级
|
||
if (levelId > memberLevel && nextLevel == null) {
|
||
const nextLevelObj = new UTSJSONObject()
|
||
nextLevelObj.set('id', levelId)
|
||
nextLevelObj.set('name', levelNameStr)
|
||
nextLevelObj.set('min_amount', levelMinAmount)
|
||
nextLevel = nextLevelObj
|
||
|
||
// 计算进度
|
||
if (levelMinAmount > 0) {
|
||
const currentLevelMinAmount = this.getCurrentLevelMinAmount(levels, memberLevel)
|
||
const progressRange = levelMinAmount - currentLevelMinAmount
|
||
const currentProgress = totalSpent - currentLevelMinAmount
|
||
progressPercent = Math.min(100, Math.round((currentProgress / progressRange) * 100))
|
||
}
|
||
}
|
||
}
|
||
|
||
result.set('member_level', memberLevel)
|
||
result.set('level_name', levelName)
|
||
result.set('discount', discount)
|
||
result.set('total_spent', totalSpent)
|
||
result.set('next_level', nextLevel)
|
||
result.set('progress_percent', progressPercent)
|
||
result.set('manual_level', manualLevel)
|
||
|
||
return result
|
||
} catch (e) {
|
||
console.error('获取用户会员信息失败:', e)
|
||
return result
|
||
}
|
||
}
|
||
|
||
// 获取当前等级的最低消费金额
|
||
private getCurrentLevelMinAmount(levels: any[], currentLevel: number): number {
|
||
for (let i = 0; i < levels.length; i++) {
|
||
const level = levels[i]
|
||
const levelAny = level as any
|
||
if (levelAny instanceof UTSJSONObject) {
|
||
const levelId = levelAny.getNumber('id') ?? 0
|
||
if (levelId === currentLevel) {
|
||
return levelAny.getNumber('min_amount') ?? 0
|
||
}
|
||
}
|
||
}
|
||
return 0
|
||
}
|
||
|
||
// 获取会员等级变更记录
|
||
async getMemberLevelLogs(): Promise<any[]> {
|
||
try {
|
||
const userId = this.getCurrentUserId()
|
||
if (userId == null) {
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
const res = await supa
|
||
.from('ml_member_level_logs')
|
||
.select('*')
|
||
.eq('user_id', userId!)
|
||
.order('created_at', { ascending: false })
|
||
.execute()
|
||
|
||
if (res.error != null || res.data == null) {
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
|
||
return res.data as any[]
|
||
} catch (e) {
|
||
console.error('获取会员等级变更记录失败:', e)
|
||
const empty: any[] = []
|
||
return empty
|
||
}
|
||
}
|
||
}
|
||
|
||
// 导出单例实例
|
||
export const supabaseService = new SupabaseService()
|
||
|
||
// 默认导出
|
||
export default supabaseService
|