前端各页面对接数据

This commit is contained in:
2026-02-02 17:34:31 +08:00
parent d57592ca7d
commit b6200cda28
25 changed files with 7634 additions and 1977 deletions

View File

@@ -1,9 +1,8 @@
import { createClient } from '@/components/supadb/aksupa.uts'
import { SUPA_URL, SUPA_KEY } from '@/ak/config.uts'
import supa from '@/components/supadb/aksupainstance.uts'
import type { AkReqResponse } from '@/uni_modules/ak-req/index.uts'
// 创建 Supabase 客户端
const supa = createClient(SUPA_URL, SUPA_KEY)
// 使用单例 Supabase 客户端
// const supa = createClient(SUPA_URL, SUPA_KEY)
// 类型定义
export interface Category {
@@ -18,18 +17,46 @@ export interface Category {
export interface Product {
id: string
category_id: string
merchant_id: string
name: string
description?: string
specification: string
specification?: string
price: number
base_price?: number
original_price?: number
market_price?: number
image?: string
manufacturer: string
main_image_url?: string
image_urls?: string // JSON string
manufacturer?: string
sales?: number
sale_count?: number
stock?: number
available_stock?: number
badge?: string
shop_id?: string
shop_name?: string
attributes?: string // JSON string
created_at?: string
expiry_date?: string
approval_number?: string
usage?: string
side_effects?: string
}
export interface Shop {
id: string
merchant_id: string
shop_name: string
shop_logo?: string
shop_banner?: string
description?: string
contact_name?: string
contact_phone?: string
rating_avg?: number
total_sales?: number
product_count?: number
total_sales_count?: number
created_at?: string
}
@@ -73,10 +100,33 @@ export interface PaginatedResponse<T> {
hasmore: boolean
}
export interface ProductSku {
id: string
product_id: string
sku_code: string
specifications: string // JSON string
price: number
market_price?: number
cost_price?: number
stock?: number
warning_stock?: number
image_url?: string
weight?: number
status?: number
created_at?: string
}
class SupabaseService {
// 获取当前用户ID
private getCurrentUserId(): string | null {
public getCurrentUserId(): string | null {
try {
// 优先从 Supabase 会话获取
const session = supa.getSession()
if (session && session.user) {
return session.user.getString('id')
}
// 后备:尝试从本地存储获取 (兼容旧逻辑)
const userId = uni.getStorageSync('user_id')
return userId ? userId as string : null
} catch (e) {
@@ -89,7 +139,7 @@ class SupabaseService {
async getCategories(): Promise<Category[]> {
try {
const response = await supa
.from('categories')
.from('ml_categories')
.select('*')
.order('name', { ascending: true })
.execute()
@@ -114,10 +164,10 @@ class SupabaseService {
): Promise<PaginatedResponse<Product>> {
try {
const response = await supa
.from('products')
.from('ml_products')
.select('*', { count: 'exact' })
.eq('category_id', categoryId)
.order('sales', { ascending: false })
.order('sale_count', { ascending: false })
.page(page)
.limit(limit)
.execute()
@@ -152,6 +202,28 @@ class SupabaseService {
}
}
// 根据商品ID获取SKU列表
async getProductSkus(productId: string): Promise<ProductSku[]> {
try {
const response = await supa
.from('ml_product_skus')
.select('*')
.eq('product_id', productId)
.eq('status', 1)
.execute()
if (response.error) {
console.error('获取商品SKU失败:', response.error)
return []
}
return response.data as ProductSku[]
} catch (error) {
console.error('获取商品SKU异常:', error)
return []
}
}
// 搜索商品
async searchProducts(
keyword: string,
@@ -162,18 +234,18 @@ class SupabaseService {
): Promise<PaginatedResponse<Product>> {
try {
let query = supa
.from('products')
.from('ml_products')
.select('*', { count: 'exact' })
.or(`name.ilike.%${keyword}%,manufacturer.ilike.%${keyword}%,specification.ilike.%${keyword}%`)
// 根据sortBy和ascending设置排序
if (sortBy === 'price') {
query = query.order('price', { ascending })
} else if (sortBy === 'sales') {
query = query.order('sales', { ascending: false }) // 销量总是降序
query = query.order('base_price', { ascending })
} else if (sortBy === 'sales' || sortBy === 'sale_count') {
query = query.order('sale_count', { ascending: false }) // 销量总是降序
} else {
// 默认按销量降序
query = query.order('sales', { ascending: false })
query = query.order('sale_count', { ascending: false })
}
const response = await query
@@ -215,7 +287,7 @@ class SupabaseService {
async getProductById(productId: string): Promise<Product | null> {
try {
const response = await supa
.from('products')
.from('ml_products')
.select('*')
.eq('id', productId)
.single()
@@ -233,11 +305,80 @@ class SupabaseService {
}
}
// 根据商户ID获取店铺信息
async getShopByMerchantId(merchantId: string): Promise<Shop | null> {
try {
const response = await supa
.from('ml_shops')
.select('*')
.eq('merchant_id', merchantId)
.single()
.executeAs<Shop>()
if (response.error) {
console.error('获取店铺信息失败:', response.error)
return null
}
const data = response.data
if (Array.isArray(data)) {
if (data.length > 0) return data[0] as Shop
return null
}
return data as Shop
} catch (error) {
console.error('获取店铺信息异常:', error)
return null
}
}
// 根据商户ID获取商品列表
async getProductsByMerchantId(merchantId: string, page: number = 1, limit: number = 20): Promise<PaginatedResponse<Product>> {
try {
const response = await supa
.from('ml_products')
.select('*', { count: 'exact' })
.eq('merchant_id', merchantId)
.order('created_at', { ascending: false })
.page(page)
.limit(limit)
.execute()
if (response.error) {
console.error('获取商户商品失败:', response.error)
return {
data: [],
total: 0,
page,
limit,
hasmore: false
}
}
return {
data: response.data as Product[],
total: response.total || 0,
page,
limit,
hasmore: response.hasmore || false
}
} catch (error) {
console.error('获取商户商品异常:', error)
return {
data: [],
total: 0,
page,
limit,
hasmore: false
}
}
}
// 获取热销商品(按销量排序)
async getHotProducts(limit: number = 10): Promise<Product[]> {
try {
const response = await supa
.from('products')
.from('ml_products')
.select('*')
.order('sales', { ascending: false })
.limit(limit)
@@ -259,7 +400,7 @@ class SupabaseService {
async getProductsByPrice(limit: number = 10, ascending: boolean = true): Promise<Product[]> {
try {
const response = await supa
.from('products')
.from('ml_products')
.select('*')
.order('price', { ascending })
.limit(limit)
@@ -281,7 +422,7 @@ class SupabaseService {
async getProductsByNewest(limit: number = 10): Promise<Product[]> {
try {
const response = await supa
.from('products')
.from('ml_products')
.select('*')
.order('created_at', { ascending: false })
.limit(limit)
@@ -304,7 +445,7 @@ class SupabaseService {
try {
// 直接使用 neq 空字符串查询,忽略 null 值null 表示没有 badge不应被推荐
const response = await supa
.from('products')
.from('ml_products')
.select('*')
.neq('badge', '')
.order('sales', { ascending: false })
@@ -328,7 +469,7 @@ class SupabaseService {
async getDiscountProducts(limit: number = 10): Promise<Product[]> {
try {
const response = await supa
.from('products')
.from('ml_products')
.select('*')
.eq('badge', '特价')
.order('sales', { ascending: false })
@@ -369,14 +510,13 @@ class SupabaseService {
selected,
created_at,
updated_at,
products!inner (
ml_products!inner (
id,
name,
image,
price,
specification,
shop_id,
shop_name
merchant_id
)
`)
.eq('user_id', userId)
@@ -387,12 +527,59 @@ class SupabaseService {
console.error('获取购物车失败:', response.error)
return []
}
const cartData = response.data as any[]
// 调试日子:打印购物车数据第一条结构,确认产品字段名
if (cartData && Array.isArray(cartData) && cartData.length > 0) {
console.log('Cart Item Structure:', JSON.stringify(cartData[0]))
}
const merchantIds: string[] = []
if (cartData && Array.isArray(cartData)) {
for (const item of cartData) {
// PostgREST 返回的关联字段通常与表名一致
// 尝试获取ml_products如果为空则尝试products
let product = item['ml_products'] as any
if (!product) {
product = item['products'] as any
}
if (product && product.merchant_id && !merchantIds.includes(product.merchant_id)) {
merchantIds.push(product.merchant_id as string)
}
}
}
// 查询店铺信息
const shopMap = new Map<string, any>()
if (merchantIds.length > 0) {
const shopRes = await supa
.from('ml_shops')
.select('id, merchant_id, shop_name')
.in('merchant_id', merchantIds)
.execute()
if (!shopRes.error && shopRes.data != null) {
const shops = shopRes.data as any[]
for (const shop of shops) {
shopMap.set(shop.merchant_id as string, shop)
}
}
}
// 处理返回数据构建CartItem数组
const cartItems: CartItem[] = []
if (response.data && Array.isArray(response.data)) {
for (const item of response.data) {
const product = item.products as any
if (cartData && Array.isArray(cartData)) {
for (const item of cartData) {
let product = item['ml_products'] as any
if (!product) {
product = item['products'] as any
}
const merchantId = product?.merchant_id as string
const shopInfo = shopMap.get(merchantId)
cartItems.push({
id: item.id as string,
@@ -405,8 +592,8 @@ class SupabaseService {
product_image: product?.image as string,
product_price: product?.price as number,
product_specification: product?.specification as string,
shop_id: product?.shop_id as string,
shop_name: product?.shop_name as string,
shop_id: shopInfo ? (shopInfo['id'] as string) : (merchantId || 'unknown_shop'),
shop_name: shopInfo ? (shopInfo['shop_name'] as string) : '未知店铺',
created_at: item.created_at as string,
updated_at: item.updated_at as string
})
@@ -430,27 +617,59 @@ class SupabaseService {
}
// 检查商品是否已在购物车中
const existingResponse = await supa
// 注意:必须处理 sku_id 为空的情况,使用 is.null 过滤器
let query = supa
.from('ml_shopping_cart')
.select('*')
.eq('user_id', userId)
.eq('product_id', productId)
.eq('sku_id', skuId || '')
.single()
.execute()
if (skuId && skuId.length > 0) {
query = query.eq('sku_id', skuId)
} else {
query = query.is('sku_id', null)
}
const existingResponse = await query.single().execute()
let existingItem: any | null = null
if (existingResponse.data != null) {
const rawData = existingResponse.data as any
if (Array.isArray(rawData)) {
if (rawData.length > 0) {
existingItem = rawData[0]
}
} else {
existingItem = rawData
}
}
let response
if (existingResponse.data) {
if (existingItem != null) {
// 商品已存在,更新数量
const existingItem = existingResponse.data as any
response = await supa
.from('ml_shopping_cart')
.update({
quantity: (existingItem.quantity || 0) + quantity,
updated_at: new Date().toISOString()
})
.eq('id', existingItem.id)
.execute()
console.log('Found existing cart item:', JSON.stringify(existingItem))
// 确保 existingItem.id 存在
const itemId = existingItem['id']
const itemQty = existingItem['quantity']
if (itemId != null) {
const currentQty = typeof itemQty === 'number' ? itemQty : parseInt(String(itemQty || 0))
const newQty = currentQty + quantity
response = await supa
.from('ml_shopping_cart')
.update({
quantity: newQty,
updated_at: new Date().toISOString()
})
.eq('id', itemId)
.execute()
} else {
console.error('购物车已有商品但缺少ID无法更新. Data:', JSON.stringify(existingItem))
return false
}
} else {
// 商品不存在,添加新记录
response = await supa
@@ -671,7 +890,7 @@ class SupabaseService {
const response = await supa
.from('ml_user_addresses')
.select('*')
.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 })
@@ -700,7 +919,7 @@ class SupabaseService {
const response = await supa
.from('ml_user_addresses')
.select('*')
.select('*, recipient_name:receiver_name, phone:receiver_phone, detail_address:address_detail')
.eq('id', addressId)
.eq('user_id', userId)
.single()
@@ -745,12 +964,12 @@ class SupabaseService {
.from('ml_user_addresses')
.insert({
user_id: userId,
recipient_name: address.recipient_name,
phone: address.phone,
receiver_name: address.recipient_name,
receiver_phone: address.phone,
province: address.province,
city: address.city,
district: address.district,
detail_address: address.detail_address,
address_detail: address.detail_address,
postal_code: address.postal_code || null,
is_default: address.is_default || false,
created_at: new Date().toISOString(),
@@ -792,13 +1011,22 @@ class SupabaseService {
if (address.is_default) {
await this.clearDefaultAddress(userId)
}
// 构造更新数据,映射字段名到数据库列名
const updateData = {}
if (address.recipient_name != null) updateData['receiver_name'] = address.recipient_name
if (address.phone != null) updateData['receiver_phone'] = address.phone
if (address.province != null) updateData['province'] = address.province
if (address.city != null) updateData['city'] = address.city
if (address.district != null) updateData['district'] = address.district
if (address.detail_address != null) updateData['address_detail'] = address.detail_address
if (address.postal_code != null) updateData['postal_code'] = address.postal_code
if (address.is_default != null) updateData['is_default'] = address.is_default
updateData['updated_at'] = new Date().toISOString()
const response = await supa
.from('ml_user_addresses')
.update({
...address,
updated_at: new Date().toISOString()
})
.update(updateData)
.eq('id', addressId)
.eq('user_id', userId)
.execute()
@@ -843,6 +1071,298 @@ class SupabaseService {
}
}
// 清除默认地址(内部使用)
private async clearDefaultAddress(userId: string): Promise<void> {
try {
await supa
.from('ml_user_addresses')
.update({
is_default: false,
updated_at: new Date().toISOString()
})
.eq('user_id', userId)
.eq('is_default', true)
.execute()
} catch (error) {
console.error('清除默认地址异常:', error)
}
}
// 获取用户资料
async getUserProfile(): Promise<any | null> {
try {
const userId = this.getCurrentUserId()
if (!userId) return null
// 联合查询 auth user 和 profile
// 由于 Supabase auth table 不可直接访问,这里查询 ml_user_profiles
const response = await supa
.from('ml_user_profiles')
.select('*')
.eq('user_id', userId)
.single()
.execute()
if (response.error) {
// 如果不存在 profile可能只有 auth user这里暂时返回空或创建默认
return null
}
return response.data
} catch (e) {
return null
}
}
// 创建订单
async createOrder(orderData: {
merchant_id: string,
product_amount: number,
shipping_fee: number,
total_amount: number,
shipping_address: any,
items: any[]
}): Promise<string | null> {
try {
const userId = this.getCurrentUserId()
if (!userId) return null
// 生成订单号
const orderNo = 'ORD' + Date.now() + Math.floor(Math.random() * 1000)
// 1. 创建主订单
const orderResponse = await supa
.from('ml_orders')
.insert({
user_id: userId,
merchant_id: orderData.merchant_id,
order_no: orderNo,
product_amount: orderData.product_amount,
shipping_fee: orderData.shipping_fee,
total_amount: orderData.total_amount,
paid_amount: 0,
shipping_address: JSON.stringify(orderData.shipping_address),
order_status: 1, // 待付款
payment_status: 1, // 未支付
shipping_status: 1, // 未发货
created_at: new Date().toISOString()
})
.select()
.single()
.execute()
if (orderResponse.error) {
console.error('创建订单失败:', orderResponse.error)
return null
}
const orderId = orderResponse.data['id'] as string
// 2. 创建订单项
const orderItems = orderData.items.map((item: any) => ({
order_id: orderId,
product_id: item.product_id,
sku_id: item.sku_id || null,
product_name: item.product_name,
sku_name: item.sku_name || '',
specifications: item.specifications ? JSON.stringify(item.specifications) : '{}',
image_url: item.image_url,
price: item.price,
quantity: item.quantity,
total_amount: item.price * item.quantity,
created_at: new Date().toISOString()
}))
const itemsResponse = await supa
.from('ml_order_items')
.insert(orderItems)
.execute()
if (itemsResponse.error) {
console.error('创建订单项失败:', itemsResponse.error)
// 此时应该回滚订单,但这里简化处理
return null
}
// 3. 清除购物车中已购买的商品(如果是从购物车购买)
// 这一步通常在前端调用 removeCartItem 或在此处根据参数处理
return orderId
} catch (error) {
console.error('创建订单异常:', error)
return null
}
}
// 获取订单列表
async getOrders(status: number = 0): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (!userId) return []
let query = supa
.from('ml_orders')
.select(`
*,
ml_order_items (*)
`)
.eq('user_id', userId)
.order('created_at', { ascending: false })
if (status > 0) {
query = query.eq('order_status', status)
}
const response = await query.execute()
if (response.error) {
console.error('获取订单列表失败:', response.error)
return []
}
return response.data || []
} catch (error) {
console.error('获取订单列表异常:', error)
return []
}
}
// 获取订单详情
async getOrderDetail(orderId: string): Promise<any | null> {
try {
const userId = this.getCurrentUserId()
if (!userId) return null
const response = await supa
.from('ml_orders')
.select(`
*,
ml_order_items (*),
ml_shops (shop_name, id)
`)
.eq('id', orderId)
.eq('user_id', userId)
.single()
.execute()
if (response.error) {
return null
}
return response.data
} catch (e) {
return null
}
}
// 收藏相关
async checkFavorite(productId: string): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (!userId) return false
const response = await supa
.from('ml_user_favorites')
.select('id')
.eq('user_id', userId)
.eq('target_id', productId)
.eq('target_type', 1) // 1 for product
.single()
.execute()
return !!response.data
} catch(e) {
return false
}
}
async toggleFavorite(productId: string): Promise<boolean> {
try {
const userId = this.getCurrentUserId()
if (!userId) return false
// Check if exists
const exists = await this.checkFavorite(productId)
if (exists) {
// Delete
await supa
.from('ml_user_favorites')
.delete()
.eq('user_id', userId)
.eq('target_id', productId)
.eq('target_type', 1)
.execute()
return false // Now not favorite
} else {
// Add
await supa
.from('ml_user_favorites')
.insert({
user_id: userId,
target_id: productId,
target_type: 1,
created_at: new Date().toISOString()
})
.execute()
return true // Now favorite
}
} catch (e) {
return false
}
}
async getFavorites(): Promise<any[]> {
try {
const userId = this.getCurrentUserId()
if (!userId) return []
// 需要关联查询商品信息
const response = await supa
.from('ml_user_favorites')
.select(`
id,
target_id,
created_at,
ml_products!target_id (
id, name, image_urls, main_image_url, price, sales
)
`)
.eq('user_id', userId)
.eq('target_type', 1)
.order('created_at', { ascending: false })
.execute()
if (response.error) return []
return response.data || []
} catch (e) {
return []
}
}
async getAddressList(): Promise<UserAddress[]> {
try {
const userId = this.getCurrentUserId()
if (!userId) return []
const response = await supa
.from('ml_user_addresses')
.select('*, recipient_name:receiver_name, phone:receiver_phone, detail_address:address_detail')
.eq('user_id', userId)
.order('is_default', { ascending: false })
.order('created_at', { ascending: false })
.execute()
if (response.error) {
console.error('获取地址列表失败:', response.error)
return []
}
return response.data as UserAddress[]
} catch (e) {
console.error('获取地址列表异常:', e)
return []
}
}
// 设置默认地址
async setDefaultAddress(addressId: string): Promise<boolean> {
try {
@@ -877,31 +1397,6 @@ class SupabaseService {
return false
}
}
// 清除用户的默认地址(内部方法)
private async clearDefaultAddress(userId: string): Promise<boolean> {
try {
const response = await supa
.from('ml_user_addresses')
.update({
is_default: false,
updated_at: new Date().toISOString()
})
.eq('user_id', userId)
.eq('is_default', true)
.execute()
if (response.error) {
console.error('清除默认地址失败:', response.error)
return false
}
return true
} catch (error) {
console.error('清除默认地址异常:', error)
return false
}
}
}
// 导出单例实例