## 线下付款下单失败排查
- 文件：`src/pages/shop/checkout.tsx`、`src/api/shop/shopOrder/index.ts`
- 现象：结算页选择线下付款（payType=9）下单时报错：`创建支付订单失败：创建微信支付订单失败：支付配置中应用ID为null或空`
- 原因：前端传参正确，但后端 `POST /shop/shop-order` 在 payType=9 时仍尝试创建微信支付订单，因未配置 appId 失败
- 前端处理：
  - `checkout.tsx` 增加线下付款支付配置类错误的友好提示
  - `createOrder` 返回类型放宽为 `WxPayResult | null`，兼容非微信支付场景
- 后端修复（`/Users/gxwebsoft/JAVA/guilixu-java`，3 个文件）：
  1. `OrderBusinessService.java` — `createOrder()` 中 Integer 比较从 `==` 改为 `.equals()`，避免装箱陷阱导致 payType=8/9 拦截失效；微信支付分支前加日志
  2. `ShopOrderServiceImpl.java` — `createWxOrder()` 方法开头增加 payType 守卫，非微信支付(1/102)直接抛异常，不走 JSAPI 分支
  3. `ShopOrderController.java` — `saveLegacy()` 方法增加 payType 检查，货到付款/线下付款/余额支付跳过 createWxOrder
- 验证：Maven compile 成功，无编译错误
- 注意：后端服务需重启才能生效

## 结算页支付方式调整
- 文件：`src/pages/shop/checkout.tsx`
- 注释掉货到付款(id=8)选项，只保留线下付款(id=9)
- 默认支付方式从 8(货到付款) 改为 9(线下付款)

## 门店订单修改金额功能
- 文件：`src/pages/store/orders/index.tsx`
- 新增 OpType `'editPrice'`，在订单卡片操作区增加橙色「修改金额」按钮
- 仅对可操作订单（非已完成/非已关闭）显示
- 弹窗包含：当前实付展示、新金额输入（digit）、修改原因（必填 textarea）
- 提交调用 `updateShopOrder({ orderId, payPrice, comments })`，金额变更记录追加到 comments
- 复用现有 `updateShopOrder` API（PUT `/shop/shop-order`），无需新增接口

## 修复 VIP 会员价格显示和下单问题
- **根因**：`useVipStatus` hook 已定义但从未被任何页面使用，所有页面直接调用 `isVipMember()` 读取同步缓存。该缓存仅在「个人中心」页刷新，导致 VIP 会员访问商品详情/购物车/结算页时缓存为 stale false，不显示 dealerPrice
- **修复文件**（共 9 个）：
  1. `src/api/shop/shopOrder/model/index.ts` — `OrderGoodsItem` 新增 `price?: string` 字段，下单时传 VIP 单价
  2. `src/pages/shop/product-detail.tsx` — 引入 `useVipStatus`，用响应式 `isVip` 替换 `isVipMember()`
  3. `src/pages/shop/checkout.tsx` — 引入 `useVipStatus`，`goodsPrice` useMemo 依赖加 `isVip`；下单时 `OrderGoodsItem` 传 `price` 字段
  4. `src/pages/shop/index.tsx` — 引入 `useVipStatus`，替换 `isVipMember()`
  5. `src/pages/shop/category.tsx` — 引入 `useVipStatus`，替换 `isVipMember()`
  6. `src/pages/shop/cart.tsx` — 引入 `useVipStatus`，替换 `isVipMember()`
  7. `src/contexts/CartContext.tsx` — CartProvider 引入 `useVipStatus`，`calcPrice` 用响应式 `isVip`
  8. `src/components/business/SkuSelector/index.tsx` — 引入 `useVipStatus`，替换 `isVipMember()`
  9. `src/components/common/ProductCard/index.tsx` — 引入 `useVipStatus`，替换 `isVipMember()`
- **原理**：`useVipStatus` 在组件挂载时异步调用 `checkAndCacheVipStatus` 查询后端并更新缓存+React state，state 变化触发重渲染，使所有价格显示/计算自动更新为 dealerPrice
- 验证：`npx taro build --type weapp` 构建成功

## 门店订单修改金额 — 修改原因保存字段变更
- 文件：`src/pages/store/orders/index.tsx`、`src/api/shop/shopOrder/model/index.ts`
- 变更：修改金额时的"修改原因"从追加到 `comments` 字段改为保存到 `merchantRemarks`（商户备注）字段
- `ShopOrder` 接口新增 `merchantRemarks?: string` 字段
- `submitEditPrice` 中 `updateShopOrder` 传参从 `comments` 改为 `merchantRemarks`

