# PROGRESS — D-01 · 用户-租户 membership + 跨租户切换 - 状态:done > 日期 2026-09-13 · 范围:只改 `gj/dealerhub` 目录(后端 core + 前端 tenants/members/切换器) ## 设计(阶段一,任务卡要求先评审再动手——本轮设计与实现一致,评审记录在此) - 模型(存量复用):`TenantMembership(user, tenant, role[owner|admin|member], is_active, joined_at)`, 唯一约束 `(user, tenant)`;存量回填(0002 迁移:超管=全租户,普通=default)iter6 已验证。 - 授权矩阵: | 角色 | 切租户/看成员 | 邀请 | 改角色/禁用 | 移除 | |---|---|---|---|---| | owner | ✅ | ✅(任意角色) | ✅ | ✅(专属) | | admin | ✅ | ✅(仅 member) | ✅(仅 member) | ❌ | | member | ✅(自己有 membership 的) | ❌ | ❌ | ❌ | | 非成员 | 403 | 403 | 403 | 403 | | 未知租户 | 400(参数错误,非 403) | 400 | 400 | 400 | | 超管 | 直通(bootstrap 契约) | 直通 | 直通 | 直通 | - 切换流程:`POST /api/v1/tenants//switch/` 校验 membership → 返回 `{code,name,role}` + 写审计(`tenant_switch`);前端换 `localStorage.tenant` + 整页 reload(会话隔离无脏数据)。 - 兼容策略:存量 membership 不动;最后一个 owner 禁止移除/降级/禁用(服务端事务内强保)。 ## 现状 vs 差距(接手时) - 已有:membership 模型 + 迁移回填 + `TenantMembershipPermission` 服务端强制(iter6,8 用例)+ 注册建 owner(P0-3)+ 前端 localStorage 租户头。 - 缺口:无切换端点、无成员管理接口、无前端切换器/成员页/无权限页。 ## 改动 后端(`backend/apps/core/` + `backend/tests/`): 1. `tenants.py`(新建):`my_tenants`(我的租户+role)、`switch_tenant`(校验+审计)、 `tenant_members`(GET 列表 / POST 邀请:admin 仅 member,新用户≥8位密码事务创建)、 `tenant_member_detail`(PATCH 改角色/禁用 / DELETE 移除:admin 不动 owner/admin, 移除仅 owner,最后一个 owner 三禁)。 2. `urls.py`:四条路由挂 `core_urlpatterns`(即 `/api/v1/tenants/...`)。 3. `tests/test_tenant_switch.py`(新建,12 条):切换 200+role/无权 403/未知 400/mine 双租户/ member 邀 403/owner 邀 201+被邀可切/admin 邀 owner 403/admin 动 owner 403/ 跨租户成员页 403/删最后 owner 400/owner 删 member/禁用后资源 403。 前端(`frontend/src/`): 4. `api/tenants.js`(新建):mine/switchTo/members/invite/updateMember/removeMember。 5. `pages/Members.vue`(新建):成员表(角色/状态标签)+ 邀请 dialog(新老用户兼容)+ 改角色/禁用启用/移除(owner 专属按钮)+ 403 跳无权限页。 6. `pages/NoPermission.vue`(新建):无权限页 + 我的租户一键进入。 7. `layouts/MainLayout.vue`:租户 tag → 下拉切换器(`mine()` 列表,切后整页 reload)+ “成员”按钮 + 菜单加“租户成员”;`router/index.js` 加 `/members` + `/no-permission`。 ## 边界遵守 - 无计费/套餐;单据/库存业务零改;权限全在服务端(前端只做展示与跳转)。 ## 验证 ```bash pytest tests/test_tenant_switch.py tests/test_membership.py # 20 passed(12 新增 + 8 存量) pytest -p no:cacheprovider # 524 passed, 4 skipped(512 基线 + 12 新增) npm test # 17 pass / 0 fail npm run build # ✓ built in 6.13s manage.py makemigrations --check --dry-run # No changes detected manage.py check # 0 issues ``` ## 状态 done。遗留:切换端到端(前端下拉 → switch → reload 无脏数据)需 dev 服务联调时录屏补证据; demo token 流不走 membership(既有旁路,未动)。