Files
xinlong-shop-taro/.workbuddy/memory/2026-07-04.md
赵忠林 b07a3525db fix(user): 优化用户页面余额积分等数据加载体验
- useRequest 改为 stale-while-revalidate 机制,缓存命中时立即展示数据并后台刷新
- 增加本地存储持久化,冷启动时先展示存储数据,避免骨架屏
- user 页面使用 mutate 结合本地缓存即时更新界面
- 网络请求失败时保留缓存数据,仅清除 loading 状态
- 优化加载逻辑,保障 30 秒缓存内数据可快速响应且自动刷新
2026-07-04 15:34:46 +08:00

193 lines
9.4 KiB
Markdown
Raw Permalink 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.

# 2026-07-04 工作日志
## VIP会员审核功能开发
### 功能概述
为 xinlong-shop-taro 小程序新增了 VIP 会员审核功能,包含以下三个核心模块:
### 1. VIP会员审核菜单入口user.tsx
- 在"门店中心"下方添加了"VIP会员审核"菜单项
- 仅门店店员身份显示(与门店中心相同的 `storeInfo` 判断条件)
- 点击跳转到 `/pages/user/vip-review/index`
- 同时在页面加载、useDidShow、下拉刷新时调用 `checkAndCacheVipStatus` 更新当前用户的 VIP 状态缓存
### 2. VIP会员审核页面vip-review/index.tsx
- 新建页面 `src/pages/user/vip-review/index.tsx`
- 页面进入时验证当前用户是否为店员(通过 `getMyClerk()`
- 三个 Tab待审核(applyStatus=10)、已通过(20)、已驳回(30)
- 展示申请人的门店名称、门店地址、申请时间
- 店员可"通过"(设 applyStatus=20或"驳回"(设 applyStatus=30附带驳回原因
- 使用 `updateShopDealerApply` API 进行审核操作
- 与 vip-upgrade/index.tsx 配套:用户提交申请 → 店员实地考察 → 审核通过/驳回
### 3. VIP会员价格特权dealerPrice
- 创建了 VIP 状态工具 `src/utils/vip.ts`
- `isVipMember()` - 同步从缓存读取 VIP 状态
- `checkAndCacheVipStatus(userId)` - 异步检查并缓存(查询 ShopDealerApply applyStatus=20
- `getDisplayPrice(product)` - 获取商品对当前用户的实际价格
- 创建了 `src/hooks/useVipStatus.ts` Hook
- 修改了以下页面/组件支持 VIP 显示 dealerPrice
- `ProductCard` - 商品列表卡片价格
- `product-detail.tsx` - 商品详情页价格 + 立即购买传参
- `SkuSelector` - SKU 选择弹窗价格 + 确认时 fakeSku 价格
- `cart.tsx` - 购物车商品价格 + 推荐商品价格
- `CartContext.tsx` - `calcPrice` 计算总价时使用 dealerPrice
- `checkout.tsx` - 结算页金额计算 + 单价显示
### 4. 路由注册
-`app.config.ts` 中注册了 `pages/user/vip-review/index`
### 5. VIP升级页字段来源修正
- 修改 `vip-upgrade/index.tsx`
- 门店名称和门店地址不再从 `user` 对象读取,统一从 `shop-dealer-apply` 表最新记录读取
- 待审核(10) 和 已通过(20) 时只读展示;已驳回(30) 时允许编辑并重新提交
- TypeScript 类型检查通过
### 6. 审核通过时添加 VIP 角色
-`vip-review/index.tsx` 中新增了 `assignVipRole` 辅助函数
- 审核通过applyStatus=20自动调用 `addUserRole` 给申请人添加 `VIP会员` 角色roleId=2032, roleCode=vip
- 先调用 `listUserRole({ userId })` 检查用户是否已有 VIP 角色,避免重复绑定
- TypeScript 类型检查通过
### 7. 门店中心改造为仪表盘
-`store/center/index.tsx` 从订单管理页改造为功能入口仪表盘
- 新建 `store/orders/index.tsx`,将原订单管理逻辑完整搬入
- VIP会员审核入口从 `user.tsx` 独立菜单移除,统一放入门店中心仪表盘
- 门店中心仪表盘功能卡片支持扩展FEATURE_CARDS 数组,新增只需加一项)
- VIP会员审核卡片显示待审核数量角标红底白字
- TypeScript 类型检查通过
### 8. 门店中心顶部显示店员头像和门店名称
- 修改 `store/center/index.tsx` 顶部信息区
- 左侧显示圆形店员头像(读取 `storeInfo.avatar`),无头像时显示默认 👤 图标
- 右侧显示门店名称(`storeInfo.storeName`)和店员姓名 + 手机号(`storeInfo.name``storeInfo.phone`
- TypeScript 类型检查通过
### 涉及文件(补充)
- 修改:`src/pages/store/center/index.tsx`
### 9. 商品详情页价格下方 0 值隐藏
- 修改 `product-detail.tsx` 价格区域
-`dealerPrice``salePrice``memberStorePrice` 增加 `Number(value) > 0` 判断
- 0 或无效值时不再渲染,避免价格下方出现单独的 "0"
- TypeScript 类型检查通过
### 涉及文件(补充)
- 修改:`src/pages/shop/product-detail.tsx`
---
### 10. 新订单提醒功能(前端轮询 + 订阅消息)
#### 10.1 创建 useNewOrderDetector Hook
- 新建 `src/hooks/useNewOrderDetector.ts`
- 封装新订单轮询检测逻辑:
- 首次加载自动建立订单 ID 基准线,不触发提醒
- 后续轮询对比发现新订单后触发 `onNewOrders` 回调
- 页面隐藏时自动暂停轮询,显示时恢复
- 提供 `clearUnread()` 方法清除未读计数
- 导出类型:`NewOrderItem``UseNewOrderDetectorOptions``UseNewOrderDetectorReturn`
- 注册到 `src/hooks/index.ts`
#### 10.2 改造订单管理页(前端轮询)
- 修改 `src/pages/store/orders/index.tsx`
- 集成 `useNewOrderDetector`30 秒轮询,静默检测新订单
- 发现新订单时:震动提醒(`Taro.vibrateShort`+ Toast 提示("您有 N 条新订单"
- "全部" Tab 增加红色角标显示未读新订单数(>99 显示 99+
- 切换 Tab 时自动清除未读计数
#### 10.3 改造门店中心页
- 修改 `src/pages/store/center/index.tsx`
- 订单管理卡片增加待处理订单角标(查询 statusFilter=1 的 total
- VIP 审核角标同步增强,统一使用 `getBadgeCount()` 方法
- 底部新增"接收新订单提醒"卡片:
- 调用 `Taro.requestSubscribeMessage` 请求微信订阅消息授权
- 模板 ID 通过 `SUBSCRIBE_TMPL_IDS` 常量配置(待微信公众平台申请后填入)
- 授权成功后 Toast 提示
#### 待后端配合
- 微信公众平台申请订阅消息模板("新订单通知"类目)
- 将模板 ID 填入 `store/center/index.tsx``SUBSCRIBE_TMPL_IDS` 数组
- 后端在订单创建成功后调用 `subscribeMessage.send` API 向店员推送
#### 涉及文件
- 新建:`src/hooks/useNewOrderDetector.ts`
- 修改:`src/hooks/index.ts`
- 修改:`src/pages/store/orders/index.tsx`
- 修改:`src/pages/store/center/index.tsx`
---
### 11. 后端订阅消息发送服务
#### 11.1 新建 WxSubscribeMessageService
- 接口:`/JAVA/shop-api/src/main/java/com/gxwebsoft/shop/service/WxSubscribeMessageService.java`
- 实现:`/JAVA/shop-api/src/main/java/com/gxwebsoft/shop/service/impl/WxSubscribeMessageServiceImpl.java`
- 功能:客户下单后自动推送微信小程序订阅消息给门店所有店员
- 流程:
1. 根据 `storeId` 查询 `shop_store_user` 表获取店员 userId 列表
2. 批量查询 `sys_user` 表获取店员小程序 openid
3. 调用 `WxMiniappAccessTokenService` 获取 access_token
4. 调用微信 `subscribeMessage.send` API 逐个发送
- 容错设计:
- 订单无 storeId 跳过
- 门店无店员/店员无 openid 跳过
- access_token 获取失败跳过
- errcode 43101用户未授权订阅正常跳过不报错
- 所有异常不向上抛出,不影响订单创建
#### 11.2 集成到 OrderBusinessService
- 修改 `OrderBusinessService.createOrder()` 方法
- 在保存订单和商品后、创建微信支付订单前,调用 `sendNewOrderNotification`
- 外层 try-catch 确保通知失败不影响主流程
#### 涉及文件
- 新建:`JAVA/shop-api/.../service/WxSubscribeMessageService.java`
- 新建:`JAVA/shop-api/.../service/impl/WxSubscribeMessageServiceImpl.java`
- 修改:`JAVA/shop-api/.../service/OrderBusinessService.java`
---
### 12. 订阅消息模板 ID 填入 + 错误提示优化
- 模板 ID`sh1K9iK7vZjebUNFu6OsMsnsJxm4whThWGrhN7I4zVg`(交易提醒)
- 字段:订单号(`character_string1`)、商品名称(`thing4`)、联系人(`thing10`)、联系电话(`phone_number7`)、送货地址(`thing8`)
- 优化 `requestSubscribeMessage` fail 回调errCode 20001 提示模板未配置20002/20004 提示已取消
### 13. 门店中心页 loading 状态修复
- `store/center/index.tsx``useEffect` 遗漏 `.finally(() => setLoading(false))`,导致页面卡在"加载中"
- 已修复
### 14. 分类页改造为左侧分类 + 右侧商品列表布局
- 重写 `src/pages/shop/category.tsx`
- 左侧:一级分类列表(绿色高亮选中态,保持不变)
- 右侧:
- 排序栏:默认/销量/最新/价格(可切换升降序)/筛选
- 商品卡片:横向布局(左图 + 右侧标题/积分标签/价格/销量 + 加购按钮)
- 滚动触底分页加载
- 点击商品跳转详情页,点击加购按钮调用 `addToCart` API
- 复用现有 VIP 价格逻辑(`isVipMember` + `dealerPrice`
- 编译通过
### 15. 用户页余额/积分/优惠券/订单数加载优化
#### 问题分析
- `useRequest` 的缓存是内存级 `Map`,冷启动丢失 → 永远显示骨架屏
- 缓存命中后直接 `return` 不发起网络请求 → 30s 内数据不更新(注释说"静默刷新"但实际没做)
- 无 localStorage 持久化 → 每次打开页面都要等网络
#### 修复 useRequest 缓存为 stale-while-revalidate
- 修改 `src/hooks/useRequest.ts``doRequest` 方法
- 缓存命中时立即展示缓存数据loading=false**不 return**,继续发网络请求后台刷新
- 无缓存时:正常显示 loading skeleton
- 网络失败时:有缓存则保留缓存数据不清除,仅清 loading 状态
#### 添加 localStorage 持久化
- 修改 `src/pages/user/user.tsx`
- `useRequest` 增加 `onSuccess` 回调:请求成功后 `Taro.setStorageSync` 持久化
- `useDidShow``useEffect(isLoggedIn)` 中:先读 `Taro.getStorageSync` 并用 `mutate` 即时展示,再调用 `run` 后台刷新
- 效果:冷启动 0ms 展示上次数据 → 后台静默更新
#### 涉及文件
- 修改:`src/hooks/useRequest.ts`
- 修改:`src/pages/user/user.tsx`