## 订单备注字段分离（buyerRemarks / merchantRemarks / comments）
- 文件：`src/api/shop/shopOrder/model/index.ts`、`src/pages/shop/checkout.tsx`、`src/pages/store/orders/index.tsx`
- `ShopOrder` 和 `OrderCreateRequest` 接口均新增 `buyerRemarks?: string`（买家备注）
- 结算页 `checkout.tsx`：订单备注从 `comments: remarks` 改为 `buyerRemarks: remarks`
- 门店订单列表 `store/orders/index.tsx`：订单卡片新增买家备注展示块（橙色背景，收货信息下方）
- 字段约定：`buyerRemarks`=买家下单备注，`merchantRemarks`=商户修改金额原因，`comments`=系统/其他备注

## 修复线下付款订单状态显示
- 文件：`src/components/common/OrderCard/index.tsx`、`src/pages/order/list.tsx`、`src/pages/order/detail.tsx`
- 问题：线下付款(payType=9)下单成功后，订单列表和详情页显示"线下付款·待确认"，用户认为状态不对
- 修复内容：
  1. OrderCard `getCardStatus`：将线下付款(payType=9)与货到付款(payType=8)统一处理，`!payStatus && !isCod && !isOffline` 才显示"待付款"，否则直接按 deliveryStatus 判断，线下付款订单显示"待发货"
  2. OrderCard `showCloseButton`：增加 `!order.payStatus` 条件，已付款订单不再显示取消按钮（需到详情页退款）
  3. OrderCard 按钮文案：从"关闭订单"改为"取消订单"，与详情页一致
  4. list.tsx `handleCloseOrder`：弹窗标题改为"确认取消订单"，toast 改为"订单已取消"
  5. detail.tsx `getOrderDisplayStatus`：线下付款未确认时显示"待发货"（蓝色），subtitle 保留付款引导文案
  6. detail.tsx `getOrderPhase`：保留 `offline_pending` 阶段，底部按钮仍为"取消订单+联系客服"（未确认收款可取消，非退款）

## 修复地址编辑页智能识别 bug
- 文件：`src/pages/user/address-edit.tsx`
- 问题：粘贴带标签的地址文本（如「收件人: 赵忠林 / 手机号码: 137... / 所在地区: 广西... / 详细地址: 大学东路...」）识别后：
  1. 姓名识别失败（之前只看文本开头的 2-4 个汉字，姓名不在开头）
  2. 详细地址保留了「收件人」「手机号码」「所在地区」「详细地址」等标签文字
- 修复 `parseAddressText`：
  1. 姓名优先匹配 `(?:收件人|收货人|姓名|联系人)[:：]\s*([\u4e00-\u9fa5]{2,4})`，找不到再回退到 `^[\u4e00-\u9fa5]{2,4}`
  2. 详细地址提取前先 strip 标签文字（收件人/手机号/所在地区/详细地址 + 中英冒号），再 strip 省市区，最后 strip 零散标签
- 验证：用 Python 模拟（Python 是用 JS 同款正则+RegionData）解析截图中的输入：
  - name: 赵忠林 ✓
  - phone: 13737128880 ✓
  - province/city/region: 广西壮族自治区/南宁市/西乡塘区 ✓
  - address: 西乡塘街道大学东路9号瀚林御景3栋1单元1101号房 ✓（街道+详细地址完整保留）
- 项目 type-check (`npx tsc --noEmit`) 无报错

## 修复默认地址唯一性 bug
- 问题：新增/编辑地址时勾选"设为默认"后，后端直接保存 `isDefault=true`，不清除其他地址的默认状态，导致出现多个默认地址
- 后端修复（`/Users/gxwebsoft/JAVA/guilixu-java`，3 个文件）：
  1. `ShopUserAddressService.java` — 新增 `clearDefault(userId, excludeId)` 接口方法
  2. `ShopUserAddressServiceImpl.java` — 实现 `clearDefault`：查询该用户所有 `isDefault=true` 的地址（可排除指定 id），批量设为 false
  3. `ShopUserAddressController.java` — `save()` 新增时若 `isDefault=true` 先调 `clearDefault(userId, null)`；`update()` 编辑时若 `isDefault=true` 先调 `clearDefault(userId, id)` 排除当前地址
- 前端修复（`src/pages/user/address-edit.tsx`）：
  - 保存成功后若 `isDefault=true`，再调一次 `setDefaultAddress(savedId)` 触发后端清理逻辑（双重保险）
  - 新增时从返回结果中提取 savedId
  - 修复 `getShopUserAddress` 返回值类型断言（`as ShopUserAddress`）
- 前端渲染兜底（`src/hooks/useAddress.ts`）：
  - `loadAddresses` 中加 `ensureSingleDefault` 工具函数：若后端返回多个 `isDefault=true`，只保留 createTime 最早的为默认，其余置 false
