Files
xinlong-shop-taro/.workbuddy/memory/2026-07-15.md
赵忠林 af645c058e fix(shop): 修复首页VIP价格显示及后端价格字段问题
- 首页index.tsx中接入useVipStatus,修复热销推荐价格未显示dealerPrice的问题
- 新增getHotDisplayPrice函数,VIP时显示dealerPrice及划线原价,并添加VIP金色角标
- 后端OrderCreateRequest.OrderGoodsItem增加price字段及校验,解决后端DTO缺失price字段问题
- 重构OrderBusinessService中价格校验逻辑,确保正确优先级及SKU库存校验
- 确认价格字段约定,前端传dealerPrice给后端下单
- Maven编译通过,后端需重启服务生效
2026-07-15 14:50:06 +08:00

160 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 线下付款下单失败排查
- 文件:`src/pages/shop/checkout.tsx``src/api/shop/shopOrder/index.ts`
- 现象结算页选择线下付款payType=9下单时报错`创建支付订单失败创建微信支付订单失败支付配置中应用ID为null或空`
- 原因:前端传参正确,但后端 `POST /shop/shop-order` 在 payType=9 时仍尝试创建微信支付订单,因未配置 appId 失败
- 前端处理:
- `checkout.tsx` 增加线下付款支付配置类错误的友好提示
- `createOrder` 返回类型放宽为 `WxPayResult | null`,兼容非微信支付场景
- 后端修复(`/Users/gxwebsoft/JAVA/guilixu-java`3 个文件):
1. `OrderBusinessService.java``createOrder()` 中 Integer 比较从 `==` 改为 `.equals()`,避免装箱陷阱导致 payType=8/9 拦截失效;微信支付分支前加日志
2. `ShopOrderServiceImpl.java``createWxOrder()` 方法开头增加 payType 守卫,非微信支付(1/102)直接抛异常,不走 JSAPI 分支
3. `ShopOrderController.java``saveLegacy()` 方法增加 payType 检查,货到付款/线下付款/余额支付跳过 createWxOrder
- 验证Maven compile 成功,无编译错误
- 注意:后端服务需重启才能生效
## 结算页支付方式调整
- 文件:`src/pages/shop/checkout.tsx`
- 注释掉货到付款(id=8)选项,只保留线下付款(id=9)
- 默认支付方式从 8(货到付款) 改为 9(线下付款)
## 门店订单修改金额功能
- 文件:`src/pages/store/orders/index.tsx`
- 新增 OpType `'editPrice'`,在订单卡片操作区增加橙色「修改金额」按钮
- 仅对可操作订单(非已完成/非已关闭)显示
- 弹窗包含当前实付展示、新金额输入digit、修改原因必填 textarea
- 提交调用 `updateShopOrder({ orderId, payPrice, comments })`,金额变更记录追加到 comments
- 复用现有 `updateShopOrder` APIPUT `/shop/shop-order`),无需新增接口
## 修复 VIP 会员价格显示和下单问题
- **根因**`useVipStatus` hook 已定义但从未被任何页面使用,所有页面直接调用 `isVipMember()` 读取同步缓存。该缓存仅在「个人中心」页刷新,导致 VIP 会员访问商品详情/购物车/结算页时缓存为 stale false不显示 dealerPrice
- **修复文件**(共 9 个):
1. `src/api/shop/shopOrder/model/index.ts``OrderGoodsItem` 新增 `price?: string` 字段,下单时传 VIP 单价
2. `src/pages/shop/product-detail.tsx` — 引入 `useVipStatus`,用响应式 `isVip` 替换 `isVipMember()`
3. `src/pages/shop/checkout.tsx` — 引入 `useVipStatus``goodsPrice` useMemo 依赖加 `isVip`;下单时 `OrderGoodsItem``price` 字段
4. `src/pages/shop/index.tsx` — 引入 `useVipStatus`,替换 `isVipMember()`
5. `src/pages/shop/category.tsx` — 引入 `useVipStatus`,替换 `isVipMember()`
6. `src/pages/shop/cart.tsx` — 引入 `useVipStatus`,替换 `isVipMember()`
7. `src/contexts/CartContext.tsx` — CartProvider 引入 `useVipStatus``calcPrice` 用响应式 `isVip`
8. `src/components/business/SkuSelector/index.tsx` — 引入 `useVipStatus`,替换 `isVipMember()`
9. `src/components/common/ProductCard/index.tsx` — 引入 `useVipStatus`,替换 `isVipMember()`
- **原理**`useVipStatus` 在组件挂载时异步调用 `checkAndCacheVipStatus` 查询后端并更新缓存+React statestate 变化触发重渲染,使所有价格显示/计算自动更新为 dealerPrice
- 验证:`npx taro build --type weapp` 构建成功
## 门店订单修改金额 — 修改原因保存字段变更
- 文件:`src/pages/store/orders/index.tsx``src/api/shop/shopOrder/model/index.ts`
- 变更:修改金额时的"修改原因"从追加到 `comments` 字段改为保存到 `merchantRemarks`(商户备注)字段
- `ShopOrder` 接口新增 `merchantRemarks?: string` 字段
- `submitEditPrice``updateShopOrder` 传参从 `comments` 改为 `merchantRemarks`
## 订单备注字段分离buyerRemarks / merchantRemarks / comments
- 文件:`src/api/shop/shopOrder/model/index.ts``src/pages/shop/checkout.tsx``src/pages/store/orders/index.tsx`
- `ShopOrder``OrderCreateRequest` 接口均新增 `buyerRemarks?: string`(买家备注)
- 结算页 `checkout.tsx`:订单备注从 `comments: remarks` 改为 `buyerRemarks: remarks`
- 门店订单列表 `store/orders/index.tsx`:订单卡片新增买家备注展示块(橙色背景,收货信息下方)
- 字段约定:`buyerRemarks`=买家下单备注,`merchantRemarks`=商户修改金额原因,`comments`=系统/其他备注
## 修复线下付款订单状态显示
- 文件:`src/components/common/OrderCard/index.tsx``src/pages/order/list.tsx``src/pages/order/detail.tsx`
- 问题:线下付款(payType=9)下单成功后,订单列表和详情页显示"线下付款·待确认",用户认为状态不对
- 修复内容:
1. OrderCard `getCardStatus`:将线下付款(payType=9)与货到付款(payType=8)统一处理,`!payStatus && !isCod && !isOffline` 才显示"待付款",否则直接按 deliveryStatus 判断,线下付款订单显示"待发货"
2. OrderCard `showCloseButton`:增加 `!order.payStatus` 条件,已付款订单不再显示取消按钮(需到详情页退款)
3. OrderCard 按钮文案:从"关闭订单"改为"取消订单",与详情页一致
4. list.tsx `handleCloseOrder`:弹窗标题改为"确认取消订单"toast 改为"订单已取消"
5. detail.tsx `getOrderDisplayStatus`:线下付款未确认时显示"待发货"蓝色subtitle 保留付款引导文案
6. detail.tsx `getOrderPhase`:保留 `offline_pending` 阶段,底部按钮仍为"取消订单+联系客服"(未确认收款可取消,非退款)
## 修复地址编辑页智能识别 bug
- 文件:`src/pages/user/address-edit.tsx`
- 问题:粘贴带标签的地址文本(如「收件人: 赵忠林 / 手机号码: 137... / 所在地区: 广西... / 详细地址: 大学东路...」)识别后:
1. 姓名识别失败(之前只看文本开头的 2-4 个汉字,姓名不在开头)
2. 详细地址保留了「收件人」「手机号码」「所在地区」「详细地址」等标签文字
- 修复 `parseAddressText`
1. 姓名优先匹配 `(?:收件人|收货人|姓名|联系人)[:]\s*([\u4e00-\u9fa5]{2,4})`,找不到再回退到 `^[\u4e00-\u9fa5]{2,4}`
2. 详细地址提取前先 strip 标签文字(收件人/手机号/所在地区/详细地址 + 中英冒号),再 strip 省市区,最后 strip 零散标签
- 验证:用 Python 模拟Python 是用 JS 同款正则+RegionData解析截图中的输入
- name: 赵忠林 ✓
- phone: 13737128880 ✓
- province/city/region: 广西壮族自治区/南宁市/西乡塘区 ✓
- address: 西乡塘街道大学东路9号瀚林御景3栋1单元1101号房 ✓(街道+详细地址完整保留)
- 项目 type-check (`npx tsc --noEmit`) 无报错
## 修复默认地址唯一性 bug
- 问题:新增/编辑地址时勾选"设为默认"后,后端直接保存 `isDefault=true`,不清除其他地址的默认状态,导致出现多个默认地址
- 后端修复(`/Users/gxwebsoft/JAVA/guilixu-java`3 个文件):
1. `ShopUserAddressService.java` — 新增 `clearDefault(userId, excludeId)` 接口方法
2. `ShopUserAddressServiceImpl.java` — 实现 `clearDefault`:查询该用户所有 `isDefault=true` 的地址(可排除指定 id批量设为 false
3. `ShopUserAddressController.java``save()` 新增时若 `isDefault=true` 先调 `clearDefault(userId, null)``update()` 编辑时若 `isDefault=true` 先调 `clearDefault(userId, id)` 排除当前地址
- 前端修复(`src/pages/user/address-edit.tsx`
- 保存成功后若 `isDefault=true`,再调一次 `setDefaultAddress(savedId)` 触发后端清理逻辑(双重保险)
- 新增时从返回结果中提取 savedId
- 修复 `getShopUserAddress` 返回值类型断言(`as ShopUserAddress`
- 前端渲染兜底(`src/hooks/useAddress.ts`
- `loadAddresses` 中加 `ensureSingleDefault` 工具函数:若后端返回多个 `isDefault=true`,只保留 createTime 最早的为默认,其余置 false
- 后端 Maven 编译命令:`"/Applications/IntelliJ IDEA Ultimate.app/Contents/plugins/maven/lib/maven3/bin/mvn" compile`guilixu 的 mvnw 损坏,用 IntelliJ 自带 Maven
- 编译结果:成功无报错
- 注意:之前误改了 paopao-java 和 websopy-java 两个项目(已废弃),正确后端是 guilixu-java
## 用户商品浏览记录功能(全栈实现)
- 需求:后台记录用户浏览商品的痕迹
- 方案:去重累加模式(同一用户+商品只留一条visit_count 累加5分钟内去重防刷
- 后端(`/Users/gxwebsoft/JAVA/guilixu-java`7 个文件 + 1 SQL
1. `sql/shop_goods_browse.sql` — 建表 SQL含3个索引uk_user_goods 唯一 / idx_user_time / idx_goods_time
2. `ShopGoodsBrowse.java` — Entity参照 ShopGoodsFavorite 范式,含 visitCount/lastVisitTime/browseSource + 关联商品快照字段)
3. `ShopGoodsBrowseParam.java` — 查询参数merchantId 用 Long 与 BaseParam 一致,含 startTime/endTime 时间范围)
4. `ShopGoodsBrowseMapper.java` + XML — Mapper关联 shop_goods 表查询商品名称/图片/价格)
5. `ShopGoodsBrowseService.java` + Impl — Service核心 recordBrowse 去重累加逻辑查已有→5分钟内忽略/超过则累加→首次插入)
6. `ShopGoodsBrowseController.java` — ControllerPOST 上报 / GET page 我的足迹 / DELETE 删单条 / DELETE 清空 / GET admin/page 后台查询)
- 前端(`xinlong-shop-taro`4 个文件):
1. `src/api/shop/shopGoodsBrowse/model/index.ts` — ShopGoodsBrowse + ShopGoodsBrowseParam 类型
2. `src/api/shop/shopGoodsBrowse/index.ts` — reportBrowse(静默)/pageBrowseHistory/deleteBrowseRecord/clearBrowseHistory
3. `src/pages/shop/product-detail.tsx` — addToHistory() 已登录时异步调 reportBrowse 上报
4. `src/pages/user/history-list/index.tsx` — 改为服务端分页(登录)/ 本地兜底(未登录),支持删除单条、清空、上拉加载
5. `src/pages/user/user.tsx` — 恢复浏览历史入口(取消注释)
- 编译验证:后端 Maven compile 成功;前端 tsc --noEmit 无新增类型错误
- 编译踩坑BaseController.success() 有 success(IPage<T>) 重载,泛型方法返回类型用 ApiResult<?> 避免 fail() 分支类型不兼容
- 待执行:数据库需手动执行 `shop_goods_browse.sql` 建表
- 后续可选:后台管理页面(`/Users/gxwebsoft/VUE/guilixu-admin`Vue3+AntD Vue
## 后台管理端浏览记录页面guilixu-admin
- 后端补充:`ShopGoodsBrowseController` 新增 `DELETE /admin/{id}` 管理端删除接口(不限定用户),原 `DELETE /{id}` 限定当前用户
- 前端文件(`/Users/gxwebsoft/VUE/guilixu-admin`4 个文件):
1. `src/api/shop/shopGoodsBrowse/model/index.ts` — ShopGoodsBrowse + ShopGoodsBrowseParam 类型
2. `src/api/shop/shopGoodsBrowse/index.ts` — pageShopGoodsBrowse(调 /admin/page) + removeShopGoodsBrowse(调 /admin/{id})
3. `src/views/shop/shopGoodsBrowse/index.vue` — 页面ele-pro-table 表格ID/用户ID/商品图片/商品名称/价格/浏览次数/浏览来源/最后浏览时间/首次浏览/操作删除)
4. `src/views/shop/shopGoodsBrowse/components/search.vue` — 搜索栏用户ID/商品ID/浏览来源/时间范围)
- 管理后台菜单机制后端动态菜单sys_menu 表),前端按 `component` 字段约定加载 `src/views/{component}/index.vue`
- 菜单配置:后台"系统管理 → 菜单管理"新增component 填 `shop/shopGoodsBrowse`
- 注意:管理后台 API 用 `res.data.code`/`res.data.data`(非小程序端的 `res.code`request 传参用 `{ params }` 包裹
- 编译验证:后端 Maven compile 成功;前端 vue-tsc 无 shopGoodsBrowse 相关错误
- 待操作:后端需重新部署(新增了 admin 删除接口);管理后台需配置菜单
## 帮助中心联系客服入口改为直连微信客服
- 文件:`src/pages/user/help-center/index.tsx`
- 改动:点击「联系在线客服」不再跳转 `/pages/user/customer-service/index`,而是直接用 `<Button openType="contact">` 调起微信客服
- 关键属性:`openType="contact"` + `sessionFrom="help_center"` + `showMessageCard` + `sendMessageTitle="欢迎咨询"` + `onContact` 回调
- Button 样式重置height:auto / lineHeight:normal / padding:16px / border:none / borderRadius:8px保持原视觉白底圆角卡片
- 引入:从 `@tarojs/components` 新增导入 `Button`
- 注意:微信客服 Button 在开发者工具中无效,需真机预览测试
## 修复 VIP 价格首页不显示 + 后端不认 price 字段
- **问题 1首页**`src/pages/index/index.tsx`tabBar 首页)没接入 `useVipStatus`,热销推荐价格一直显示 `product.price` 而非 `dealerPrice`。商品详情/商城列表/购物车/分类都修了,唯独漏了 tabBar 首页
- **问题 2后端 DTO 缺字段)**:前端 `OrderGoodsItem` 已传 `price` 字段,但后端 `OrderCreateRequest.OrderGoodsItem`(嵌套静态类)**根本没有 `price` 字段**Jackson 反序列化直接丢弃。后端 `OrderBusinessService.validateAndCalculateTotal()``saveOrderGoods()` 都用 `goods.getPrice()` 兜底 —— 这就是「前端传的对、后端处理就错」的根本原因
- **修复3 个文件)**
1. `src/pages/index/index.tsx`
- 引入 `useVipStatus`,加 `const { isVip } = useVipStatus()`
-`getHotDisplayPrice()`VIP + 有 dealerPrice → `{price: dealerPrice, original: price}`,否则原逻辑
- Price 组件传 `price + original` + 旁边加金色「VIP」角标
2. `/Users/gxwebsoft/JAVA/guilixu-java/src/main/java/com/gxwebsoft/shop/dto/OrderCreateRequest.java`
- `OrderGoodsItem` 内部类加 `private BigDecimal price;` 字段 + `@DecimalMin("0")` 校验
3. `/Users/gxwebsoft/JAVA/guilixu-java/src/main/java/com/gxwebsoft/shop/service/OrderBusinessService.java`
- `validateAndCalculateTotal()``saveOrderGoods()` 两处重构:先取 sku无论价格来源都要校验 sku 合法性+取库存),再用「前端 price > sku.price > goods.price」优先级决定 actualPrice
- 注意不要把 sku 查询和 price 选择耦合在同一个 if-else 链里,否则 VIP+多规格商品会跳过 sku 库存校验
- **编译验证**Maven compile 成功无报错;后端需重启服务才能生效
- **价格字段约定(再确认)**
- `goods.price` - 到手价(主价格)
- `goods.salePrice` - 市场价(划掉的)
- `goods.dealerPrice` - VIP 专享价
- 前端传 `dealerPrice``OrderGoodsItem.price`,后端通过该字段下单