# 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`
