diff --git a/.workbuddy/memory/2026-07-14.md b/.workbuddy/memory/2026-07-14.md index 10d589b..5576528 100644 --- a/.workbuddy/memory/2026-07-14.md +++ b/.workbuddy/memory/2026-07-14.md @@ -55,3 +55,38 @@ adapter `/list` 接口忽略分页,一次返回全部收藏;前端 `listShop 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 兜底) + +## 购物车后端 SQL 关联查询修复(2026-07-14) + +### 问题 +`GET /api/shop/shop-cart` 返回的数据没有商品图片、价格等信息。前端 `CartContext.refresh()` 已映射 `goodsName/goodsImage/skuPrice/skuSpec/stock` 等字段,但后端根本没返回这些字段。 + +### 根因 +后端 `ShopCartMapper.xml` 的 SQL 只做 `SELECT a.* FROM shop_cart a`,没有 JOIN 商品表和 SKU 表。 + +### 后端修改(`guilixu-java` 项目) +1. **`ShopCart.java`**:新增 7 个 `@TableField(exist = false)` 非持久化字段:`goodsName`、`goodsImage`、`skuPrice`、`skuSpec`、`stock`、`salePrice`、`dealerPrice`;补充 `TableField` import +2. **`ShopCartMapper.xml`**:SQL 改为 `LEFT JOIN shop_goods g ON a.goods_id = g.goods_id` + `LEFT JOIN shop_goods_sku s ON a.sku_id = s.id`,SELECT 别名映射到新字段(`COALESCE(s.price, g.price) AS sku_price`、`COALESCE(s.stock, g.stock) AS stock`);同时修复 `userId/goodsId/merchantId` 从 `LIKE` 改为 `=`(原 LIKE 会导致 userId=1 匹配到 10/11/21 等) +3. **`ShopCartController.java`**:`list()` 方法自动按当前登录用户过滤(`param.setUserId(loginUser.getUserId().longValue())`) + +### 前端修改 +4. **`src/api/shop/shopCart/model/index.ts`**:`ShopCart` 接口新增 `salePrice`、`dealerPrice` 字段 +5. **`src/contexts/CartContext.tsx`**:`CartItem` 新增 `salePrice`、`dealerPrice`;`refresh()` 映射这两个字段;`calcPrice()` VIP 优先用扁平 `i.dealerPrice`(之前只看 `i.product?.dealerPrice`) +6. **`src/pages/shop/cart.tsx`** + **`src/pages/shop/checkout.tsx`**:价格显示逻辑同步更新,优先使用扁平 `item.dealerPrice` / `item.salePrice` + +## 购物车前后端字段名对齐修复(2026-07-14) + +### 问题 +修改购物车数量时后端报 `UPDATE shop_cart WHERE id=?`(SET 子句为空),MyBatis-Plus 找不到要更新的字段。 + +### 根因 +前后端字段名不匹配: +- 前端发送 `num`,后端实体字段是 `cartNum` → Jackson 反序列化时 `cartNum` 为 null → 空 UPDATE +- 前端发送 `checked`,后端实体字段是 `selected` → 同样不匹配 +- 列表返回 `cartNum`,前端读 `item.num` → 数量永远为默认值 1 + +### 修改文件 +1. **`src/api/shop/shopCart/model/index.ts`**:`ShopCart.num` → `cartNum`;新增 `selected` 字段;`AddToCartParam.num` → `cartNum`;`UpdateCartNumParam.num` → `cartNum` +2. **`src/api/shop/shopCart/index.ts`**:`updateCartNum` 发 `{id, cartNum}`;`updateCartChecked` 发 `{id, selected}` +3. **`src/contexts/CartContext.tsx`**:`refresh()` 读 `item.cartNum` 和 `item.selected`;`addItem()` 发 `cartNum`;`updateQuantity` 调用 `{id, cartNum}` +4. **`src/pages/shop/index.tsx`** + **`src/pages/shop/category.tsx`**:`addToCart` 参数 `num` → `cartNum` diff --git a/src/api/shop/shopCart/index.ts b/src/api/shop/shopCart/index.ts index 5129714..d8c9c37 100644 --- a/src/api/shop/shopCart/index.ts +++ b/src/api/shop/shopCart/index.ts @@ -63,7 +63,7 @@ export async function addToCart(data: AddToCartParam) { export async function updateCartNum(data: UpdateCartNumParam) { const res = await request.put>( '/shop/shop-cart', - { id: data.id, num: data.num } + { id: data.id, cartNum: data.cartNum } ); if (res.code === 0) { return res.message || '更新成功'; @@ -77,7 +77,7 @@ export async function updateCartNum(data: UpdateCartNumParam) { export async function updateCartChecked(id: number, checked: boolean) { const res = await request.put>( '/shop/shop-cart', - { id, checked } + { id, selected: checked } ); if (res.code === 0) { return res.message || '更新成功'; diff --git a/src/api/shop/shopCart/model/index.ts b/src/api/shop/shopCart/model/index.ts index b8ed1ad..580a73e 100644 --- a/src/api/shop/shopCart/model/index.ts +++ b/src/api/shop/shopCart/model/index.ts @@ -12,8 +12,8 @@ export interface ShopCart { goodsId?: number; // SKU ID skuId?: number; - // 数量 - num?: number; + // 数量(后端字段名 cartNum) + cartNum?: number; // 商品名称 goodsName?: string; // 商品图片 @@ -24,6 +24,12 @@ export interface ShopCart { skuSpec?: string; // 库存 stock?: number; + // 市场价(划线价) + salePrice?: string | number; + // 经销商价(VIP专享) + dealerPrice?: string | number; + // 是否选中(后端字段名 selected) + selected?: boolean; // 租户id tenantId?: number; // 创建时间 @@ -52,8 +58,8 @@ export interface AddToCartParam { goodsId: number; // SKU ID(单规格可不传) skuId?: number; - // 数量 - num: number; + // 数量(后端字段名 cartNum) + cartNum: number; } /** @@ -62,8 +68,8 @@ export interface AddToCartParam { export interface UpdateCartNumParam { // 购物车ID id: number; - // 数量 - num: number; + // 数量(后端字段名 cartNum) + cartNum: number; } /** diff --git a/src/contexts/CartContext.tsx b/src/contexts/CartContext.tsx index a9ea1ec..6f90d6c 100644 --- a/src/contexts/CartContext.tsx +++ b/src/contexts/CartContext.tsx @@ -18,6 +18,8 @@ export interface CartItem { skuPrice?: string skuSpec?: string stock?: number + salePrice?: string | number + dealerPrice?: string | number } interface CartContextType { @@ -53,21 +55,20 @@ export const CartProvider: React.FC<{ children: ReactNode }> = ({ children }) => const data = await listShopCart() // 防御性检查:确保返回的是数组 const safeData = Array.isArray(data) ? data : [] - // 转换为前端格式,默认都选中 + // 转换为前端格式 const mappedItems: CartItem[] = safeData.map(item => ({ id: item.id, goodsId: item.goodsId!, skuId: item.skuId, - quantity: item.num || 1, + quantity: item.cartNum || 1, goodsName: item.goodsName, goodsImage: item.goodsImage, skuPrice: item.skuPrice, skuSpec: item.skuSpec, stock: item.stock, - // 后端可能返回嵌套的 product/sku 对象(对应 ShopCartItem 结构) - product: (item as any).product, - sku: (item as any).sku, - checked: true, + salePrice: item.salePrice, + dealerPrice: item.dealerPrice, + checked: item.selected !== false, })) setItems(mappedItems) } catch (err) { @@ -83,7 +84,7 @@ export const CartProvider: React.FC<{ children: ReactNode }> = ({ children }) => try { const params = { goodsId: product.goodsId!, - num: quantity, + cartNum: quantity, } as Record // 只有多规格商品才传 skuId,且不能为 0 if (sku?.id && sku.id > 0) { @@ -125,7 +126,7 @@ export const CartProvider: React.FC<{ children: ReactNode }> = ({ children }) => if (!item?.id) return try { - await updateCartNum({ id: item.id, num: quantity }) + await updateCartNum({ id: item.id, cartNum: quantity }) // 本地更新 setItems(prev => prev.map(i => (i.goodsId === goodsId && i.skuId === skuId) ? { ...i, quantity } : i @@ -201,8 +202,8 @@ export const CartProvider: React.FC<{ children: ReactNode }> = ({ children }) => const calcPrice = (list: CartItem[]) => { const vip = isVipMember() return list.reduce((sum, i) => { - // VIP 会员优先使用 dealerPrice - const dealerPrice = vip ? (i.product as any)?.dealerPrice : null + // VIP 会员优先使用 dealerPrice(后端关联字段 > product 嵌套对象) + const dealerPrice = vip ? (i.dealerPrice || (i.product as any)?.dealerPrice) : null const unitPrice = dealerPrice || i.skuPrice || i.sku?.price || i.product?.salePrice || i.product?.price || 0 return sum + Number(unitPrice) * i.quantity }, 0).toFixed(2) diff --git a/src/pages/shop/cart.tsx b/src/pages/shop/cart.tsx index 3f502f0..acc55d2 100644 --- a/src/pages/shop/cart.tsx +++ b/src/pages/shop/cart.tsx @@ -182,7 +182,7 @@ const CartPage: React.FC = () => { )} - ¥{isVipMember() && (item.product as any)?.dealerPrice ? (item.product as any).dealerPrice : (item.skuPrice || item.sku?.price || item.product?.salePrice || item.product?.price || '0')} + ¥{isVipMember() && (item.dealerPrice || (item.product as any)?.dealerPrice) ? (item.dealerPrice || (item.product as any).dealerPrice) : (item.skuPrice || item.sku?.price || item.salePrice || item.product?.salePrice || item.product?.price || '0')} updateQuantity(item.goodsId, item.skuId, item.quantity - 1)}> diff --git a/src/pages/shop/category.tsx b/src/pages/shop/category.tsx index 7f50733..7e091c1 100644 --- a/src/pages/shop/category.tsx +++ b/src/pages/shop/category.tsx @@ -135,7 +135,7 @@ const CategoryPage: React.FC = () => { if (!requireLogin({ action: 'addToCart' })) return addToCart({ goodsId: product.goodsId!, - num: product.step || 1, + cartNum: product.step || 1, }).then(() => { Taro.showToast({ title: '已加入购物车', icon: 'success' }) }).catch(err => { diff --git a/src/pages/shop/checkout.tsx b/src/pages/shop/checkout.tsx index 0154699..e4f0497 100644 --- a/src/pages/shop/checkout.tsx +++ b/src/pages/shop/checkout.tsx @@ -149,9 +149,9 @@ const CheckoutPage: React.FC = () => { if (!items || items.length === 0) return 0 const vip = isVipMember() return items.reduce((sum, item) => { - // VIP 会员优先使用 dealerPrice - const dealerPrice = vip ? (item.product as any)?.dealerPrice : null - const unitPrice = dealerPrice || item.skuPrice || item.sku?.price || item.product?.salePrice || item.product?.price || 0 + // VIP 会员优先使用 dealerPrice(后端关联字段 > product 嵌套对象) + const dealerPrice = vip ? (item.dealerPrice || (item.product as any)?.dealerPrice) : null + const unitPrice = dealerPrice || item.skuPrice || item.sku?.price || item.salePrice || item.product?.salePrice || item.product?.price || 0 return sum + Number(unitPrice) * (item.quantity || item.num || 1) }, 0) }, [buyNowItems, selectedItems]) @@ -368,7 +368,7 @@ const CheckoutPage: React.FC = () => { x{item.quantity || item.num || 1} - ¥{isVipMember() && (item.product as any)?.dealerPrice ? (item.product as any).dealerPrice : (item.skuPrice || item.sku?.salePrice || item.sku?.price || item.product?.salePrice || item.product?.price || '0')} + ¥{isVipMember() && (item.dealerPrice || (item.product as any)?.dealerPrice) ? (item.dealerPrice || (item.product as any).dealerPrice) : (item.skuPrice || item.sku?.salePrice || item.sku?.price || item.salePrice || item.product?.salePrice || item.product?.price || '0')} diff --git a/src/pages/shop/index.tsx b/src/pages/shop/index.tsx index 1066814..59fd6fa 100644 --- a/src/pages/shop/index.tsx +++ b/src/pages/shop/index.tsx @@ -147,7 +147,7 @@ const ShopPage: React.FC = () => { if (!requireLogin({ action: 'addToCart' })) return addToCart({ goodsId: product.goodsId!, - num: product.step || 1, + cartNum: product.step || 1, }).then(() => { Taro.showToast({ title: '已加入购物车', icon: 'success' }) }).catch(err => {