- 配置文件 env.js 中新增 CMS_API_URL,三个环境均统一配置 - app.ts 导出 CmsBaseUrl,用于 cms-api 相关请求 - cmsAd 模块接口调用统一改为通过 CmsBaseUrl 拼接完整 cms-api 路径 - 幻灯片广告相关接口走独立 cms-api 域名,保证数据隔离 - 更新工作备忘录,补充 API 域名配置与幻灯片广告调用说明
12 KiB
12 KiB
项目长期记忆
项目概述
- 项目名: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) - API 域名配置(
config/env.js三环境一致):API_BASE_URL=https://shop-api.websoft.top/api、SERVER_API_URL=https://server.websoft.top/api、CMS_API_URL=https://cms-api.websoft.top/api;config/app.ts导出BaseUrl/ServerBaseUrl/CmsBaseUrl - 幻灯片广告(轮播图)调用已切到 cms-api:
src/api/cms/cmsAd/index.ts的读接口(listCmsAd/getCmsAd/getCmsAdByCode/pageCmsAd)通过cmsUrl()拼接CmsBaseUrl,首页轮播listCmsAd({ adType: 'banner', status: 0 })走https://cms-api.websoft.top/api/cms/cms-ad;写接口仍走 shop-api - 门店店员身份判断:通过
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 状态:使用
useVipStatushook(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 - 代码修复:
- 调用
Taro.chooseImage/chooseMedia/getPhoneNumber等敏感 API 前,先getPrivacySetting→requirePrivacyAuthorize预检(封装在src/api/system/file/index.ts的ensurePrivacyAuthorized()里,已 export)。注意:不应在登录页useDidShow中主动预检,避免用户一进入登录页就弹框;应由微信在用户点击getPhoneNumber时自行触发onNeedPrivacyAuthorization。 src/app.tsx的useLaunch中注册Taro.onNeedPrivacyAuthorization回调,并通过src/components/PrivacyModal展示隐私协议授权弹窗。弹窗内的 Button 必须设置open-type="agreePrivacyAuthorization",用户点击后触发onAgreePrivacyAuthorization再resolve({ event: 'agree', button: 'agree' });Taro.showModal的按钮无法被微信识别为有效的隐私授权- 门店上传图片页面在调用
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.tsgenerateShareCode(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,待修