consumer模块完成度95%,优化安卓端界面和小程序测试

This commit is contained in:
cyh666666
2026-03-11 17:17:32 +08:00
parent 5517c93666
commit 77f9968d18
622 changed files with 3100 additions and 1995 deletions

View File

@@ -18,8 +18,13 @@ function fixImageUrls(urls: any): string[] {
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)
try {
const urlStr = JSON.stringify(arr[i])
if (urlStr != null && urlStr.startsWith('"') && urlStr.endsWith('"')) {
const fixed = fixImageUrl(urlStr.substring(1, urlStr.length - 1))
if (fixed !== '') result.push(fixed)
}
} catch (e) {}
}
return result
}
@@ -34,10 +39,12 @@ 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 ''
const strVal = JSON.stringify(rawVal)
if (strVal == null) return ''
if (strVal.startsWith('"') && strVal.endsWith('"')) {
return strVal.substring(1, strVal.length - 1)
}
return strVal
} catch (e) {
console.error('safeGetString error for key:', key, e)
return ''
@@ -49,11 +56,10 @@ 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
}
try {
const numVal = rawVal as number
if (!isNaN(numVal)) return numVal
} catch (e) {}
return 0
} catch (e) {
console.error('safeGetNumber error for key:', key, e)
@@ -66,9 +72,10 @@ 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
try {
const boolVal = rawVal as boolean
return boolVal
} catch (e) {}
return false
} catch (e) {
console.error('safeGetBoolean error for key:', key, e)
@@ -93,78 +100,46 @@ function safeGetStringArray(obj: UTSJSONObject, key: string): string[] {
// 辅助函数:从原始数据解析商品
function parseProductFromRaw(item: any): Product {
try {
console.log('[parseProductFromRaw] 开始解析商品')
const itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
console.log('[parseProductFromRaw] JSON转换成功')
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 mainImageUrl = fixImageUrl(safeGetString(itemObj, 'main_image_url'))
const imageUrls = fixImageUrls(safeGetStringArray(itemObj, 'image_urls'))
console.log('[parseProductFromRaw] 图片处理完成')
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'),
const result: Product = {
id: safeGetString(itemObj, 'id'),
name: safeGetString(itemObj, 'name'),
description: safeGetString(itemObj, 'description'),
base_price: safeGetNumber(itemObj, 'base_price'),
price: safeGetNumber(itemObj, 'base_price'),
original_price: safeGetNumber(itemObj, 'market_price'),
market_price: safeGetNumber(itemObj, '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
category_id: safeGetString(itemObj, 'category_id'),
brand_id: safeGetString(itemObj, 'brand_id'),
merchant_id: safeGetString(itemObj, 'merchant_id'),
total_stock: safeGetNumber(itemObj, 'total_stock'),
stock: safeGetNumber(itemObj, 'total_stock'),
sale_count: safeGetNumber(itemObj, 'sale_count'),
status: safeGetNumber(itemObj, 'status'),
is_featured: safeGetBoolean(itemObj, 'is_featured'),
is_new: safeGetBoolean(itemObj, 'is_new'),
is_hot: safeGetBoolean(itemObj, 'is_hot'),
specification: safeGetString(itemObj, 'specification'),
usage: safeGetString(itemObj, 'usage'),
side_effects: safeGetString(itemObj, 'side_effects'),
precautions: safeGetString(itemObj, 'precautions'),
expiry_date: safeGetString(itemObj, 'expiry_date'),
storage_conditions: safeGetString(itemObj, 'storage_conditions'),
approval_number: safeGetString(itemObj, 'approval_number'),
created_at: safeGetString(itemObj, 'created_at')
}
console.log('[parseProductFromRaw] 商品解析成功:', result.name)
return result
} catch (e) {
console.error('parseProductFromRaw error:', e)
return {
@@ -949,17 +924,23 @@ class SupabaseService {
ascending: boolean = false
): Promise<PaginatedResponse<Product>> {
try {
const keywordLower = keyword.toLowerCase()
const encodedKeyword = encodeURIComponent(keywordLower)
const orString = `name.ilike.%${encodedKeyword}%,description.ilike.%${encodedKeyword}%,subtitle.ilike.%${encodedKeyword}%,brand_name.ilike.%${encodedKeyword}%`
console.log('[searchProducts] 搜索关键词:', keyword, '编码后:', encodedKeyword)
console.log('[searchProducts] or条件:', orString)
let query = supa
.from('ml_products_detail_view')
.select('*', { count: 'exact' })
.eq('status', 1)
.or(orString)
// 根据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 }) // 销量总是降序
query = query.order('sale_count', { ascending: false })
} else {
// 默认按销量降序
query = query.order('sale_count', { ascending: false })
}
@@ -968,8 +949,27 @@ class SupabaseService {
.limit(limit)
.execute()
if (response.error != null) {
console.error('搜索商品失败:', response.error)
let dataLength = 0
try {
const respData = response.data
if (respData != null && Array.isArray(respData)) {
dataLength = (respData as any[]).length
}
} catch (e) {
console.error('[searchProducts] 获取数据长度失败:', e)
}
let statusNum = 0
try {
statusNum = response.status as number
} catch (e) {}
console.log('[searchProducts] 响应状态:', statusNum, '数据条数:', dataLength)
let hasError = false
try {
hasError = response.error != null
} catch (e) {}
if (hasError) {
console.error('[searchProducts] 搜索商品失败:', response.error)
return {
data: [] as Product[],
total: 0,
@@ -980,6 +980,7 @@ class SupabaseService {
}
const rawData = response.data
console.log('[searchProducts] rawData:', rawData != null ? 'not null' : 'null')
if (rawData == null) {
return {
data: [] as Product[],
@@ -991,43 +992,42 @@ class SupabaseService {
}
const products: Product[] = []
const rawList = rawData as any[]
const keywordLower = keyword.toLowerCase()
let rawList: any[] = []
try {
rawList = rawData as any[]
console.log('[searchProducts] rawList长度:', rawList.length)
} catch (e) {
console.error('[searchProducts] 转换rawList失败:', e)
return {
data: [] as Product[],
total: 0,
page,
limit,
hasmore: false
}
}
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
}
console.log('[searchProducts] 处理第', i + 1, '个商品')
products.push(parseProductFromRaw(item))
}
let totalNum = 0
try {
totalNum = response.total as number
} catch (e) {}
let hasmoreVal = false
try {
hasmoreVal = response.hasmore as boolean
} catch (e) {}
return {
data: products,
total: response.total ?? products.length,
total: totalNum > 0 ? totalNum : products.length,
page,
limit,
hasmore: response.hasmore ?? false
hasmore: hasmoreVal
}
} catch (error) {
console.error('搜索商品异常:', error)
@@ -1048,10 +1048,11 @@ class SupabaseService {
limit: number = 20
): Promise<PaginatedResponse<Shop>> {
try {
const encodedKeyword = encodeURIComponent(keyword)
const response = await supa
.from('ml_shops')
.select('*', { count: 'exact' })
.ilike('shop_name', `%${keyword}%`)
.ilike('shop_name', `%${encodedKeyword}%`)
.order('product_count', { ascending: false })
.page(page)
.limit(limit)
@@ -1984,24 +1985,31 @@ class SupabaseService {
}
const productRes = await supa
.from('ml_products_detail_view')
.select('id,name,main_image_url,base_price,attributes,merchant_id,shop_name')
.from('ml_products')
.select('*')
.in('id', productIdsAny)
.execute()
console.log('[getCartItems] 商品查询结果:', productRes.error != null ? 'error' : 'success', productRes.error)
if (productRes.error == null && productRes.data != null) {
const products = productRes.data as any[]
console.log('[getCartItems] 商品数量:', products.length)
for (let i = 0; i < products.length; i++) {
let p = products[i]
let pid: string = ''
let pname: string = ''
if (p instanceof UTSJSONObject) {
pid = p.getString('id') ?? ''
pname = p.getString('name') ?? ''
} else {
const pObj = JSON.parse(JSON.stringify(p)) as UTSJSONObject
pid = pObj.getString('id') ?? ''
pname = pObj.getString('name') ?? ''
}
console.log('[getCartItems] 商品:', pid, pname)
if (pid !== '') {
productMap.set(pid, p)
}
@@ -2009,6 +2017,55 @@ class SupabaseService {
}
}
// 批量查询店铺信息
const shopMap = new Map<string, string>()
const merchantIds: string[] = []
// 遍历 productMap 获取 merchant_id
productMap.forEach((p: any, pid: string) => {
let mid: string = ''
if (p instanceof UTSJSONObject) {
mid = p.getString('merchant_id') ?? ''
} else {
const pObj = JSON.parse(JSON.stringify(p)) as UTSJSONObject
mid = pObj.getString('merchant_id') ?? ''
}
if (mid !== '' && !merchantIds.includes(mid)) {
merchantIds.push(mid)
}
})
if (merchantIds.length > 0) {
const merchantIdsAny: any[] = []
for(let i=0; i<merchantIds.length; i++) {
merchantIdsAny.push(merchantIds[i])
}
const shopRes = await supa
.from('ml_shops')
.select('merchant_id,shop_name')
.in('merchant_id', merchantIdsAny)
.execute()
if (shopRes.error == null && shopRes.data != null) {
const shops = shopRes.data as any[]
for (let i = 0; i < shops.length; i++) {
let s = shops[i]
let mid: string = ''
let sname: string = ''
if (s instanceof UTSJSONObject) {
mid = s.getString('merchant_id') ?? ''
sname = s.getString('shop_name') ?? ''
} else {
const sObj = JSON.parse(JSON.stringify(s)) as UTSJSONObject
mid = sObj.getString('merchant_id') ?? ''
sname = sObj.getString('shop_name') ?? ''
}
if (mid !== '') {
shopMap.set(mid, sname)
}
}
}
}
// 批量查询 SKU 详情
const skuMap = new Map<string, any>()
if (skuIds.length > 0) {
@@ -2018,7 +2075,7 @@ class SupabaseService {
}
const skuRes = await supa
.from('ml_product_skus')
.select('id, specifications, price, image_url')
.select('*')
.in('id', skuIdsAny)
.execute()
@@ -2083,6 +2140,8 @@ class SupabaseService {
const product = productMap.get(productId)
const sku = (skuId !== '' && skuMap.has(skuId)) ? skuMap.get(skuId) : null
console.log('[getCartItems] 处理购物车项:', itemId, 'productId:', productId, 'product存在:', product != null, 'sku存在:', sku != null)
let merchantId: string = cartMerchantId
let productName: string = ''
let productImage: string = ''
@@ -2091,6 +2150,7 @@ class SupabaseService {
let shopNameStr: string = '未知店铺'
if (product != null) {
console.log('[getCartItems] product类型:', typeof product, 'instanceof UTSJSONObject:', product instanceof UTSJSONObject)
if (product instanceof UTSJSONObject) {
// 优先使用购物车中的 merchant_id如果没有则使用商品中的
if (merchantId == '') {
@@ -2099,170 +2159,114 @@ class SupabaseService {
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) {}
}
}
}
*/
console.log('[getCartItems] 从UTSJSONObject获取: name=', productName, 'price=', productPrice)
} else {
const pObj = JSON.parse(JSON.stringify(product)) as UTSJSONObject
merchantId = pObj.getString('merchant_id') ?? ''
if (merchantId == '') {
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) {}
}
}
}
*/
console.log('[getCartItems] 从普通对象获取: name=', productName, 'price=', productPrice)
}
// 从 shopMap 获取店铺名称
if (merchantId !== '' && shopMap.has(merchantId)) {
shopNameStr = shopMap.get(merchantId) ?? '未知店铺'
}
}
// 如果有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
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) {
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 {
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 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) {}
}
}
}
const specRaw = sObj.get('specifications')
if (specRaw != null) {
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 {
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'