diff --git a/.workbuddy/memory/2026-07-13.md b/.workbuddy/memory/2026-07-13.md index 74934ab..a1f2b65 100644 --- a/.workbuddy/memory/2026-07-13.md +++ b/.workbuddy/memory/2026-07-13.md @@ -23,6 +23,12 @@ - 分类页和首页一样是 tabBar 页,三按钮全亮(好友/朋友圈/复制链接) - 类型检查:零错误 +## 修复收藏列表(图文不显示、价格不对) +- `src/api/shop/shopGoodsFavorite/model.ts`:扩展 `ShopGoodsFavorite` 接口,增加 `product?: { id, name, image, price, salePrice }` 嵌套字段(与秒杀/拼团模型一致)。后端 API 返回的嵌套商品数据会带 `product` 字段。 +- `src/pages/user/favorite-list/index.tsx`: + - 图片:`item.goodsImage` → `item.goodsImage || item.product?.image` + - 价格:`item.salePrice`(市场价)→ `item.product?.price || item.salePrice`(到手价),当市场价与到手价不同时再显示划掉的市场价 +- 类型检查:零错误 ## 修复订单列表看到他人订单 - `src/pages/order/list.tsx`:导入 `useUser`,请求参数加 `userId: user?.id`。原代码请求 `pageShopOrder` 时没传 `userId`,导致后端返回所有人订单。 - `ShopOrderParam` 接口已有 `userId` 字段(见 `src/api/shop/shopOrder/model/index.ts:225`),后端按此过滤——只需前端补传。 diff --git a/.workbuddy/memory/2026-07-14.md b/.workbuddy/memory/2026-07-14.md new file mode 100644 index 0000000..10d589b --- /dev/null +++ b/.workbuddy/memory/2026-07-14.md @@ -0,0 +1,57 @@ +## 购物车功能修复(2026-07-14) + +### 问题 +购物车页面(pages/shop/cart)商品显示异常: +- 商品名称/图片/价格信息缺失 +- 加入购物车后无法正确显示 + +### 根因 +`CartContext.refresh()` 方法在将后端 API 返回的购物车项映射为前端 `CartItem` 时,**遗漏了 `product`(商品详情)和 `sku`(SKU 详情)嵌套对象的映射**。导致页面中 `item.product?.name/image/price` 和 `item.sku?.price/image` 均为 `undefined`,UI 只能显示硬编码的默认值。 + +### 修改 +1. **`src/contexts/CartContext.tsx`**(核心修复) + - `refresh()` 的 `mappedItems` 映射中补充 `product` 和 `sku` 字段 + - 使用 `(item as any).product` / `(item as any).sku` 获取后端返回的嵌套对象 + +2. **`src/utils/image.ts`**(增强健壮性) + - 新增 `ensureFullUrl()` 辅助函数:相对路径自动补全 OSS 域名 `https://oss.wsdns.cn` + - `getCompressedImageUrl()` 内部调用 `ensureFullUrl` 确保 URL 完整性 + +3. **`src/pages/shop/cart.tsx`**(补充图片回退源) + - 图片 `src` 回退链增加 `(item.sku as any)?.image` + - 完整回退链:`goodsImage -> product.image -> sku.image -> ''` + +## 收藏列表图片/价格不显示修复(2026-07-14) + +### 问题 +`pages/user/favorite-list/index` 收藏列表:商品图片、价格、名称均空白。 + +### 根因 +后端 `/api/shop/goods/favorite/list`(前端 `/shop/goods/favorite/list`)走 `ShopGoodsFavoriteServiceImpl.listRel` → `selectListRel`,SQL 只 `SELECT a.*` 未关联商品表,返回字段仅 `id/userId/goodsId/merchantId/tenantId/createTime`,不含商品信息。前端依赖 `goodsImage/goodsName/price/salePrice` 均为 undefined。 + +### 修改(方案1:后端返回商品信息) +1. **后端** `guilixu-java`: + - `ShopGoodsFavorite.java`:新增 4 个 `@TableField(exist=false)` 非持久化字段 `goodsName(String)`、`goodsImage(String)`、`price(BigDecimal)`、`salePrice(BigDecimal)`,并补 `BigDecimal`/`TableField` import。 + - `ShopGoodsFavoriteMapper.xml` 的 `selectSql`:`SELECT a.*, g.name AS goods_name, g.image AS goods_image, g.price AS price, g.sale_price AS sale_price`,`LEFT JOIN shop_goods g ON a.goods_id = g.goods_id`(已确认 `map-underscore-to-camel-case: true`,别名可映射)。 + - 注意:本机无 maven 无法编译,改动为常规 MyBatis/MP 写法,需用户自行编译部署。 + +2. **前端**: + - `src/api/shop/shopGoodsFavorite/model.ts`:`favoriteId` 改为 `id`(后端实际返回 `id`);新增 `price?: string|number`;`salePrice` 改为 `string|number`。 + - `pages/user/favorite-list/index.tsx`:图片用 `item.goodsImage`;价格改为优先 `item.price`(到手价),`salePrice` 作为划线价(`salePrice > price` 时显示);`key` 改用 `item.id ?? item.goodsId`(原 `favoriteId` 始终 undefined 会导致 key 重复)。 + - 注:`src/pages/favorite-list.tsx` 为未注册路由的重复文件,未改动。 + +### 遗留(非本次范围,供参考) +adapter `/list` 接口忽略分页,一次返回全部收藏;前端 `listShopGoodsFavorite({page,limit:20})` 的无限滚动在收藏>20条时会重复追加。如需彻底解决建议改前端走 `/page` 接口或后端 `/list` 支持分页。 + +## 购物车更新 API 请求方式修复(2026-07-14) + +### 问题 +`PUT /api/shop/shop-cart/{id}` 返回 `HttpRequestMethodNotSupportedException: Request method 'PUT' not supported`。 + +### 根因 +`src/api/shop/shopCart/index.ts` 中 3 个函数用了非标准 URL 模式 `PUT /shop/shop-cart/{id}`,而项目所有其他 API(shopOrder、shopArticle 等)的更新操作统一走 `PUT /shop/shop-cart`(基础 URL,id 放 body)。 + +### 修改 +1. `updateCartNum`:`PUT /shop/shop-cart/{id}` + `{quantity}` → `PUT /shop/shop-cart` + `{id, num}` +2. `updateCartChecked`:`PUT /shop/shop-cart/{id}` + `{selected}` → `PUT /shop/shop-cart` + `{id, checked}`(同时修正字段名 `selected`→`checked` 与 model 一致) +3. `updateCartAllChecked`:`PUT /shop/shop-cart/selected` + `{selected}` → `POST /shop/shop-cart/selected` + `{checked}`(改用 POST,自定义批量操作更可能支持 POST;CartContext 已有静默 catch 兜底) diff --git a/src/api/shop/shopCart/index.ts b/src/api/shop/shopCart/index.ts index 16ac413..5129714 100644 --- a/src/api/shop/shopCart/index.ts +++ b/src/api/shop/shopCart/index.ts @@ -62,8 +62,8 @@ export async function addToCart(data: AddToCartParam) { */ export async function updateCartNum(data: UpdateCartNumParam) { const res = await request.put>( - '/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>( - '/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>( + const res = await request.post>( '/shop/shop-cart/selected', - { selected: checked } + { checked } ); if (res.code === 0) { return res.message || '更新成功'; diff --git a/src/api/shop/shopGoodsFavorite/model.ts b/src/api/shop/shopGoodsFavorite/model.ts index 2a89dac..cfa2458 100644 --- a/src/api/shop/shopGoodsFavorite/model.ts +++ b/src/api/shop/shopGoodsFavorite/model.ts @@ -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 { diff --git a/src/contexts/CartContext.tsx b/src/contexts/CartContext.tsx index 2d7b85f..a9ea1ec 100644 --- a/src/contexts/CartContext.tsx +++ b/src/contexts/CartContext.tsx @@ -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) diff --git a/src/pages/shop/cart.tsx b/src/pages/shop/cart.tsx index 489f035..3f502f0 100644 --- a/src/pages/shop/cart.tsx +++ b/src/pages/shop/cart.tsx @@ -170,7 +170,7 @@ const CartPage: React.FC = () => { diff --git a/src/pages/user/favorite-list/index.tsx b/src/pages/user/favorite-list/index.tsx index bde605e..e877df3 100644 --- a/src/pages/user/favorite-list/index.tsx +++ b/src/pages/user/favorite-list/index.tsx @@ -63,23 +63,30 @@ const FavoriteListPage: React.FC = () => { {list.map(item => ( handleItemClick(item.goodsId!)} > - {item.goodsName} - - - ¥{item.salePrice || '0'} + {item.goodsName || item.product?.name} + + + ¥{item.price || item.salePrice || '0'} + + {item.salePrice && Number(item.salePrice) > Number(item.price || 0) ? ( + + ¥{item.salePrice} + + ) : null} + ))} diff --git a/src/utils/image.ts b/src/utils/image.ts index 99616ed..bb251f6 100644 --- a/src/utils/image.ts +++ b/src/utils/image.ts @@ -19,6 +19,36 @@ const DEFAULT_OPTIONS: Required = { 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 = { * // => '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}`; }