- 移除登录页和注册页 `useDidShow` 中主动调用 `ensurePrivacyAuthorized()` 预检的逻辑 - 登录页不再主动弹出隐私协议弹窗,由微信在用户点击手机号登录按钮时强制触发授权 - 门店订单管理页面调用 `chooseProofImage` 前新增 `ensurePrivacyAuthorized()` 预检,确保上传凭证时授权 - 修改隐私弹窗文案为更通用描述,避免误导用户以为是相册授权 - 保留全局 `onNeedPrivacyAuthorization` 通过 `PrivacyModal` 处理,适配微信基础库强制机制 - 统一门店商品管理上传使用已内置预检的 `uploadFile()` 接口调用流程
104 lines
11 KiB
Markdown
104 lines
11 KiB
Markdown
# 项目长期记忆
|
||
|
||
## 项目概述
|
||
- 项目名:xinlong-shop-taro(Taro 微信小程序商城)
|
||
- 技术栈: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 state,isVip 变化触发重渲染
|
||
- **已接入 `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,待修
|