Files
xinlong-shop-taro/.workbuddy/memory/MEMORY.md
赵忠林 ce1e2bd4dd refactor(privacy): 调整隐私授权触发逻辑,登录页取消主动预检
- 移除登录页和注册页 `useDidShow` 中主动调用 `ensurePrivacyAuthorized()` 预检的逻辑
- 登录页不再主动弹出隐私协议弹窗,由微信在用户点击手机号登录按钮时强制触发授权
- 门店订单管理页面调用 `chooseProofImage` 前新增 `ensurePrivacyAuthorized()` 预检,确保上传凭证时授权
- 修改隐私弹窗文案为更通用描述,避免误导用户以为是相册授权
- 保留全局 `onNeedPrivacyAuthorization` 通过 `PrivacyModal` 处理,适配微信基础库强制机制
- 统一门店商品管理上传使用已内置预检的 `uploadFile()` 接口调用流程
2026-07-16 01:40:16 +08:00

104 lines
11 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.

# 项目长期记忆
## 项目概述
- 项目名xinlong-shop-taroTaro 微信小程序商城)
- 技术栈Taro + React + TypeScript + Tailwind CSS
- 后端 API 前缀:`/shop/``/api/`
- **后端项目路径:`/Users/gxwebsoft/JAVA/guilixu-java`**(⚠️ 不要找 paopao-java 或 websopy-java那是旧的
- **后端 Maven 编译命令**`cd /Users/gxwebsoft/JAVA/guilixu-java && "/Applications/IntelliJ IDEA Ultimate.app/Contents/plugins/maven/lib/maven3/bin/mvn" compile`guilixu 的 mvnw 损坏,用 IntelliJ 自带的 mvn
- 门店店员身份判断:通过 `getMyClerk()` API (`/shop/shop-store-user/my`) 返回值判断
- VIP/分销商申请记录表:`ShopDealerApply`,状态码 10待审核/20通过/30驳回
## 用户状态与禁用机制
- 用户状态字段:`User.status``0` = 正常,`1` = 禁用
- 禁用接口:`updateUserStatus(userId, status)` → PUT `/system/user/status`
- 禁用拦截工具:`src/utils/auth.ts``isUserDisabled(user)``blockDisabledUser(user)`
- 拦截层级UserContext 启动异步校验 + refreshUser/syncFromStorage/loginUser 检查 + 登录 API 和登录页检查
- 用户管理页:`src/pages/store/users/index.tsx`(门店中心入口,支持搜索/筛选/查看详情/禁用启用)
## VIP 会员机制
- 用户在 `vip-upgrade` 页面提交门店名称和地址申请
- 门店店员在 `vip-review` 页面审核(通过/驳回)
- VIP 会员特权:商品显示 `dealerPrice` 价格,下单使用该价格结算
- VIP 状态判断:`isVipMember()` 从 localStorage 缓存读取(同步),`checkAndCacheVipStatus(userId)` 异步更新
- **响应式 VIP 状态**:使用 `useVipStatus` hook`src/hooks/useVipStatus.ts`),组件挂载时异步校验并更新缓存 + React stateisVip 变化触发重渲染
- **已接入 `useVipStatus` 的页面/组件**商品详情、结算、商城首页、分类页、购物车页、CartContext、SkuSelector、ProductCard
- `OrderGoodsItem` 接口新增 `price` 字段VIP 下单时传 dealerPrice 给后端
## 商品价格字段约定
- `product.price` - 到手价(主价格)
- `product.salePrice` - 市场价(划掉的)
- `product.dealerPrice` - VIP 会员专享价
- `product.memberStorePrice` - 会员价
## 关键文件位置
- VIP 工具:`src/utils/vip.ts`
- VIP Hook`src/hooks/useVipStatus.ts`
- VIP 审核页:`src/pages/user/vip-review/index.tsx`
- VIP 升级页:`src/pages/user/vip-upgrade/index.tsx`
- 门店中心:`src/pages/store/center/index.tsx`
- 购物车 API`src/api/shop/shopCart/index.ts`(批量删除接口 `/shop/shop-cart/batch` 后端期望直接接收 `ArrayList<Integer>``[1,2,3]`,不要用 `{ ids }` 包裹)
- 购物车 Context`src/contexts/CartContext.tsx`
- 购物车合并加购:后端 `ShopCartServiceImpl.addToCart()` 按 user_id+goods_id+sku_id 判重,已存在则累加 cart_num不存在才新增skuId 为 null/0单规格匹配 IS NULL 或 =0。`ShopCart.userId` 是 Integer非 Long。历史重复数据清理脚本 `sql/shop_cart_merge_duplicates.sql`
## 订阅消息
- 模板 ID`sh1K9iK7vZjebUNFu6OsMsnsJxm4whThWGrhN7I4zVg`(交易提醒)
- 字段:订单号(`character_string1`)、商品名称(`thing4`)、联系人(`thing10`)、联系电话(`phone_number7`)、送货地址(`thing8`)
- 场景说明:下单提醒
- 新订单检测 Hook`src/hooks/useNewOrderDetector.ts`30秒轮询页面隐藏暂停
## 微信隐私协议(基础库 3.16.1+ 强制)
- 报错特征:`chooseImage:fail api scope is not declared in the privacy agreement` / `getPhoneNumber:fail ... privacy ...` + `errno:112`
- 代码修复:
1. 调用 `Taro.chooseImage` / `chooseMedia` / `getPhoneNumber` 等敏感 API 前,先 `getPrivacySetting``requirePrivacyAuthorize` 预检(封装在 `src/api/system/file/index.ts``ensurePrivacyAuthorized()` 里,已 export。**注意**:不应在登录页 `useDidShow` 中主动预检,避免用户一进入登录页就弹框;应由微信在用户点击 `getPhoneNumber` 时自行触发 `onNeedPrivacyAuthorization`
2. `src/app.tsx``useLaunch` 中注册 `Taro.onNeedPrivacyAuthorization` 回调,并通过 `src/components/PrivacyModal` 展示隐私协议授权弹窗。弹窗内的 Button 必须设置 `open-type="agreePrivacyAuthorization"`,用户点击后触发 `onAgreePrivacyAuthorization``resolve({ event: 'agree', button: 'agree' })``Taro.showModal` 的按钮无法被微信识别为有效的隐私授权
3. 门店上传图片页面在调用 `chooseImage` 前主动预检:`src/pages/store/orders/index.tsx``chooseProofImage` 已加 `ensurePrivacyAuthorized()``src/pages/store/goods/index.tsx` 通过 `uploadFile()` 上传,`uploadFile()` 内部已预检。
- 手动配置:**小程序管理后台** → 设置 → 第三方设置 → 用户隐私保护指引 → 添加「开发者收集你的相册/摄像头」和「手机号」声明
- ⚠️ **`requiredPrivateInfos` 字段只接受位置类 API 白名单**chooseAddress/chooseLocation/choosePoi/getFuzzyLocation/getLocation/onLocationChange/startLocationUpdate/startLocationUpdateBackground不要加 `chooseImage`/`chooseMedia`/`getPhoneNumber` 等非位置类,会导致 app.json 解析失败
- 直接调 `Taro.chooseImage` 的页面:`pages/order/evaluate/index.tsx``pages/after-sale/apply/index.tsx``pages/store/orders/index.tsx` —— 这些页面绕过了 `uploadFile`,需要单独加 `ensurePrivacyAuthorized` 预检
- 直接调 `getPhoneNumber` 的页面:`passport/login.tsx``passport/register.tsx` —— 不再主动预检,依赖微信点击按钮时自动触发 `onNeedPrivacyAuthorization`;保留降级短信登录
- 隐私授权弹窗组件:`src/components/PrivacyModal/index.tsx`(全局单例,通过 `src/utils/privacy.ts` 管理显隐与 resolve
## 手机号授权登录降级方案2026-07-15 ~ 07-16 修复)
- **业务现实**`getPhoneNumber` 按钮被用户拒绝后,微信会短期"记住"用户的选择,再次点击会直接 fail`errMsg: getPhoneNumber:fail user deny`)。一旦进入这个状态,前端必须提供降级入口
- **统一模式**`passport/login.tsx``passport/register.tsx` 都有 `getPhoneNumber` 回调,**不允许**只 toast 一句"未授权手机号"就完事
- 必做:失败时调 `showPhoneAuthFailedModal(errMsg)`,按 errMsg 区分场景弹窗user deny / privacy / no permission / 通用)
- 弹窗带"短信登录"按钮自动跳 `/passport/sms-login`(透传 redirect + 邀请参数 inviter/source/t
- 必做:主登录按钮**长期可见**地提供"使用短信验证码登录"链接,不要依赖弹窗
- **接口地址统一**login/register 的手机号登录接口统一使用 `SERVER_API_URL + '/wx-login/loginByMpWxPhone'`register 之前误写 `https://shop-api.websoft.top`
- **短信登录页**`/passport/sms-login`(路由表里已有),调 `loginBySms` + `sendSmsCaptcha` 接口
- **已落地页面**`passport/login.tsx``passport/register.tsx`(隐私协议预检 + 统一 request + 降级弹窗)
## 分享 / 朋友圈能力2026-07-13 接入)
- 统一封装:`src/hooks/useShare.ts``useShare({title, path?, query?, imageUrl?, enableTimeline?, enableCopyUrl?})`,一次注册 `useShareAppMessage` + `useShareTimeline` + 可选 `onCopyUrl` 复制链接;自动在 path/query 追加 `inviter=${当前用户id}` 做裂变归因;`isTimelineSinglePage()` 判断朋友圈单页模式 scene===1154
- 海报组件:`src/components/SharePoster/index.tsx`Canvas 2D 绘制封面+标题+价格+小程序码,返回临时图路径作 imageUrl
- 小程序码接口:`src/api/share.ts` `generateShareCode(page, inviterId)` 复用 `generateMiniProgramCode`scene 用 `uid_${inviterId}`page 透传但后端需读取转发才生效)
- 裂变约定:分享链接 query 用 `inviter`,与 `src/utils/invite.ts``parseInviteParams` 读取字段一致
- 已接入页面:商品详情、积分商品、拼团、秒杀、活动、赛事(带 canvas 海报邀请下级、分销推广、升级VIP、分享赚佣金仅 enableShare + useShare无海报首页好友+朋友圈+复制链接三按钮全亮)
- 注意tabBar 页**也支持朋友圈分享**(之前误判已纠正)——只要 config 开 enableShareTimeline + 定义 onShareTimeline 即显示按钮;"tabBar 不渲染"仅指朋友圈打开后的单页模式里底部 tabBar 不显示。朋友圈/复制链接开发者工具可能看不到入口,必须真机测试。复制链接是 Beta基础库 2.14.3+)。
## 订单备注字段约定2026-07-15
- `buyerRemarks` — 买家备注用户下单时填写的订单备注checkout 页提交)
- `merchantRemarks` — 商户备注:门店修改金额时填写的原因记录
- `comments` — 系统/其他备注:保留给系统内部使用
- `ShopOrder``OrderCreateRequest` 接口均已包含 `buyerRemarks``merchantRemarks` 字段
## 用户浏览记录功能2026-07-15
- 存储模式:**去重累加**(同一用户+商品只留一条visit_count 累加5分钟内去重
- 数据库表:`shop_goods_browse`字段id/user_id/goods_id/tenant_id/merchant_id/visit_count/last_visit_time/browse_source/create_time/update_time
- 唯一索引:`uk_user_goods (user_id, goods_id)`;查询索引:`idx_user_time``idx_goods_time`
- 后端 API 路径:`/api/shop/shop-goods-browse`POST 上报 / GET page 我的足迹 / DELETE {id} 删单条 / DELETE 清空 / GET admin/page 后台查询)
- 前端 API`src/api/shop/shopGoodsBrowse/index.ts`reportBrowse 静默上报 / pageBrowseHistory / deleteBrowseRecord / clearBrowseHistory
- 商品详情页 `product-detail.tsx``addToHistory()` 已接入:已登录时异步调 reportBrowse未登录仅本地 browse_history
- 浏览历史页 `src/pages/user/history-list/index.tsx` 已改为服务端分页(登录),未登录回退本地存储
- 后端文件:`ShopGoodsBrowse.java`(entity) / `ShopGoodsBrowseParam.java` / `ShopGoodsBrowseMapper.java`+XML / `ShopGoodsBrowseService.java`+Impl / `ShopGoodsBrowseController.java`
- 建表 SQL`/Users/gxwebsoft/JAVA/guilixu-java/sql/shop_goods_browse.sql`
- 后台管理前端:`/Users/gxwebsoft/VUE/guilixu-admin`Vue3 + Ant Design Vue + Vite非 RuoYi
- 注意:`BaseController.success()``success(IPage<T>)` 重载,泛型方法返回类型用 `ApiResult<?>` 而非 `ApiResult<PageResult<T>>`(否则 fail() 分支类型不兼容)
## tabBar 配置2026-07-15
- 当前 4 个 tabBar`/pages/index/index`(首页)、`/pages/shop/index`(分类)、`/pages/shop/cart`(购物车)、`/pages/user/user`(我的)
- 「订单」(pages/order/list) 已从 tabBar 移除,改为普通页,所有进入订单列表的地方用 `navigateTo` 而非 `switchTab`
- 多处 tabBar 判断列表login/register/sms-login/auth-flow/user/index/index/index已统一为实际 4 个 tabBar 路径
- ⚠️ pages/user/index.tsx:140 仍用 `switchTab(points/index)`,积分页非 tabBar待修