fix(shop): 修复购物车和收藏列表图片及价格显示问题
- 购物车上下文补全映射后端返回的 product 和 sku 嵌套对象字段 - 购物车页面商品图片资源回退增加 sku.image - 添加 ensureFullUrl 辅助函数,完善图片 URL 完整性,增强图片压缩函数健壮性 - 收藏列表扩展 ShopGoodsFavorite 模型,支持嵌套商品详情字段 - 收藏列表图片、名称、价格显示逻辑升级,优先显示嵌套商品信息 - 购物车相关接口请求参数及方法调整,修正 PUT 请求路径和请求体字段 - 收藏列表列表项 key 使用 id 或 goodsId 避免重复Key警告
This commit is contained in:
@@ -62,8 +62,8 @@ export async function addToCart(data: AddToCartParam) {
|
||||
*/
|
||||
export async function updateCartNum(data: UpdateCartNumParam) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/shop/shop-cart/' + data.id,
|
||||
{ quantity: data.num }
|
||||
'/shop/shop-cart',
|
||||
{ id: data.id, num: data.num }
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message || '更新成功';
|
||||
@@ -76,8 +76,8 @@ export async function updateCartNum(data: UpdateCartNumParam) {
|
||||
*/
|
||||
export async function updateCartChecked(id: number, checked: boolean) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/shop/shop-cart/' + id,
|
||||
{ selected: checked }
|
||||
'/shop/shop-cart',
|
||||
{ id, checked }
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message || '更新成功';
|
||||
@@ -89,9 +89,9 @@ export async function updateCartChecked(id: number, checked: boolean) {
|
||||
* 全选/取消全选购物车
|
||||
*/
|
||||
export async function updateCartAllChecked(checked: boolean) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/shop/shop-cart/selected',
|
||||
{ selected: checked }
|
||||
{ checked }
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message || '更新成功';
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
export interface ShopGoodsFavorite {
|
||||
favoriteId?: number
|
||||
id?: number
|
||||
userId?: number
|
||||
goodsId?: number
|
||||
createTime?: string
|
||||
goodsName?: string
|
||||
goodsImage?: string
|
||||
salePrice?: string
|
||||
price?: string | number
|
||||
salePrice?: string | number
|
||||
/** 嵌套商品详情 */
|
||||
product?: {
|
||||
id?: number
|
||||
name?: string
|
||||
image?: string
|
||||
price?: string
|
||||
salePrice?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface ShopGoodsFavoriteParam {
|
||||
|
||||
@@ -64,6 +64,9 @@ export const CartProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||
skuPrice: item.skuPrice,
|
||||
skuSpec: item.skuSpec,
|
||||
stock: item.stock,
|
||||
// 后端可能返回嵌套的 product/sku 对象(对应 ShopCartItem 结构)
|
||||
product: (item as any).product,
|
||||
sku: (item as any).sku,
|
||||
checked: true,
|
||||
}))
|
||||
setItems(mappedItems)
|
||||
|
||||
@@ -170,7 +170,7 @@ const CartPage: React.FC = () => {
|
||||
</View>
|
||||
<Image
|
||||
className="w-20 h-20 rounded-md bg-gray-100 flex-shrink-0"
|
||||
src={getCompressedImageUrl(item.goodsImage || item.product?.image || '')}
|
||||
src={getCompressedImageUrl(item.goodsImage || item.product?.image || (item.sku as any)?.image || '')}
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<View className="flex-1 flex flex-col justify-between">
|
||||
|
||||
@@ -63,23 +63,30 @@ const FavoriteListPage: React.FC = () => {
|
||||
<View className='grid grid-cols-2 gap-3'>
|
||||
{list.map(item => (
|
||||
<View
|
||||
key={item.favoriteId}
|
||||
key={item.id ?? item.goodsId}
|
||||
className='bg-white rounded-lg overflow-hidden'
|
||||
onClick={() => handleItemClick(item.goodsId!)}
|
||||
>
|
||||
<Image
|
||||
className='w-full'
|
||||
style={{ height: '160px' }}
|
||||
src={getCompressedImageUrl(item.goodsImage)}
|
||||
mode='aspectFill'
|
||||
src={getCompressedImageUrl(item.goodsImage || item.product?.image || '')}
|
||||
mode='aspectFit'
|
||||
/>
|
||||
<View className='p-2'>
|
||||
<Text className='text-sm text-gray-800 line-clamp-2 block'>
|
||||
{item.goodsName}
|
||||
</Text>
|
||||
<Text className='text-red-500 text-sm font-medium mt-1 block'>
|
||||
¥{item.salePrice || '0'}
|
||||
{item.goodsName || item.product?.name}
|
||||
</Text>
|
||||
<View className='flex flex-row items-baseline gap-1 mt-1'>
|
||||
<Text className='text-red-500 text-sm font-medium'>
|
||||
¥{item.price || item.salePrice || '0'}
|
||||
</Text>
|
||||
{item.salePrice && Number(item.salePrice) > Number(item.price || 0) ? (
|
||||
<Text className='text-xs text-gray-400 line-through'>
|
||||
¥{item.salePrice}
|
||||
</Text>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
|
||||
@@ -19,6 +19,36 @@ const DEFAULT_OPTIONS: Required<ImageCompressOptions> = {
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* OSS 域名(文件上传接口同域)
|
||||
*/
|
||||
const OSS_BASE_URL = 'https://oss.wsdns.cn';
|
||||
|
||||
/**
|
||||
* 确保图片 URL 为完整路径
|
||||
*
|
||||
* 后端可能返回相对路径(如 /uploads/xxx.jpg),需要补全 OSS 域名。
|
||||
*
|
||||
* @param url 原始图片 URL
|
||||
* @returns 完整图片 URL
|
||||
*
|
||||
* @example
|
||||
* ensureFullUrl('/headers/xxx.jpg')
|
||||
* // => 'https://oss.wsdns.cn/headers/xxx.jpg'
|
||||
*
|
||||
* @example
|
||||
* ensureFullUrl('https://oss.wsdns.cn/headers/xxx.jpg')
|
||||
* // => 'https://oss.wsdns.cn/headers/xxx.jpg'
|
||||
*/
|
||||
export function ensureFullUrl(url: string): string {
|
||||
if (!url) return '';
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
return url;
|
||||
}
|
||||
// 相对路径:补全 OSS 域名
|
||||
return `${OSS_BASE_URL}${url.startsWith('/') ? '' : '/'}${url}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 OSS 压缩后的图片 URL
|
||||
*
|
||||
@@ -47,19 +77,20 @@ const DEFAULT_OPTIONS: Required<ImageCompressOptions> = {
|
||||
* // => 'https://oss.wsdns.cn/xxx.jpg'
|
||||
*/
|
||||
export function getCompressedImageUrl(url: string, options?: ImageCompressOptions): string {
|
||||
// 空 URL 直接返回空字符串
|
||||
if (!url) return '';
|
||||
// 先补全为完整 URL
|
||||
const fullUrl = ensureFullUrl(url);
|
||||
if (!fullUrl) return '';
|
||||
|
||||
const { width, quality, enabled } = { ...DEFAULT_OPTIONS, ...options };
|
||||
|
||||
// 未启用压缩,原样返回
|
||||
if (!enabled) return url;
|
||||
if (!enabled) return fullUrl;
|
||||
|
||||
// 已包含 OSS 处理参数,避免重复拼接
|
||||
if (url.includes('x-oss-process')) return url;
|
||||
if (fullUrl.includes('x-oss-process')) return fullUrl;
|
||||
|
||||
// 已有其他 query 参数用 & 拼接,否则用 ?
|
||||
const separator = url.includes('?') ? '&' : '?';
|
||||
const separator = fullUrl.includes('?') ? '&' : '?';
|
||||
|
||||
return `${url}${separator}x-oss-process=image/resize,w_${width}/quality,Q_${quality}`;
|
||||
return `${fullUrl}${separator}x-oss-process=image/resize,w_${width}/quality,Q_${quality}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user