consumer模块完成度95%,准备部署消费者端测试

This commit is contained in:
cyh666666
2026-03-05 08:45:00 +08:00
parent cceb556c62
commit 7f7f723d93
1043 changed files with 53958 additions and 3445 deletions

View File

@@ -1,6 +1,31 @@
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)
@@ -107,7 +132,8 @@ function parseProductFromRaw(item: any): Product {
return []
}
const mainImageUrl = getSafeString('main_image_url')
const mainImageUrl = fixImageUrl(getSafeString('main_image_url'))
const imageUrls = fixImageUrls(getSafeStringArray('image_urls'))
return {
id: getSafeString('id'),
@@ -119,7 +145,7 @@ function parseProductFromRaw(item: any): Product {
market_price: getSafeNumber('market_price'),
main_image_url: mainImageUrl,
image_url: mainImageUrl,
images: getSafeStringArray('image_urls'),
images: imageUrls,
category_id: getSafeString('category_id'),
brand_id: getSafeString('brand_id'),
merchant_id: getSafeString('merchant_id'),
@@ -617,7 +643,7 @@ class SupabaseService {
name: (typeof nameVal == 'string') ? (nameVal as string) : '',
icon: icon,
description: (typeof descVal == 'string') ? (descVal as string) : '',
color: (typeof colorVal == 'string') ? (colorVal as string) : '#4CAF50',
color: (typeof colorVal == 'string') ? (colorVal as string) : '#ff5000',
level: 1,
slug: (typeof slugVal == 'string') ? (slugVal as string) : ''
}
@@ -674,7 +700,7 @@ class SupabaseService {
name: safeGetString(itemObj, 'name'),
icon: icon,
description: safeGetString(itemObj, 'description'),
color: safeGetString(itemObj, 'color').length > 0 ? safeGetString(itemObj, 'color') : '#4CAF50',
color: safeGetString(itemObj, 'color').length > 0 ? safeGetString(itemObj, 'color') : '#ff5000',
level: 2,
parent_id: safeGetString(itemObj, 'parent_id'),
slug: safeGetString(itemObj, 'slug')
@@ -1055,7 +1081,23 @@ class SupabaseService {
}
if (statusNum !== 1) continue
shops.push(item as Shop)
// 手动创建 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 {
@@ -1317,7 +1359,7 @@ class SupabaseService {
const item = JSON.parse(JSON.stringify(rawData[i])) as UTSJSONObject
const images: string[] = []
const mainImageUrl = item.getString('main_image_url')
const mainImageUrl = fixImageUrl(item.getString('main_image_url'))
if (mainImageUrl != null && mainImageUrl !== '') {
images.push(mainImageUrl)
}
@@ -1329,7 +1371,8 @@ class SupabaseService {
const arr = imageUrlsRaw as string[]
if (arr.length > 0 && images.length === 0) {
for (let j = 0; j < arr.length; j++) {
images.push(arr[j])
const fixedUrl = fixImageUrl(arr[j])
if (fixedUrl !== '') images.push(fixedUrl)
}
}
} else {
@@ -1338,11 +1381,13 @@ class SupabaseService {
const parsed = JSON.parse(rawUrlStr)
if (Array.isArray(parsed) && images.length === 0) {
for (let j = 0; j < parsed.length; j++) {
images.push(parsed[j] as string)
const fixedUrl = fixImageUrl(parsed[j] as string)
if (fixedUrl !== '') images.push(fixedUrl)
}
}
} else {
if (images.indexOf(rawUrlStr) === -1) images.push(rawUrlStr)
const fixedUrl = fixImageUrl(rawUrlStr)
if (fixedUrl !== '' && images.indexOf(fixedUrl) === -1) images.push(fixedUrl)
}
}
} catch(e) {
@@ -1415,8 +1460,15 @@ class SupabaseService {
}
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: response.data as Product[],
data: parsedProducts,
total: response.total ?? 0,
page,
limit,
@@ -1482,7 +1534,7 @@ class SupabaseService {
const item = JSON.parse(JSON.stringify(rawData[i])) as UTSJSONObject
const images: string[] = []
const mainImageUrl = item.getString('main_image_url')
const mainImageUrl = fixImageUrl(item.getString('main_image_url'))
if (mainImageUrl != null && mainImageUrl !== '') {
images.push(mainImageUrl)
}
@@ -1494,7 +1546,8 @@ class SupabaseService {
const arr = imageUrlsRaw as string[]
if (arr.length > 0 && images.length === 0) {
for (let j = 0; j < arr.length; j++) {
images.push(arr[j])
const fixedUrl = fixImageUrl(arr[j])
if (fixedUrl !== '') images.push(fixedUrl)
}
}
} else {
@@ -1503,11 +1556,13 @@ class SupabaseService {
const parsed = JSON.parse(rawUrlStr)
if (Array.isArray(parsed) && images.length === 0) {
for (let j = 0; j < parsed.length; j++) {
images.push(parsed[j] as string)
const fixedUrl = fixImageUrl(parsed[j] as string)
if (fixedUrl !== '') images.push(fixedUrl)
}
}
} else {
if (images.indexOf(rawUrlStr) === -1) images.push(rawUrlStr)
const fixedUrl = fixImageUrl(rawUrlStr)
if (fixedUrl !== '' && images.indexOf(fixedUrl) === -1) images.push(fixedUrl)
}
}
} catch(e) {
@@ -1580,8 +1635,15 @@ class SupabaseService {
}
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: response.data as Product[],
data: parsedProducts,
total: response.total ?? 0,
page,
limit,
@@ -2122,19 +2184,33 @@ class SupabaseService {
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}`)
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}`)
}
}
productSpec = parts.join('; ')
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, '; ')
productSpec = jsonStr.replace(/["{}]/g, '').replace(/,/g, ' ').replace(/:/g, ' ')
} catch (e) {}
}
}
@@ -2148,23 +2224,37 @@ class SupabaseService {
const specRaw = sObj.get('specifications')
if (specRaw != null) {
// 优先使用SKU的规格
// 优先使用SKU的规格
if (typeof specRaw === 'string') {
productSpec = specRaw
} else if (specRaw instanceof UTSJSONObject) {
const keys = UTSJSONObject.keys(specRaw)
const parts: string[] = []
for(let k = 0; k < keys.length; k++) {
let val = specRaw.get(keys[k])
if (val != null) {
parts.push(`${keys[k]}: ${val}`)
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}`)
}
}
productSpec = parts.join('; ')
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, '; ')
productSpec = jsonStr.replace(/["{}]/g, '').replace(/,/g, ' ').replace(/:/g, ' ')
} catch (e) {}
}
}
@@ -2379,23 +2469,29 @@ class SupabaseService {
}
}
// 获取与特定商家的聊天记录
async getChatMessages(merchantId: string): Promise<ChatMessage[]> {
// 获取与特定商家的聊天记录 (合并版本)
async getChatMessages(merchantId: string, page: number = 1, pageSize: number = 20): Promise<ChatMessage[]> {
try {
console.log('[getChatMessages] 开始获取聊天记录merchantId:', merchantId)
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(`and(sender_id.eq.${userId},receiver_id.eq.${merchantId}),and(sender_id.eq.${merchantId},receiver_id.eq.${userId})`)
.order('created_at', { ascending: false })
.limit(50)
.or(queryStr)
.order('created_at', { ascending: false }) // 最新在前
.range(fromIndex, toIndex)
.execute()
if (response.error != null) {
console.error('获取聊天记录失败:', response.error)
console.error('getChatMessages error:', response.error)
return []
}
@@ -2413,16 +2509,14 @@ class SupabaseService {
const getSafeString = (key: string): string => {
const val = msgObj.get(key)
if (val == null) return ''
if (typeof val == 'string') return val
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
if (typeof val == 'number') return (val as number) == 1
return false
if (typeof val == 'boolean') return val as boolean
return (val.toString() == '1' || val.toString() == 'true')
}
const msg: ChatMessage = {
@@ -3018,6 +3112,94 @@ class SupabaseService {
}
}
// 取消订单
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 {
@@ -3202,14 +3384,23 @@ class SupabaseService {
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++) {
console.log('[CreateOrder] 处理商品项', i, '类型:', typeof rawItems[i])
const rawItem = rawItems[i]
const itemStr = JSON.stringify(rawItem)
console.log('[CreateOrder] 商品项 JSON:', itemStr)
const itemParsed = JSON.parse(itemStr)
if (itemParsed == null) {
console.error('[CreateOrder] 商品项解析失败')
@@ -3334,7 +3525,6 @@ class SupabaseService {
let grandTotal = 0.0
for(let k = 0; k < groups.length; k++) {
const g = JSON.parse(JSON.stringify(groups[k])) as UTSJSONObject
// 安全获取 items 数组
const gItemsRaw = g.get('items')
if (gItemsRaw == null) continue
const gItems = gItemsRaw as any[]
@@ -3452,7 +3642,36 @@ class SupabaseService {
const empty: any[] = []
return empty
}
return data as any[]
// 修复订单项中的图片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[] = []
@@ -3626,16 +3845,25 @@ class SupabaseService {
// 提交售后申请
async createRefund(data: any): Promise<RefundResponse> {
try {
console.log('[createRefund] 开始处理退款申请')
const userId = this.getCurrentUserId()
if (userId == null) return { success: false, message: '请先登录' }
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 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,
@@ -3649,16 +3877,38 @@ class SupabaseService {
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)
@@ -4809,9 +5059,41 @@ class SupabaseService {
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[] = []
const rawData = response.data as any[]
for (let i = 0; i < rawData.length; i++) {
const item = rawData[i]
let template: any | null = null
@@ -4861,19 +5143,21 @@ class SupabaseService {
tMin = tObj.getNumber('min_spend') ?? 0
}
const couponObj = new UTSJSONObject()
couponObj.set('id', itemId)
couponObj.set('user_id', itemUserId)
couponObj.set('template_id', itemTmplId)
couponObj.set('coupon_code', itemCode)
couponObj.set('status', itemStatus)
couponObj.set('received_at', itemRecv)
couponObj.set('expire_at', itemExpire)
couponObj.set('template_name', tName)
couponObj.set('amount', tAmount)
couponObj.set('min_spend', tMin)
// 创建真正的 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(couponObj as UserCoupon)
coupons.push(couponItem)
}
return coupons
@@ -5061,88 +5345,6 @@ class SupabaseService {
// 聊天相关方法
// ==========================================
// 获取特定会话的消息历史
async getChatMessages(merchantId: string, page: number = 1, pageSize: number = 20): Promise<ChatMessage[]> {
console.log('[getChatMessages] 开始获取聊天记录merchantId:', merchantId, 'page:', page)
const userId = this.getCurrentUserId()
if (userId == null) {
const empty: ChatMessage[] = []
return empty
}
// 计算分页 range
const fromIndex = (page - 1) * pageSize
const toIndex = fromIndex + pageSize - 1
try {
// 使用 or 组合条件查询:(sender_id=me AND receiver_id=merchant) OR (sender_id=merchant AND receiver_id=me)
// 注意Supabase postgrest-js 的 .or() 语法如果是针对同一列很简单,针对复杂逻辑用 string syntax
// 这里简化处理,如果不加 userId 过滤,全靠 RLS
const response = await supa
.from('ml_chat_messages')
.select('*')
.or(`sender_id.eq.${merchantId},receiver_id.eq.${merchantId}`)
.order('created_at', { ascending: false })
.range(fromIndex, toIndex)
.execute()
if (response.error != null) {
console.error('getChatMessages error:', response.error)
const empty: ChatMessage[] = []
return empty
}
const rawData = response.data
if (rawData == null) {
const empty: ChatMessage[] = []
return empty
}
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 ''
if (typeof val == 'string') return val
return ''
}
const getSafeBoolean = (key: string): boolean => {
const val = msgObj.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 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('getChatMessages exception:', e)
const empty: ChatMessage[] = []
return empty
}
}
// 发送消息
async sendMessage(merchantId: string, content: string, msgType: string = 'text'): Promise<boolean> {
// 确保 session 有效