import { supabase } from '@/components/supadb/aksupainstance.uts' import supa from '@/components/supadb/aksupainstance.uts' export type ProductReviewItem = { id: string product_id: string product_name: string product_image: string | null user_id: string username: string | null rating: number content: string | null merchant_reply: string | null status: number created_at: string total_count: number } export type ProductReviewQuery = { searchProduct?: string | null searchUser?: string | null status?: number | null startTime?: string | null endTime?: string | null page?: number pageSize?: number } // 直接查询 ml_product_reviews,JOIN ml_products 和 ak_users 获取商品名/图片/用户名 // 使用 supabase.select + Content-Range 实现服务端分页,无需 RPC export async function fetchAdminProductReviews(query?: ProductReviewQuery): Promise<{ total: number; items: Array }> { const page = query?.page ?? 1 const pageSize = query?.pageSize ?? 20 const offset = (page - 1) * pageSize // 构建 PostgREST filter 字符串 const filters: string[] = [] if (query?.status != null) filters.push(`status=eq.${query.status}`) if (query?.startTime != null && query.startTime !== '') filters.push(`created_at=gte.${query.startTime}`) if (query?.endTime != null && query.endTime !== '') filters.push(`created_at=lte.${query.endTime}`) // offset 注入(PostgREST 识别为 SQL OFFSET,避免发送 Range 头) if (offset > 0) filters.push(`offset=${offset}`) const filterStr = filters.length > 0 ? filters.join('&') : null // 查询评价表,并内联关联商品和用户信息 const res = await supabase.select( 'ml_product_reviews', filterStr, { columns: 'id, product_id, user_id, rating, content, merchant_reply, status, created_at, product:ml_products!ml_product_reviews_product_id_fkey(name, main_image_url), reviewer:ak_users!ml_product_reviews_user_id_fkey(username)', limit: pageSize, order: 'created_at.desc', count: 'exact' } ) if (res.status < 200 || res.status >= 300 || res.data == null) { console.error('fetchAdminProductReviews 查询失败, status:', res.status) return { total: 0, items: [] as Array } } // 从 Content-Range 解析总行数 let totalCount = 0 const hdrs = res.headers if (hdrs != null) { let cr: string | null = null if (typeof (hdrs as any).get === 'function') { cr = (hdrs as any).get('content-range') as string | null } if (cr == null) cr = (hdrs as UTSJSONObject)['content-range'] as string | null if (cr != null) { const m = /\/(\d+)$/.exec(cr) if (m != null) totalCount = parseInt(m[1] ?? '0') } } if (totalCount === 0) { totalCount = offset + (Array.isArray(res.data) ? (res.data as any[]).length : 0) } // 字段映射:把 JOIN 结果摊平为 ProductReviewItem const rawRows = res.data as any[] const items: Array = [] // 客户端 searchProduct / searchUser 过滤(PostgREST embedded filter 兼容性问题时的兜底) const spLower = (query?.searchProduct ?? '').toLowerCase() const suLower = (query?.searchUser ?? '').toLowerCase() for (let i = 0; i < rawRows.length; i++) { const row = rawRows[i] as UTSJSONObject const productRaw = row.get('product') const reviewerRaw = row.get('reviewer') let productName = '' let productImage: string | null = null if (productRaw != null) { const p = (productRaw instanceof UTSJSONObject ? productRaw : JSON.parse(JSON.stringify(productRaw))) as UTSJSONObject productName = p.getString('name') ?? '' productImage = p.getString('main_image_url') ?? null } let username: string | null = null if (reviewerRaw != null) { const u = (reviewerRaw instanceof UTSJSONObject ? reviewerRaw : JSON.parse(JSON.stringify(reviewerRaw))) as UTSJSONObject username = u.getString('username') ?? null } // 客户端过滤(searchProduct / searchUser) if (spLower !== '' && !productName.toLowerCase().includes(spLower)) continue if (suLower !== '' && (username ?? '').toLowerCase().includes(suLower) === false) continue items.push({ id: row.getString('id') ?? '', product_id: row.getString('product_id') ?? '', product_name: productName, product_image: productImage, user_id: row.getString('user_id') ?? '', username: username, rating: row.getNumber('rating') ?? 0, content: row.getString('content') ?? null, merchant_reply: row.getString('merchant_reply') ?? null, status: row.getNumber('status') ?? 1, created_at: row.getString('created_at') ?? '', total_count: totalCount } as ProductReviewItem) } return { total: totalCount, items } } export async function approveProductReview(id: string): Promise { const { error } = await supa.from('ml_product_reviews').update({ status: 1, updated_at: new Date().toISOString() }).eq('id', id).execute() if (error != null) { console.error('审核通过失败:', error) return false } return true } export async function rejectProductReview(id: string): Promise { const { error } = await supa.from('ml_product_reviews').update({ status: 3, updated_at: new Date().toISOString() }).eq('id', id).execute() if (error != null) { console.error('审核驳回/隐藏失败:', error) return false } return true } export async function replyProductReview(id: string, reply: string): Promise { const { error } = await supa .from('ml_product_reviews') .update({ merchant_reply: reply, merchant_replied_at: new Date().toISOString(), updated_at: new Date().toISOString() }) .eq('id', id) .execute() if (error != null) { console.error('回复失败:', error) return false } return true } export async function deleteProductReview(id: string): Promise { const { error } = await supa.from('ml_product_reviews').update({ status: 2, updated_at: new Date().toISOString() }).eq('id', id).execute() if (error != null) { console.error('删除失败:', error) return false } return true }