Files
Neo-ZQYY/docs/prd/Neo_Specs/review-audit/P10-NS4-02.md
Neo 6f8f12314f feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本
包含多个会话的累积代码变更:
- backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔
- admin-web: ETL 状态页、任务管理、调度配置、登录优化
- miniprogram: 看板页面、聊天集成、UI 组件、导航更新
- etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强
- tenant-admin: 项目初始化
- db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8)
- packages/shared: 枚举和工具函数更新
- tools: 数据库工具、报表生成、健康检查
- docs: PRD/架构/部署/合约文档更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 00:03:48 +08:00

5.9 KiB
Raw Permalink Blame History

P10-NS4-02门店切换功能的交互规范

简要结论

  • 状态:⚠️ 部分解决
  • 后端数据隔离和 JWT 门店权限体系完整;前端 SiteSelector 组件已实现但仅在维客线索页集成,全局布局未放置门店选择器,用户审核和 Excel 上传页面缺少门店筛选,门店名称显示为 ID 而非中文名。

详细审查

数据库端

auth.tenant_admins 表DDL: db/zqyy_app/migrations/2026-03-20__ns4_tenant_admin_tables.sql

字段 类型 说明
managed_site_ids BIGINT[] 管辖门店 ID 数组,用于数据隔离
tenant_id BIGINT 所属租户
is_active BOOLEAN 账号状态

managed_site_ids 为 PostgreSQL 数组类型,支持多门店管辖场景。

public.site_code_mapping 表(被后端多处引用)

用途 说明
site_code → site_id 映射 用户申请时通过球房编号关联门店
site_id → site_name 映射 用户列表、线索搜索时补充门店名称

门店名称映射表存在,后端已在 tenant_users.pytenant_clues.py 中使用。

结论:数据库层面完整支持多门店管辖和门店名称映射。

后端代码

1. 登录与 JWTtenant_auth.py

登录成功后返回的 JWT payload 包含 managed_site_ids 数组:

payload = {
    "sub": str(admin_id),
    "tenant_id": tenant_id,
    "managed_site_ids": managed_site_ids,  # ← 门店列表
    "aud": "tenant-admin",
}

refresh_token 也携带 managed_site_ids,刷新后不丢失。

2. 认证中间件(auth/tenant_admins.py

CurrentTenantAdmin dataclass 包含 managed_site_ids: list[int] site_filter_clause() 工具函数生成 site_id IN (...) SQL 片段,用于数据隔离。 verify_site_access() 校验单个 site_id 是否在管辖范围内,越权返回 403。 空 managed_site_ids 时生成 1 = 0 条件,不匹配任何行(安全兜底)。

3. 查询接口(tenant_users.py

list_applications — 通过 site_filter_clause 附加 site_id IN (管辖列表) 条件。 list_users — 同上JOIN user_site_roles 表做门店隔离。 approve_application / reject_application — 调用 verify_site_access 校验单门店权限。 edit_user — 新 site_id 必须在管辖范围内。

4. 维客线索(tenant_clues.pygrep 结果确认)

搜索接口支持 site_id 参数筛选。 返回结果补充 site_name(通过 site_code_mapping 查询)。

后端结论:数据隔离体系完整,所有查询均附加 site_id IN (管辖列表) 条件,符合 P10 spec 要求。

前端代码

1. SiteSelector 组件(components/SiteSelector/index.tsx

组件已实现,基于 Ant Design Select支持多选/全选。 配套 useSiteFilter hook管理选中状态提供 effectiveSiteIds(空选时返回全部)。 数据源来自 useAuth().user.managedSiteIds

2. useAuth hookhooks/useAuth.tsx

从 JWT payload 解析 managedSiteIds 数组。 登录后通过 extractUserInfo 提取并存入 React Context。 页面刷新时从 localStorage 恢复。

3. API 服务(services/api.ts

JWT 自动附加到请求头。 401 时自动刷新 token含并发保护⚠️ 无全局 site_id 拦截器——各页面需自行传递 site_id 参数。

4. App.tsx 全局布局

全局布局未集成 SiteSelector。侧边栏仅包含导航菜单和退出按钮,无门店选择器。 无全局 "当前选中门店" 状态管理(无 SiteContext/SiteProvider

5. 各页面集成情况

页面 SiteSelector 集成 site_id 传递 门店名称显示
维客线索 (RetentionClues) 已集成 单门店时传 site_id ⚠️ 显示 门店 {id}
用户管理 (UserManagement) 未集成 依赖后端 JWT 隔离 ⚠️ 显示 门店 {id}
用户审核 (UserApproval) 未集成 依赖后端 JWT 隔离 仅显示球房编号
Excel 上传 (ExcelUpload) 未集成 无门店筛选 无门店信息

6. 门店名称问题

⚠️ SiteSelector 选项显示为 门店 {id}(硬编码数字),未查询 site_code_mapping 获取真实门店名称。用户无法通过名称识别门店。

差距分析

P10 要求 当前状态 差距
租户级管理员管辖所有店铺 managed_site_ids 支持
店铺级管理员只管指定 site_id JWT + site_filter_clause
所有查询附加 site_id IN 条件 后端全部实现
门店选择器 UI ⚠️ 组件存在但未全局集成 仅维客线索页使用
切换门店后数据自动刷新 ⚠️ 维客线索页有效 其他页面无此机制
门店选择器位置 未定义全局位置 App.tsx 布局无选择器
门店名称可读性 显示 ID 而非名称 需查询 site_name

建议

  1. 全局门店选择器:在 App.tsxContent 区域顶部(或 Header放置 SiteSelector创建 SiteContext 全局管理当前选中门店,各页面通过 Context 消费。

  2. 门店名称映射:登录后或首次加载时调用后端接口获取 managed_site_ids → site_name 映射SiteSelector 选项显示真实门店名称(如"朗朗桌球·XX店")。

  3. 页面集成UserApproval、UserManagement、ExcelUpload 页面接入全局门店筛选API 请求携带 site_ids 参数。

  4. 切换刷新机制:门店切换时触发数据重新加载(可通过 useEffect 监听 selectedSiteIds 变化,或使用 React Query 的 queryKey 包含 site_ids 实现自动 refetch

  5. 后端补充:新增 GET /api/tenant/sites 接口,返回当前管理员管辖门店的 {site_id, site_name} 列表,供前端门店选择器使用。