## 购物车功能修复(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 兜底) ## 购物车后端 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` ## 禁用 loginByOpenId 静默登录(2026-07-14) ### 改动 - **`src/contexts/UserContext.tsx`**:移除"情况3"静默登录块(`Taro.login` + `loginByOpenId`),无 token 时直接保持未登录状态;清理 `loginByOpenId`、`TenantId` 无用导入 - **`src/hooks/useUser.ts`**:移除降级方案中的 `loginByOpenId` 调用,仅从 storage 读取;清理 `loginByOpenId`、`TenantId` 无用导入 - `src/api/layout/index.ts` 中 `loginByOpenId` 函数定义保留,仅不再被调用 ## 商城分类页价格登录可见(2026-07-14) ### 改动 - **`src/pages/shop/index.tsx`**:价格显示从手写 `Text` 改为 `Price` 组件 + `loginMask` 属性(与首页一致),游客看到"登录后查看价格";VIP 划线价逻辑保留在 `original` prop 中;单位 `unitName` 仅登录后显示 ## 订单列表页未登录可查看订单 Bug 修复(2026-07-14) ### 问题 `pages/order/list` 未登录用户也能看到订单数据。 ### 根因 页面缺少登录校验——`useEffect` 直接调用 `loadOrders`,未登录时 `user?.userId` 为 `undefined`,后端未按用户过滤直接返回了订单。 ### 修改 - **`src/pages/order/list.tsx`**: 1. 从 `useUser()` 获取 `isLoggedIn` 2. `useEffect` 中未登录时调用 `requireLogin({ action: 'viewOrder', redirect: '/pages/order/list', content: '登录后才能查看订单,是否前往登录?' })` 弹窗引导,而不是直接跳转 3. render 中 `if (!isLoggedIn) return null` 兜底,避免未登录时渲染订单内容 - **`src/passport/login.tsx`**:登录成功后的 `tabBarUrls` 白名单补全 `pages/order/list`,确保从订单入口登录后能 `switchTab` 回订单页 ## 我的页面未登录可点击需登录功能 Bug 修复(2026-07-14) ### 问题 `pages/user/user` 未登录时,点击「我的订单」下的按钮(待付款/待发货/待收货/已完成)、「全部订单」、以及菜单中的「升级VIP会员」「我的钱包」「收货地址」「我的收藏」都能直接跳转页面。 ### 修改 - **`src/pages/user/user.tsx`**: 1. 导入 `requireLogin` from `@/utils/login-guard` 2. 给需要登录的菜单项加 `requireAuth: true` 标记(升级VIP会员/门店中心/我的钱包/收货地址/我的收藏) 3. 新增 `handleMenuClick(item)`:`requireAuth` 项调用 `requireLogin` 弹窗拦截,已登录才 `navigateTo` 4. 新增 `handleOrderTabClick(status)` 和 `handleViewAllOrders()`:订单入口 `requireLogin` 拦截,并加 `redirect: '/pages/order/list'` 让登录后回跳订单页 5. 未登录时需登录的菜单项和订单按钮加 `opacity-50` 视觉降级 6. 「帮助中心」「设置」不需要登录,保持原样 ## 订单列表页登录后仍看到别人订单 Bug 修复(2026-07-14) ### 问题 `pages/order/list` 刚登录成功后仍能看到别人的订单,刷新重进后才正常。 ### 根因 `loadOrders` 的 `useCallback` 依赖数组为 `[updateTabState]`,未包含 `user`,导致**闭包过期**——登录后 `isLoggedIn` 变 `true` 触发 effect 调用 `loadOrders`,但闭包中的 `user` 仍是初始渲染时的 `null`,`userId: user?.userId` 传了 `undefined`,后端未按用户过滤返回了全部订单。 ### 修改 - **`src/pages/order/list.tsx`**: 1. 新增 `userIdRef`(`useRef(user?.userId)`),每渲染同步 `userIdRef.current = user?.userId`,避免闭包过期 2. `loadOrders` 开头从 `userIdRef.current` 取 userId,`if (!userId) return` 硬拦截——没有当前登录用户 userId 就禁止加载 3. `useEffect` 中增加 `if (!user?.userId) return` 判断,已登录但 userId 还没拿到时等待下次渲染 4. `useEffect` 依赖数组加 `user?.userId`,userId 变化时重新触发加载 ## 商城列表页浮动购物车按钮(2026-07-14) ### 需求 `pages/shop/index` 页面右下角添加浮动购物车按钮,显示购物车商品数量角标。 ### 实现 - **`src/pages/shop/index.tsx`**: 1. 从 `useCartContext()` 额外解构 `totalCount` 2. 页面末尾添加 fixed 定位浮动按钮:绿色圆形(48px),`bottom:80px right:16px`(避开 tabBar) 3. `totalCount > 0` 时右上角显示红色角标,超过 99 显示 `99+` 4. 点击 `Taro.navigateTo` 跳转购物车页(cart 非 tabBar 页) 5. 数量实时更新:`addItem` 内部调 `refresh()` → `totalCount` 派生值自动重渲染 ## 购物车页面进入不刷新 Bug 修复(2026-07-14) ### 问题 从商品列表页/分类页加入购物车后,进入购物车页面,新加的商品不显示,只有之前已在购物车里的商品。 ### 根因 1. **购物车页面缺少 `useDidShow`**:`cart.tsx` 仅在 `useEffect([isLoggedIn])` 中调用 `refresh()`,登录状态不变时不会重新拉取数据。从其他页面返回购物车时,页面不刷新。 2. **商品列表页/分类页直接调 API 加购**:`shop/index.tsx` 和 `shop/category.tsx` 直接调用 `addToCart` API,未通过 `CartContext.addItem`,导致 CartContext 状态不同步(`addItem` 内部会调 `refresh()`)。 ### 修改 1. **`src/pages/shop/cart.tsx`**:导入 `useDidShow`,添加 `useDidShow(() => { if (isLoggedIn) refresh() })`,页面每次显示时从服务端拉取最新购物车数据 2. **`src/pages/shop/index.tsx`**:导入 `useCartContext`,`handleAddToCart` 改用 `addItem(product, undefined, product.step || 1)` 代替直接调 `addToCart` API;移除 `addToCart` 导入 3. **`src/pages/shop/category.tsx`**:同上,改用 `useCartContext` 的 `addItem` ### 需求 首页顶部搜索栏右侧铃铛图标,门店店员有新订单时能收到提醒。 ### 改动 - **`src/pages/index/index.tsx`**: 1. 导入 `useNewOrderDetector`、`getMyClerk`、`pageShopOrder`、`useDidShow`、`useCallback`、`useRef` 2. 登录后调 `getMyClerk()` 检测店员身份,存 `isClerk` state + `isClerkRef` ref(ref 用于轮询回调中读取最新值,避免闭包过期) 3. `useDidShow` 时也刷新店员身份 4. 接入 `useNewOrderDetector`:30秒轮询 `pageShopOrder({page:1,limit:5})`;`fetchLatestOrders` 内用 `isClerkRef.current` 网关——非店员直接返回空数组(不建 baseline,无副作用) 5. 新订单回调:`Taro.vibrateShort` 震动 + `Taro.showToast` 提示"您有 N 条新订单" 6. 铃铛角标从静态红点改为动态:`newOrderCount > 0` 显示数字角标(>99 显示 99+),`=== 0` 时显示小圆点 7. 点击处理:店员 → `navigateTo` 门店订单页(有新订单时先 `clearUnread()`);普通用户 → 通知页 ### 设计要点 - `isClerkRef` 解决 hooks 闭包过期问题——`fetchLatestOrders` 的 `useCallback` 依赖空数组,但通过 ref 读取最新店员状态 - 非店员时 `fetchLatestOrders` 返回 `[]`,hook 内 `doCheck` 在 `list.length === 0` 时 return,`knownOrderIdsRef` 保持 `null` 不建 baseline;店员身份确认后首次真实请求才建 baseline,不会误报 - `useNewOrderDetector` 内部已处理 `useDidShow`/`useDidHide` 生命周期,tabBar 页切走自动停轮询 ## 商品分类页浮动购物车图标优化(2026-07-14) ### 问题 商品分类页(pages/shop/index)右下角绿色圆形浮动购物车按钮,使用 emoji `🛒` 作为图标,在绿色背景上对比度不足、线条不清晰。 ### 修改 1. 新增白色线条购物车 SVG 图标:`src/assets/icons/cart.svg` 2. 导出 base64 数据 URI 常量:`src/assets/icons/index.ts` 中 `CART_ICON_WHITE` 3. `src/pages/shop/index.tsx` 浮动购物车按钮改用 `Image` 组件加载 `CART_ICON_WHITE`(28×28px,`mode='aspectFit'`),图标更清晰 ## 商品详情页出现孤立 "0" 排查修复(2026-07-14) ### 问题 `pages/shop/product-detail` 价格区域下方出现一个不归属任何标签的孤立 `0`。 ### 根因 `src/pages/shop/product-detail.tsx` 中赚取积分行的条件渲染写成: ```tsx {product.gainIntegral && Number(product.gainIntegral) > 0 && (...)} ``` 当后端返回 `gainIntegral` 为数字 `0` 时,JSX 短路与运算直接返回 `0`,React 会把它渲染成一个纯文本节点。这就是页面上那个孤立的 `0`。 ### 修改 - **`src/pages/shop/product-detail.tsx`**: - 去掉条件里的 `product.gainIntegral &&`,改成 `{Number(product.gainIntegral) > 0 && (...)}`,避免 `0` 泄漏。 - 顺手把配送区域 `goodsWeight` 的同类写法也改成 `{Number(product.goodsWeight) > 0 && (...)}`,防止重量为 0 时同样泄漏。 - 另按用户要求「是 0 就不显示」,把销量行也改为 `{Number(product.sales) > 0 && (...)}`,销量为 0 时整条「销量: 0」不渲染。 ## 订单列表页下单后不自动刷新修复(2026-07-14) ### 问题 `pages/order/list` 下单后从 checkout 页 `Taro.switchTab` 跳过来,订单列表不刷新,看不到新订单。 ### 根因 订单列表页是 tabBar 页,`switchTab` 不会重新挂载组件。`useEffect` 依赖 `[tabIndex, isLoggedIn, user?.userId]`,这些值不变就不会重新触发;且 effect 内有 `if (!tabStates[tabIndex].initialized)` 守卫,页面已初始化过就直接跳过。 ### 修改 - **`src/pages/order/list.tsx`**:新增 `Taro.useDidShow` 生命周期钩子 - 用 `isFirstShow` ref 跳过首次显示(由 `useEffect` 处理初始加载) - 后续每次显示时:标记所有 tab 为 `initialized: false`(切 tab 时会重新加载),并立即 `loadOrders(tabIndex, 1)` 刷新当前 tab - 与之前购物车页 `useDidShow` 刷新是同一模式 ## 商品详情页底部购物车角标(2026-07-14) ### 需求 `pages/shop/product-detail` 底部操作栏的购物车入口需要显示当前购物车商品数量角标。 ### 修改 - **`src/pages/shop/product-detail.tsx`**: 1. 从 `useCartContext` 额外解构 `totalCount`、`refresh` 2. 引入 `useDidShow`,页面显示时(已登录)调 `refresh()` 拉取最新购物车,保证角标准确 3. 购物车图标外包一层 `relative` 容器,`totalCount > 0` 时右上角显示红色圆角角标(>99 显示 `99+`),样式与商城列表页浮动购物车角标一致 ## 在线客服功能修复(2026-07-14) ### 问题 `pages/user/customer-service/index` 在线客服功能使用不了,用户已在微信后台添加客服人员。 ### 根因(多个问题叠加) 1. **`openType="contact"` 按钮缺少关键属性**:缺少 `sessionFrom`(会话来源标识)、`onContact`(客服回调)、`showMessageCard`/`sendMessageTitle`(客服消息卡片),且按钮被 Tailwind `border-0` 类可能干扰原生渲染 2. **开发者工具限制**:`open-type="contact"` 在微信开发者工具中不生效,只能在真机上使用,页面缺少提示 3. **自定义聊天系统消息发送缺失 `conversationId`**:`addShopChatMessage` 调用未传 `conversationId`,消息无法关联到会话;`ShopChatMessage` 模型本身也缺少该字段 4. **消息发送缺失 `formUserId`**:发送人 ID 未设置 5. **`conv.lastMessage` 字段不存在**:`ShopChatConversation` 模型只有 `content` 字段,代码引用了不存在的 `lastMessage` 6. **`useReachBottom` 用错场景**:页面使用 `ScrollView` 组件,`useReachBottom` 是页面级滚动钩子,对 ScrollView 无效,应改用 `onScrollToLower` 7. **`scrollToBottom` ref 设了但从未使用**:死代码 ### 修改 1. **`src/api/shop/shopChatMessage/model/index.ts`**: - `ShopChatMessage` 接口新增 `conversationId?: number` - `ShopChatMessageParam` 接口新增 `conversationId?: number` 2. **`src/pages/user/customer-service/index.tsx`**(全面修复): - 微信客服按钮补全 `sessionFrom="customer_service"`、`onContact`、`showMessageCard`、`sendMessageTitle` - 按钮样式从 Tailwind 类改为 inline style(避免 `border-0` 干扰原生按钮),显式设置 `border: 'none'` - 添加"需真机预览测试"提示文字 - `addShopChatMessage` 调用补全 `conversationId` 和 `formUserId`(从 `Taro.getStorageSync('UserId')` 获取) - `conv.lastMessage` 全部改为 `conv.content` - 会话列表更新 `lastMessage: text` 改为 `content: text` - 移除 `useReachBottom`,改用 ScrollView `onScrollToLower` + `lowerThreshold={50}` - 移除未使用的 `scrollToBottom` ref - `pageShopChatMessage` 参数去掉 `as any` 断言(模型已支持 `conversationId`) - 新增 `getCurrentUserId()` 辅助函数 - `handleScrollToLower` 增加 `showHistory` 条件守卫(仅展开历史时才加载更多) 3. **`src/components/NavBar/index.tsx`**(关键修复,导致白屏): - Taro 4.1.11 没有 `useNavigate` hook,原代码 `import { useNavigate } from '@tarojs/taro'` 运行时为 `undefined` - 改为 `import Taro from '@tarojs/taro'`,回退逻辑用 `Taro.navigateBack({ delta: 1 })` 4. **删除未注册的死代码**: - `src/pages/customer-service.tsx`(重复且仍引用 `useReachBottom`,干扰编译/热更新) - `src/pages/customer-service.config.ts` ### 验证 - 全局搜索确认无其他 `useNavigate` 和 `useReachBottom` 引用 - TypeScript 检查修改文件无错误