- 后端 Maven 编译命令：`"/Applications/IntelliJ IDEA Ultimate.app/Contents/plugins/maven/lib/maven3/bin/mvn" compile`（guilixu 的 mvnw 损坏，用 IntelliJ 自带 Maven）
- 编译结果：成功无报错
- 注意：之前误改了 paopao-java 和 websopy-java 两个项目（已废弃），正确后端是 guilixu-java

## 用户商品浏览记录功能（全栈实现）
- 需求：后台记录用户浏览商品的痕迹
- 方案：去重累加模式（同一用户+商品只留一条，visit_count 累加，5分钟内去重防刷）
- 后端（`/Users/gxwebsoft/JAVA/guilixu-java`，7 个文件 + 1 SQL）：
  1. `sql/shop_goods_browse.sql` — 建表 SQL（含3个索引：uk_user_goods 唯一 / idx_user_time / idx_goods_time）
  2. `ShopGoodsBrowse.java` — Entity（参照 ShopGoodsFavorite 范式，含 visitCount/lastVisitTime/browseSource + 关联商品快照字段）
  3. `ShopGoodsBrowseParam.java` — 查询参数（merchantId 用 Long 与 BaseParam 一致，含 startTime/endTime 时间范围）
  4. `ShopGoodsBrowseMapper.java` + XML — Mapper（关联 shop_goods 表查询商品名称/图片/价格）
  5. `ShopGoodsBrowseService.java` + Impl — Service（核心 recordBrowse 去重累加逻辑：查已有→5分钟内忽略/超过则累加→首次插入）
  6. `ShopGoodsBrowseController.java` — Controller（POST 上报 / GET page 我的足迹 / DELETE 删单条 / DELETE 清空 / GET admin/page 后台查询）
- 前端（`xinlong-shop-taro`，4 个文件）：
  1. `src/api/shop/shopGoodsBrowse/model/index.ts` — ShopGoodsBrowse + ShopGoodsBrowseParam 类型
  2. `src/api/shop/shopGoodsBrowse/index.ts` — reportBrowse(静默)/pageBrowseHistory/deleteBrowseRecord/clearBrowseHistory
  3. `src/pages/shop/product-detail.tsx` — addToHistory() 已登录时异步调 reportBrowse 上报
  4. `src/pages/user/history-list/index.tsx` — 改为服务端分页（登录）/ 本地兜底（未登录），支持删除单条、清空、上拉加载
  5. `src/pages/user/user.tsx` — 恢复浏览历史入口（取消注释）
- 编译验证：后端 Maven compile 成功；前端 tsc --noEmit 无新增类型错误
- 编译踩坑：BaseController.success() 有 success(IPage<T>) 重载，泛型方法返回类型用 ApiResult<?> 避免 fail() 分支类型不兼容
- 待执行：数据库需手动执行 `shop_goods_browse.sql` 建表
- 后续可选：后台管理页面（`/Users/gxwebsoft/VUE/guilixu-admin`，Vue3+AntD Vue）

## 后台管理端浏览记录页面（guilixu-admin）
- 后端补充：`ShopGoodsBrowseController` 新增 `DELETE /admin/{id}` 管理端删除接口（不限定用户），原 `DELETE /{id}` 限定当前用户
- 前端文件（`/Users/gxwebsoft/VUE/guilixu-admin`，4 个文件）：
  1. `src/api/shop/shopGoodsBrowse/model/index.ts` — ShopGoodsBrowse + ShopGoodsBrowseParam 类型
  2. `src/api/shop/shopGoodsBrowse/index.ts` — pageShopGoodsBrowse(调 /admin/page) + removeShopGoodsBrowse(调 /admin/{id})
  3. `src/views/shop/shopGoodsBrowse/index.vue` — 页面（ele-pro-table 表格，列：ID/用户ID/商品图片/商品名称/价格/浏览次数/浏览来源/最后浏览时间/首次浏览/操作删除）
  4. `src/views/shop/shopGoodsBrowse/components/search.vue` — 搜索栏（用户ID/商品ID/浏览来源/时间范围）
- 管理后台菜单机制：后端动态菜单（sys_menu 表），前端按 `component` 字段约定加载 `src/views/{component}/index.vue`
- 菜单配置：后台"系统管理 → 菜单管理"新增，component 填 `shop/shopGoodsBrowse`
- 注意：管理后台 API 用 `res.data.code`/`res.data.data`（非小程序端的 `res.code`），request 传参用 `{ params }` 包裹
- 编译验证：后端 Maven compile 成功；前端 vue-tsc 无 shopGoodsBrowse 相关错误
- 待操作：后端需重新部署（新增了 admin 删除接口）；管理后台需配置菜单


