# 项目长期记忆（websopy-taro）

## 核心约定

### 开发者身份的表示方式
- **开发者身份主校验位 = `sys_user.is_developer` 布尔位**（数据库列 `is_developer`），登录态 `MainController.loginByDeveloperSms` 校验它才允许开发者登录。
- 后端仓库：`/Users/gxwebsoft/JAVA/com.gxwebsoft.core`（单 Maven 模块，包名 `com.gxwebsoft` 分包）。
- 标记接口：`PUT /api/system/user/developer/{userId}`（`UserController.setDeveloper` → `UserService.markAsDeveloper`），成功后经 `syncMessageProducer` 同步 websopy。
- 前端调用：`@/api/system/user` 的 `setUserDeveloper(userId)`。
- **vip-review 审核通过流程（用户 2026-07-22 最终确认，2026-07-23 补充）**：前端 `handleApprove` 依次调 ① `updateShopDealerApply(applyStatus=20)`；② `setUserDeveloper(userId)`（不碰 `sys_user_role`）；③ **`addShopStoreUser`**——在当前门店下给申请人补一条 `shop_store_user`（店员）记录（`userId=申请人, storeId=审核店员所属门店, tenantId=同店员, isDelete=0`）。后端 `markAsDeveloper` 做两件事：① 把 `sys_user.is_developer` 置 `1`；② **替换**用户角色为 `developer`——`roleService.getByRoleCode(roleCode=developer, tenantId=用户租户)` 找到角色 → 删掉该用户现有 `sys_user_role` → 只绑定 developer 这一个角色，**不与原角色并存**（角色替换包 try/catch，失败不影响 isDeveloper 落库）。即"升级为开发者 = 单角色 developer + isDeveloper 标记位 + 一条门店店员记录"，不是新增第二个角色。审核店员的 `storeId/tenantId` 来自 `getMyClerk()` 返回值（存于 `clerkStoreId/clerkTenantId` state）。
- 注意：前端 `vip-review` / `vip-upgrade` 等页面的变量名仍残留 `VIP_ROLE_*`、`pendingVipCount`、`dealerPrice` 等旧标识符（仅文案改成了「开发者」），后端角色与缓存 key 仍对应这些旧名，重命名需谨慎。

### 超级管理员（isSuperAdmin=true）访问门槛（2026-07-23 确认）
- 用户对象字段 `isSuperAdmin`（`(user as any)?.isSuperAdmin === true`，布尔 true 才算），前端此前未引用，由后端 `sys_user` 返回。
- **平台管理**（`pages/admin/index/index`）与**门店中心**（`pages/store/center/index`）两个入口，**菜单与首页工作台均只在 `isSuperAdmin===true` 时显示**（原先平台管理是 `isAdmin`、门店中心是店员 `storeInfo`）。
- 门店中心页面已改为「仅超级管理员可进」：移除原 `getMyClerk()` 店员门槛；非超管访问弹「仅超级管理员可访问」并 `navigateBack`；超管若同时是店员仍会加载门店信息用于头部，否则头部兜底显示「门店中心」+ 管理员昵称/手机号。
- 两页都用 `useUser()` 的 `loading`（userLoading）做首屏门卫，避免超管用户未从 storage 解析完时先闪「无访问权限」。注意：`pages/store/center` 的入口曾依赖 `user.tsx` 里的 `storeInfo`（getMyClerk），现已不再用 `storeInfo` 决定菜单可见性。

### 后端代码风格（com.gxwebsoft.core）
- Controller 继承 `BaseController`，统一返回 `ApiResult`（成功 `code=0`，失败 `code=1`），用 `success()/fail()`。
- 「按 userId 置一个标志位」的范式：`new User(){{setUserId(); setXxx();}}` → `userService.updateById(u)`（参考 `updateStatus`/`updateRecommend`/`auditUser`）。
- 用户变更后如需同步 websopy，经 `syncMessageProducer.sendUserSyncMessage("websopy","UPDATE",user)`（在 `UserServiceImpl` 内，`@Autowired(required=false)` 注入）。
- 用户相关代码都在 `com.gxwebsoft.common.system`（entity/service/controller），业务应用数据在 `com.gxwebsoft.websopy`（无 Controller）。

## ⚠️ Taro 小程序 nutui-react-taro 兼容性陷阱（2026-07-23 确认）

**问题**：`@nutui/nutui-react-taro@2.7.4` 的 **Cell / Price / Divider / Button** 等展示类组件，内部用 `React.createElement("div", ...)` 构建布局。Taro 4 的 babel/swc 没有把这些 `div` 转成 `view`，而 `dist/base.wxml` 里完全没有 `<div>` 模板，导致这些组件在 weapp 中"空壳化"——外层元素不存在，内层内容（`<div>`）被丢弃。

**症状**：组件位置完全空白 / 按钮不可见 / 价格显示为空。Text/View 与 `@nutui/icons-react-taro` 的图标（Tips/Check/Close/Clock，基于 SVG）渲染正常。

**解决办法**（最小改动）：在该页面用 Taro 原生组件替代：
| nutui（不可用） | Taro 原生 / 项目自定义 |
|---|---|
| `import { Button, Cell, Price, Divider } from '@nutui/nutui-react-taro'` | `import { Button } from '@tarojs/components'` + `import Price from '@/components/common/Price'` |
| `<Cell title="X" description={Y} />` | `<View className="flex justify-between px-4 py-3 border-b border-gray-100"><Text className="text-gray-600">X</Text><Text className="text-gray-800">{Y}</Text></View>` |
| `<Price price={n} size="large" thousands />` | `<Price price={n} size="large" />`（**用项目自定义 Price**，不用 nutui 的） |
| `<Divider />` | 用 `border-b border-gray-100` 替代 |
| `<Button type="primary" block loading={x} onClick={y}>...</Button>` | `<Button loading={x} onClick={y} className="flex-1 bg-blue-500 text-white rounded-full border-0">...</Button>`（className 控制颜色，`loading` prop 仍然支持） |

**适用范围判断**：项目里其它页面（qr-login、register、passport/forget 等）也用了 nutui 的 Cell/Card/Divider/Button，遇到同类"组件消失"现象时按同样模式替换即可。已修：`src/passport/pay/index.tsx`（2026-07-23，扫码进入小程序后支付页空白的 bug）。

**验证手段**：跑 `npm run build:weapp` 后在 `dist/<page>/index.js` 里 grep `nut-cell|nut-button|nut-divider|nut-price`，应为 0；grep `l.zx`（Taro Button 在 weapp 编译后的局部变量名）应出现 2 次（取消 + 立即支付）。

**已知的 nutui 组件清单**（按 2026-07-23 grep 结果）：`Button` 在 `passport/pay, passport/login/setting/forget/register, pages/order/*, pages/booking/*, components/common/{FixedButton,EmptyState}` 等大量页面；`Cell` 在 `passport/pay, pages/order/order.tsx`；`Divider` 在 `passport/pay, passport/qr-login`；`Price` 在 `passport/pay`。修复顺序建议：pay（已修）→ 视觉影响最大的页面 → 批量。
