212 lines
5.5 KiB
Plaintext
212 lines
5.5 KiB
Plaintext
import supa from '@/components/supadb/aksupainstance.uts'
|
||
import { rpcOrNull, rpcOrValue } from '@/services/analytics/rpc.uts'
|
||
|
||
export type AdminProduct = {
|
||
id: string
|
||
name: string
|
||
image: string
|
||
price: number
|
||
stock: number
|
||
sales: number
|
||
status: number
|
||
created_at: string
|
||
category_id: string
|
||
category_name: string
|
||
subtitle: string | null
|
||
description: string | null
|
||
product_code: string | null
|
||
unit: string | null
|
||
image_urls: string[] | null
|
||
video_urls: string[] | null
|
||
tags: string[] | null
|
||
attributes: any | null
|
||
}
|
||
|
||
export type AdminProductSku = {
|
||
id?: string
|
||
product_id?: string
|
||
sku_code: string
|
||
specifications: any
|
||
price: number
|
||
stock: number
|
||
status: number
|
||
image_url: string | null
|
||
}
|
||
|
||
export type ProductPageResult = {
|
||
total: number
|
||
items: Array<AdminProduct>
|
||
}
|
||
|
||
/**
|
||
* 分页获取商品列表
|
||
*/
|
||
export async function fetchAdminProductPage(
|
||
page: number,
|
||
pageSize: number,
|
||
filters: {
|
||
name?: string,
|
||
status?: number,
|
||
categoryId?: string
|
||
}
|
||
): Promise<ProductPageResult> {
|
||
const res = await rpcOrNull('rpc_admin_product_list', {
|
||
p_page: page,
|
||
p_page_size: pageSize,
|
||
p_name: filters.name ?? null,
|
||
p_status: filters.status ?? null,
|
||
p_category_id: filters.categoryId ?? null
|
||
} as UTSJSONObject)
|
||
|
||
if (res == null) {
|
||
return { total: 0, items: [] as Array<AdminProduct> }
|
||
}
|
||
|
||
const anyTotal = (res as any).total
|
||
const anyItems = (res as any).items
|
||
|
||
return {
|
||
total: typeof anyTotal === 'number' ? anyTotal : 0,
|
||
items: Array.isArray(anyItems) ? anyItems : [] as Array<AdminProduct>
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取商品详情(包含 SKU 列表)
|
||
*/
|
||
export async function fetchAdminProductDetail(productId: string): Promise<{ product: AdminProduct | null, skus: AdminProductSku[] }> {
|
||
const { data: productData, error: productError } = await supa
|
||
.from('ml_products')
|
||
.select('*, ml_categories(name)')
|
||
.eq('id', productId)
|
||
.single()
|
||
.execute()
|
||
|
||
if (productError != null) {
|
||
console.error('获取商品详情失败:', productError)
|
||
return { product: null, skus: [] }
|
||
}
|
||
|
||
const { data: skuData, error: skuError } = await supa
|
||
.from('ml_product_skus')
|
||
.select('*')
|
||
.eq('product_id', productId)
|
||
.execute()
|
||
|
||
const product = productData as any
|
||
if (product != null && product.ml_categories != null) {
|
||
product.category_name = (product.ml_categories as any).name
|
||
}
|
||
|
||
return {
|
||
product: product as AdminProduct,
|
||
skus: (skuData ?? []) as AdminProductSku[]
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 保存商品(新增或更新,含 SKU 关联保存)
|
||
*/
|
||
export async function saveAdminProduct(product: Partial<AdminProduct>, skus: AdminProductSku[]): Promise<boolean> {
|
||
const session = supa.getSession()
|
||
const uid = session?.user?.getString('id')
|
||
if (uid == null) return false
|
||
|
||
// 1. 保存商品主表
|
||
const { data: savedProduct, error: productError } = await supa
|
||
.from('ml_products')
|
||
.upsert({
|
||
...product,
|
||
merchant_id: uid,
|
||
updated_at: new Date().toISOString()
|
||
})
|
||
.select()
|
||
.single()
|
||
.execute()
|
||
|
||
if (productError != null || savedProduct == null) {
|
||
console.error('保存商品主表失败:', productError)
|
||
return false
|
||
}
|
||
|
||
const savedProductId = (savedProduct as any).id as string
|
||
|
||
// 2. 保存 SKU(简单策略:先删除旧的再插入新的,或使用 upsert)
|
||
// 注意:如果涉及订单关联,不能简单的物理删除旧 SKU
|
||
const skuPayload = skus.map(s => ({
|
||
...s,
|
||
product_id: savedProductId,
|
||
updated_at: new Date().toISOString()
|
||
}))
|
||
|
||
const { error: skuError } = await supa
|
||
.from('ml_product_skus')
|
||
.upsert(skuPayload as any)
|
||
.execute()
|
||
|
||
if (skuError != null) {
|
||
console.error('保存 SKU 失败:', skuError)
|
||
return false
|
||
}
|
||
|
||
return true
|
||
}
|
||
|
||
/**
|
||
* 获取商品状态汇总统计
|
||
*/
|
||
export async function fetchAdminProductCountStats() : Promise<UTSJSONObject | null> {
|
||
return await rpcOrNull('rpc_admin_product_count_stats', {} as UTSJSONObject)
|
||
}
|
||
|
||
/**
|
||
* 获取商品概况统计指标
|
||
*/
|
||
export async function fetchAdminProductStats(startTime : string, endTime : string) : Promise<UTSJSONObject | null> {
|
||
return await rpcOrNull('rpc_admin_product_stats', {
|
||
p_start_time: startTime,
|
||
p_end_time: endTime
|
||
} as UTSJSONObject)
|
||
}
|
||
|
||
/**
|
||
* 获取商品营业趋势数据
|
||
*/
|
||
export async function fetchAdminProductTrend(startTime : string, endTime : string) : Promise<Array<UTSJSONObject>> {
|
||
const res = await rpcOrValue('rpc_admin_product_trend', {
|
||
p_start_time: startTime,
|
||
p_end_time: endTime
|
||
} as UTSJSONObject)
|
||
return Array.isArray(res) ? (res as Array<UTSJSONObject>) : [] as Array<UTSJSONObject>
|
||
}
|
||
|
||
/**
|
||
* 获取商品排行
|
||
*/
|
||
export async function fetchAdminProductRanking(startTime : string, endTime : string, sortBy : string = 'sales', limit : number = 10) : Promise<Array<UTSJSONObject>> {
|
||
const res = await rpcOrValue('rpc_admin_product_ranking', {
|
||
p_start_time: startTime,
|
||
p_end_time: endTime,
|
||
p_sort_by: sortBy,
|
||
p_limit: limit
|
||
} as UTSJSONObject)
|
||
return Array.isArray(res) ? (res as Array<UTSJSONObject>) : [] as Array<UTSJSONObject>
|
||
}
|
||
|
||
/**
|
||
* 更新商品状态 (上架/下架/回收站)
|
||
* @param status 1:上架 2:下架 3:草稿 4:删除
|
||
*/
|
||
export async function updateAdminProductStatus(productId: string, status: number): Promise<boolean> {
|
||
try {
|
||
const ok = await rpcOrValue('rpc_admin_product_update_status', {
|
||
p_product_id: productId,
|
||
p_status: status
|
||
} as UTSJSONObject)
|
||
return ok === true
|
||
} catch (e: any) {
|
||
console.error('更新商品状态失败:', e)
|
||
return false
|
||
}
|
||
}
|