## 帮助中心联系客服入口改为直连微信客服
- 文件：`src/pages/user/help-center/index.tsx`
- 改动：点击「联系在线客服」不再跳转 `/pages/user/customer-service/index`，而是直接用 `<Button openType="contact">` 调起微信客服
- 关键属性：`openType="contact"` + `sessionFrom="help_center"` + `showMessageCard` + `sendMessageTitle="欢迎咨询"` + `onContact` 回调
- Button 样式重置：height:auto / lineHeight:normal / padding:16px / border:none / borderRadius:8px，保持原视觉（白底圆角卡片）
- 引入：从 `@tarojs/components` 新增导入 `Button`
- 注意：微信客服 Button 在开发者工具中无效，需真机预览测试

## 订单详情页查看物流改造（显示 shopOrderDelivery）
- 需求：订单详情页点击「查看物流」跳转 `/pages/order/logistics`，原页面强制要求 URL 带 expressNo + expressCompany，但详情页只传了 orderId，导致 toast「缺少物流参数」+ 空状态
- 后端修复（`/Users/gxwebsoft/JAVA/guilixu-java`，1 个文件）：
  - `ShopOrderServiceImpl.java` — `getByIdRel(orderId)` 原本只调 `baseMapper.selectListRel(param)`，没有像 `pageRel` 那样填充 `shopOrderDelivery`。新增私有方法 `fillShopOrderDelivery(order)`，在快递配送(deliveryType==0/null)且已发货(deliveryStatus>10)时调 `shopOrderDeliveryService.getByOrderId(orderId)` 查发货单，并关联 `shopExpressService.getById(expressId)` 取 expressName 填充
  - 编译验证：Maven compile 成功
- 前端类型补充（`src/api/shop/shopOrder/model/index.ts`）：
  - 新增 `ShopOrderDelivery` 接口（与后端 ShopOrderDelivery 实体对应：deliveryId/orderId/deliveryMethod/expressId/expressName/sendName/sendPhone/sendAddress/expressNo/createTime 等）
  - `ShopOrder` 接口新增 `shopOrderDelivery?: ShopOrderDelivery | null` 字段
- 前端物流页改造（`src/pages/order/logistics.tsx`）：
  - 不再强制要求 URL 带 expressNo/expressCompany；只要求 orderId
  - 调 `getShopOrder(orderId)` 加载订单（含后端填充的 shopOrderDelivery）
  - 运单号优先级：URL expressNo → delivery.expressNo → order.expressNo
  - 物流轨迹：仅在有 expressNo + expressCompany 代码时才查询 queryLogistics，失败不阻塞发货单展示
  - 新增「发货信息」卡：发货人/联系方式/发货地址/物流单号(可复制)/发货时间/发货方式(10手动/20无需物流/30电子面单)
  - 新增「收货信息」卡：从订单读 realName/mobile/address
  - 保留原物流轨迹时间线（仅在查询到时展示）和温馨提示
  - 空状态：订单接口也失败时才显示
- 后端需重启服务才生效

## 修复 VIP 价格首页不显示 + 后端不认 price 字段
- **问题 1（首页）**：`src/pages/index/index.tsx`（tabBar 首页）没接入 `useVipStatus`，热销推荐价格一直显示 `product.price` 而非 `dealerPrice`。商品详情/商城列表/购物车/分类都修了，唯独漏了 tabBar 首页
- **问题 2（后端 DTO 缺字段）**：前端 `OrderGoodsItem` 已传 `price` 字段，但后端 `OrderCreateRequest.OrderGoodsItem`（嵌套静态类）**根本没有 `price` 字段**，Jackson 反序列化直接丢弃。后端 `OrderBusinessService.validateAndCalculateTotal()` 和 `saveOrderGoods()` 都用 `goods.getPrice()` 兜底 —— 这就是「前端传的对、后端处理就错」的根本原因
- **修复（3 个文件）**：
  1. `src/pages/index/index.tsx`
     - 引入 `useVipStatus`，加 `const { isVip } = useVipStatus()`
     - 加 `getHotDisplayPrice()`：VIP + 有 dealerPrice → `{price: dealerPrice, original: price}`，否则原逻辑
     - Price 组件传 `price + original` + 旁边加金色「VIP」角标
  2. `/Users/gxwebsoft/JAVA/guilixu-java/src/main/java/com/gxwebsoft/shop/dto/OrderCreateRequest.java`
     - `OrderGoodsItem` 内部类加 `private BigDecimal price;` 字段 + `@DecimalMin("0")` 校验
  3. `/Users/gxwebsoft/JAVA/guilixu-java/src/main/java/com/gxwebsoft/shop/service/OrderBusinessService.java`
     - `validateAndCalculateTotal()` 和 `saveOrderGoods()` 两处重构：先取 sku（无论价格来源都要校验 sku 合法性+取库存），再用「前端 price > sku.price > goods.price」优先级决定 actualPrice
     - 注意不要把 sku 查询和 price 选择耦合在同一个 if-else 链里，否则 VIP+多规格商品会跳过 sku 库存校验
- **编译验证**：Maven compile 成功无报错；后端需重启服务才能生效
- **价格字段约定（再确认）**：
  - `goods.price` - 到手价（主价格）
  - `goods.salePrice` - 市场价（划掉的）
  - `goods.dealerPrice` - VIP 专享价
  - 前端传 `dealerPrice` 到 `OrderGoodsItem.price`，后端通过该字段下单

## 修复订单列表"待付款"Tab 下状态显示"待发货"
- 文件：`src/components/common/OrderCard/index.tsx`
- 问题：用户切到"待付款"Tab，下方订单卡片状态文字显示"待发货"，与 Tab 不一致
- 根因：线下付款(payType=9)订单的 `payStatus=false`（数据库 pay_status=0），后端 `statusFilter=0` 的 SQL 条件 `pay_status=0 AND order_status=0` 把它归入"待付款"Tab 是合理的（待商家确认收款）。但 OrderCard 的 `getCardStatus` 因 `isOffline=true` → `!isOffline=false` → 跳过"待付款"分支，进入 `if (deliveryStatus === 10)` → 显示"待发货"
- 关键业务规则：
  - 货到付款(8)：后端 Controller `saveLegacy()` 下单时已 `setPayStatus(true)`，不会进入 statusFilter=0
  - 线下付款(9)：保持 `payStatus=false`，会进入 statusFilter=0
  - 微信支付(1)：未付款 `payStatus=false`，正常进入 statusFilter=0
- 修复：`getCardStatus` 第 30 行去掉 `&& !isOffline` 条件，改为 `if (!payStatus && !isCod) return { text: '待付款' }`。保留 `!isCod`（货到付款的特殊兜底，虽然正常流程不会走到这里）；清理掉不再使用的 `isOffline` 变量
- 决策依据：用户认可"线下付款订单出现在待付款 Tab"是合理的，因此只改前端文案判断，不动后端 SQL

## 修复购物车同一商品并列显示（应合并数量）
- 现象：购物车页面同一商品并列显示多行，本应合并数量到一行
- 根因：后端 `ShopCartController.save()` 直接调用 MyBatis-Plus 默认 `shopCartService.save()`，每次加购都 INSERT 新行，没有"同用户+商品+SKU已存在则累加数量"逻辑
- 后端修复（`/Users/gxwebsoft/JAVA/guilixu-java`，3 个文件）：
  1. `ShopCartService.java` — 接口新增 `boolean addToCart(ShopCart shopCart)` 方法
  2. `ShopCartServiceImpl.java` — 实现 `addToCart`：按 user_id+goods_id+sku_id 查询是否已存在（skuId 为 null/0 时匹配 IS NULL 或 =0），存在则累加 cart_num 后 updateById，不存在才 save；引入 `QueryWrapper`
  3. `ShopCartController.java` — `save()` 接口从 `shopCartService.save(shopCart)` 改为 `shopCartService.addToCart(shopCart)`
- 注意：`ShopCart.userId` 是 `Integer` 类型（不是 Long），`goodsId` 是 Long
- 历史数据清理：提供 `sql/shop_cart_merge_duplicates.sql`，按 (user_id,goods_id,sku_id) 分组合并 cart_num 并删除重复行（sku_id 可能为 NULL，用 `<=>` null-safe 匹配）；执行前先备份
- 编译验证：Maven compile 成功
- 后端需重启服务才生效

## 购物车加购报 spec_info 字段缺失
- 现象：添加购物车时报 `Unknown column 'spec_info' in 'field list'`
- 原因：后端实体 `ShopCart.java` 定义了 `specInfo` 字段（存储"颜色:红色|尺寸:L"这类规格信息），但数据库表 `shop_cart` 未建 `spec_info` 列；`ShopCartParam`、`OrderBusinessService` 均在用该字段，属设计需保留
- 修复：新建迁移脚本 `/Users/gxwebsoft/JAVA/guilixu-java/sql/shop_cart_add_spec_info.sql`，`ALTER TABLE shop_cart ADD COLUMN spec_info VARCHAR(500) NULL ... AFTER spec`，需在数据库执行

## 修复登录页「未授权手机号」无降级入口
- 现象：用户反复反馈"又又又登录不了了"，截图显示点击「手机号快捷登录」按钮后 toast 弹「未授权手机号」，用户被困在登录页
- 根因：`src/passport/login.tsx` 第 162-173 行 `onGetPhoneNumber` 回调里 `detail.code` 为 undefined（用户拒绝授权，errMsg 含 `user deny`/`fail`）时只 toast 了一句生硬文案，没有任何引导。但项目里其实有 `/passport/sms-login` 短信登录页（且在 `app.config.ts` 路由表里），`login.tsx` 却完全没暴露这个入口。`register.tsx` 同样有此问题，且其 weapp 环境下隐藏了短信按钮（原 `{!isWeapp && ...}` 判断）
- 修复（2 个文件 + 1 个 scss）：
  1. `src/passport/login.tsx`
     - 新增 `goSmsLogin()` 函数：透传 `redirect` + 邀请参数（`inviter`/`source`/`t`）跳 `/passport/sms-login`
     - 新增 `showPhoneAuthFailedModal(errMsg)`：按 errMsg 区分场景弹窗引导
       - `user deny` → "您已取消微信手机号授权，可改用短信验证码登录"
       - `privacy` → "微信未授权获取手机号，可改用短信验证码登录"
       - `no permission` → "小程序暂未获得手机号授权，可改用短信验证码登录"
       - 其他 → 通用"请尝试使用短信验证码登录"
       - 弹窗带"短信登录"按钮自动跳 sms-login，"我知道了"关闭
     - 失败分支从 `Taro.showToast` 改为 `showPhoneAuthFailedModal(errMsg)`
     - 主登录按钮下方新增长期可见的「使用短信验证码登录」链接（仿 register.tsx 的 goSmsLogin）
  2. `src/passport/login.scss`
     - 新增 `.login-methods__alt` / `.login-methods__alt-link` 样式（白色下划线小字，居中）
  3. `src/passport/register.tsx`
     - 同步加 `showPhoneAuthFailedModal(errMsg)` 弹窗引导
     - 去掉 `{!isWeapp && <Button>短信验证码注册/登录</Button>}` 包裹，让 weapp 下也始终显示短信按钮作为降级入口
- 类型检查：`npx tsc --noEmit` 无报错
- 业务规则：项目当前仅信任微信一键登录作为主路径，短信登录一直是兜底通道。后续如发现用户量大，可考虑直接在登录页主流程就两个按钮并排（不再让微信登录独占）

## sms-login 页面 UI 重构
- 原状：截图显示基本是裸的——只看到两个孤零零的 placeholder 文字，发送验证码按钮和登录按钮都看不见。NutUI 的 `Input` + `Button` 在 Taro 小程序上经常渲染不出边框/背景，外层 `flex justify-between` 也没把验证码按钮挤出来
- 改造（2 个文件）：
  1. `src/passport/sms-login.tsx` — 弃用 NutUI 改用 Taro 原生 `View/Text/Input`，重做整页布局
     - 渐变背景 + 浮动光圈（和 login.tsx 同一套色系 `#667eea → #764ba2`）
     - 顶部圆形 logo（140rpx 白底圆角 + 阴影）+ 标题 + 副标题
     - 白色卡片输入区（圆角 28rpx + 阴影）：手机号（📞）+ 灰线分隔 + 验证码（🔐 + 绿色胶囊「获取验证码」按钮）
     - 绿色渐变登录大按钮（圆角 50rpx + 阴影 + 字间距 4rpx）
     - 「← 返回微信快捷登录」白色下划线链接（一键 navigateBack 回到 login.tsx，避免用户被困）
     - 底部协议 + 服务协议/隐私政策超链接
     - 业务逻辑（state、60s 倒计时、handleSendCode/handleLogin 校验、redirect 跳转、邀请参数透传）全部保留
  2. `src/passport/sms-login.scss`（新文件）— 配套样式：渐变背景、浮动光圈动画（gradientMove + float）、卡片毛玻璃、按钮 hover 效果（scale 0.98）、分阶段入场动画（slideDown / slideUp / fadeIn）
- 类型检查：`npx tsc --noEmit` 无报错（项目原本的 `@tarojs/taro` 类型错误与本次无关）
- 编译验证：`npx taro build --type weapp` 14.89s 成功
- 设计稿预览：`.workbuddy/sms-login-preview.html`（按 375×812 真机尺寸渲染的 mockup，可直接浏览器打开看）
- 业务规则：login/register/sms-login 三个页面已统一视觉风格（同一套渐变 + 圆形 logo + 大字标题 + 白色卡片），后续新增 passport 类页面照搬这一套即可

## 修复首页轮播图只显示一张的问题
- 文件：`src/pages/index/index.tsx`
- 现象：后台一个广告位下配置了 3 张图片，但小程序首页轮播只显示第一张
- 根因：原代码把每个 `CmsAd` 作为一个 `SwiperItem`，图片只取 `item.imageList?.[0]?.url`，所以无论后台配几张都只渲染首图
- 修复：
  - 新增 `bannerSlides`：用 `flatMap` 把每个广告位下的 `imageList` 全部展开成独立轮播项，key 用 `adId-uid`（无 uid 则 fallback 到索引）
  - 保留 `item.image` 单图兜底逻辑，兼容只配单张图片的老数据
  - 渲染 `SwiperItem` 改为遍历 `bannerSlides`，点击跳转仍使用对应广告位的 `path`
- 验证：代码逻辑走查通过；项目原有 `npx tsc --noEmit` 的 `@tarojs/taro` 类型定义错误与本次修改无关

## 首页轮播图高度改为按后台 width/height 自适应
- 文件：`src/pages/index/index.tsx`
- 原状：`Swiper` 的 `height` 固定写死 `160px`，与后台广告位配置脱节
- 修复：
  - 新增 `bannerWrapWidth` 状态，页面渲染后用 `Taro.createSelectorQuery().select('.index-banner-wrap').boundingClientRect()` 获取容器实际宽度
  - 新增 `bannerHeight`（useMemo）：取第一个带有效 `width/height` 的广告位，按 `容器宽度 * (height / width)` 计算高度；无值或解析失败则 fallback `160px`
  - 轮播容器增加 `index-banner-wrap` 类名用于选择器查询
  - `Swiper` 和「暂无轮播图」占位区的高度都改用 `bannerHeight`
- 注意：宽度仍由容器 `w-full` + `mx-3` 边距控制，不直接套用后台 `width` 像素值；高度按比例缩放适配不同屏幕



## 修复「我的」页订单数量统计全为 0 的 bug
- 文件：`src/api/shop/shopUserCard/index.ts`、`src/api/shop/shopUserCard/model/index.ts`、`src/pages/user/user.tsx`
- 根因：前后端字段名不匹配
  - 后端 `/api/user/orders/stats` 返回 `UserOrderStats` DTO 字段：`total/waitPay/waitDeliver/waitVerify/waitReceive/waitComment/completed/refund/deleted/canceled`（参见 `com.gxwebsoft.shop.dto.UserOrderStats`）
  - 前端 `getUserOrderStats()` 却读取 `pending/paid/shipped/completed`，导致 `pending/paid/shipped` 永远 undefined → 兜底为 0；只有 `completed` 字段名巧合一致才有数
- 附带 bug：user.tsx「我的订单」快捷入口的 `status` 值写的是 0/1/2/3，与 `OrderListStatus`（0待付款 1待发货 3待收货 5已完成）不一致，点击「待收货」「已完成」会跳到订单列表页错误的 tab
- 修复：
  1. `UserOrderStats` 接口字段与后端 DTO 完全对齐（含 total/waitPay/waitDeliver/waitVerify/waitReceive/waitComment/completed/refund/deleted/canceled）
  2. `getUserOrderStats()` 改读后端真实字段，并用 `Number(d.x) || 0` 兜底
  3. user.tsx 订单快捷入口 count 改用 `waitPay/waitDeliver/waitReceive/completed`，status 改为 `0/1/3/5` 与 `OrderListStatus` 对齐
  4. localStorage 缓存 key 从 `user_order_stats_v2` 升级到 `v3`，让旧格式缓存自动失效，避免冷启动先渲染旧格式导致仍是 0
- 关键约定：`OrderListStatus` 是前端订单 Tab 筛选状态的唯一真相源（-1全部/0待付款/1待发货/2待核销/3待收货/4待评价/5已完成/6退款/7已删除），所有跳订单列表的入口传 status 都要对齐它

## 优化「我的订单」角标样式（2026-07-15 22:55）
- 文件：`src/pages/user/user.tsx`
- 用户反馈：截图中「已完成」下方出现了一个"0"字（hot reload 之前旧版代码里 `<View>{item.count}</View>` 直接渲染了 count，0 也会显示）。同时希望统计数字更"右上角"
- 修改：
  1. 角标位置 `top-0 right-2` → `-top-1 -right-1`：让小红点紧贴 cell 右上角外缘，更像标准徽标
  2. 角标尺寸 `text-xs` + 自由拉伸 → `min-w-[16px] h-4` + 10px/12px 字体：单数字也保持圆形，多位数字自动变椭圆
  3. 0 判断 `item.count && item.count > 0` → `item.count && Number(item.count) > 0`：用 `Number()` 包一层，防止后端返回字符串 "0" 时 `> 0` 误判

## 抽取通用 Badge 组件（2026-07-15 23:10）
- 新文件：`src/components/common/Badge/index.tsx`
- 用途：替代项目里多处复制粘贴的内联角标 View（user.tsx / index/index.tsx / store/center/index.tsx 都有 `min-w-[16px] h-4 px-1 bg-red-500 rounded-full` 这种重复写法）
- API：
  ```ts
  interface BadgeProps {
    count?: number | null   // 0 / null / undefined / 负数 → 不渲染
    max?: number            // 默认 99，超过显示 `${max}+`
    className?: string      // 外层定位 className，如 'absolute -top-1 -right-1'
    color?: string          // 默认 #ef4444（red-500）
    fontSize?: number       // 默认 10px
  }
  ```
- user.tsx 已替换：内联 `{Number(item.count) > 0 && <View ...>}` → `<Badge count={item.count} className='absolute -top-1 -right-1' />`
- 风格遵循项目 common 组件约定：函数式组件 + 默认导出 + Taro View/Text + Tailwind className
- 待办（未做）：首页 `src/pages/index/index.tsx:248` 和门店中心 `src/pages/store/center/index.tsx:218` 也有相同内联角标写法，后续可统一替换为 Badge

## 抽取通用 ArrowRight 组件（2026-07-15 23:13）
- 新文件：`src/components/common/ArrowRight/index.tsx`
- 用途：替代项目里到处可见的 `{'>'}` / `'>'` 文本箭头
- 实现：CSS V 形（border-top + border-right + rotate(45deg)），颜色用 `currentColor` 跟父级 `text-*` 类走
- API：
  ```ts
  interface ArrowRightProps {
    className?: string  // 颜色用 text-* 类（如 'text-gray-400'），间距用 ml-1 等
    size?: number       // V 形边长 px，默认 6
    thickness?: number  // 描边粗细 px，默认 1.5
  }
  ```
- user.tsx 已替换 3 处 `{'>'}`：
  1. 用户卡片头部箭头（白色 60% 透明）→ `<ArrowRight className='text-white text-opacity-60' />`
  2. 「全部订单 >」→ 拆成 `<View className='flex items-center text-xs text-gray-400'><Text>全部订单</Text><ArrowRight className='ml-1' /></View>`
  3. 菜单项末尾箭头（amber-700 / gray-300）→ `<ArrowRight className={...} />`
- 关键点：Tailwind 的 `text-opacity-60` 会通过 `--tw-text-opacity` 影响 color，currentColor 会继承这个带 opacity 的 color，所以 ArrowRight 的透明度也会跟随
- 待办（未做）：其它页面也有 `{'>'}` 文本箭头，后续可统一替换

## 统一替换 index/index.tsx 和 store/center 的内联角标为 Badge（2026-07-15 23:17）
- Badge 组件 API 扩展：新增 `size`（默认 16）和 `fontWeight`（默认 'normal'）参数；padding 从 className 的 `px-1` 改为 inline style 根据 size 自动计算（16→2px，20→3px），避免与调用方 className 冲突
- `src/pages/index/index.tsx:248`：数字角标（16x16/10px）→ `<Badge count={newOrderCount} className='absolute -top-1 -right-1' />`
  - 注意：保留了 `isClerk && newOrderCount > 0` 外层判断（虽然 Badge 内部 0 不渲染，但下面还有 `newOrderCount === 0` 的小红点逻辑，保留判断让语义更明确）
- `src/pages/store/center/index.tsx:218`：数字角标（20x20/11px/加粗）→ `<Badge count={getBadgeCount(card.key)} className='absolute -top-1 -right-1' size={20} fontSize={11} fontWeight='bold' />`
  - 简化了外层判断：`card.showBadge && getBadgeCount > 0` → `card.showBadge`（Badge 内部已处理 0 不渲染）
- 项目内联角标已全部统一为 Badge 组件，共 3 处（user.tsx / index/index.tsx / store/center/index.tsx）

## tabBar「订单」换「购物车」后的链接修正（2026-07-15 23:31）
- 背景：tabBar 原「订单」(pages/order/list) 改为「购物车」(pages/shop/cart)，order/list 不再是 tabBar 页
- A. 6 处 `switchTab(order/list)` → `navigateTo`（switchTab 只能跳 tabBar 页，否则失败）：
  - pages/shop/checkout.tsx:304（下单成功跳订单列表）
  - pages/user/user.tsx:144,149（handleOrderTabClick / handleViewAllOrders）
  - pages/user/index.tsx:158,175,190（订单概览数/全部订单/订单状态快捷入口）
- B. 4 处 tabBar 判断列表更新为实际 4 个 tabBar（index/shop/shop-cart/user）：
  - pages/user/index.tsx:11、pages/index/index.tsx:185（tabBarPages）
  - passport/login.tsx（tabBarUrls，顺带去掉非 tabBar 的 points/index）
  - passport/auth-flow.ts:12（TABBAR_URLS，原含错误路径 order/order）
- C. 2 处 isTabBarUrl 修正（原路径 cart/cart、category/index 不存在）：
  - passport/register.tsx:88、passport/sms-login.tsx:36
- D. pages/order/list.tsx:127 注释更新（去掉"tabBar 页"描述）
- 遗留（未改，预先存在）：pages/user/index.tsx:140 `switchTab(points/index)` 积分页非 tabBar，会失败
