# 实施计划:后台管理 - 共享订阅显示
## 问题描述
设备组成员加入后,其原始订阅被删除,使用所有者的共享订阅。
在后台管理的用户订阅面板中,查看设备组成员的订阅时显示为空,因为数据已在合并时被删除。
需要在后台自动检测并显示共享订阅信息。
## 技术方案
**纯前端方案**,不需要后端 API 变更。利用现有 API 组合实现:
1. `getUserSubscribe({ user_id })` → 获取用户自身订阅(可能为空)
2. `getFamilyList({ user_id, page: 1, size: 1 })` → 检测用户是否属于设备组
3. `getUserSubscribe({ user_id: owner_user_id })` → 获取所有者的共享订阅
**核心逻辑**:当用户自身订阅为空时,自动检查是否为设备组成员。若是非所有者成员,则展示所有者的订阅信息,并添加"共享订阅"视觉标识。
## 实施步骤
### Step 1: 修改 UserSubscription 组件
**文件**: `apps/admin/src/sections/user/user-subscription/index.tsx`
将组件从纯 ProTable 改为带有共享订阅检测逻辑的组件:
```
伪代码:
function UserSubscription({ userId }) {
// 1. 正常获取用户订阅
const { data: ownSubscriptions } = useQuery(getUserSubscribe({ user_id: userId }))
// 2. 当自身订阅为空时,检查设备组成员身份
const hasOwnSubscriptions = ownSubscriptions.list.length > 0
const { data: familyData } = useQuery(
getFamilyList({ user_id: userId, page: 1, size: 1 }),
{ enabled: !hasOwnSubscriptions } // 仅当订阅为空时触发
)
// 3. 判断是否为非所有者成员
const family = familyData?.list?.[0]
const isNonOwnerMember = family && family.owner_user_id !== userId && family.status === 'active'
const ownerUserId = family?.owner_user_id
// 4. 若为成员,获取所有者的订阅
const { data: sharedSubscriptions } = useQuery(
getUserSubscribe({ user_id: ownerUserId }),
{ enabled: isNonOwnerMember && !!ownerUserId }
)
// 5. 决定展示内容
const isSharedView = isNonOwnerMember && sharedSubscriptions?.list?.length > 0
const displayData = isSharedView ? sharedSubscriptions : ownSubscriptions
return (
{isSharedView &&
}
[只读操作] } : { render: () => [完整操作] }}
...
/>
)
}
```
**关键变更点**:
- 将 ProTable 的 `request` 回调改为 React Query 管理数据获取
- 或者保持 ProTable request 模式,在外层用 state 管理共享视图切换
- 推荐方案:保持 ProTable 的 request 模式,但在 request 回调内部做链式检查
### Step 2: 添加共享订阅信息横幅
在 ProTable 上方显示提示信息:
```
┌─────────────────────────────────────────────────────┐
│ ℹ️ 该用户为设备组成员,当前显示所有者 (ID: 258) │
│ 的共享订阅。[查看设备组] [查看所有者] │
└─────────────────────────────────────────────────────┘
```
- 使用 Alert 组件展示
- 提供跳转到设备组详情和所有者用户页面的链接
- 标题列后追加 `共享` 标识
### Step 3: 共享视图下禁用写操作
当处于共享订阅视图时:
- **隐藏** "添加订阅" 按钮(toolbar)
- **隐藏** "编辑" 按钮
- **隐藏** 删除、停止/恢复、重置令牌等破坏性操作
- **保留** 只读操作:查看日志、流量统计、在线设备等
### Step 4: 添加国际化翻译
**文件**:
- `apps/admin/public/assets/locales/zh-CN/user.json`
- `apps/admin/public/assets/locales/en-US/user.json`
新增翻译 key:
| Key | 中文 | 英文 |
|-----|------|------|
| `sharedSubscription` | 共享订阅 | Shared Subscription |
| `sharedSubscriptionInfo` | 该用户为设备组成员,当前显示所有者 (ID: {{ownerId}}) 的共享订阅 | This user is a device group member. Showing shared subscriptions from owner (ID: {{ownerId}}) |
| `viewDeviceGroup` | 查看设备组 | View Device Group |
| `viewOwner` | 查看所有者 | View Owner |
## 关键文件
| 文件 | 操作 | 说明 |
|------|------|------|
| `apps/admin/src/sections/user/user-subscription/index.tsx` | 修改 | 添加共享订阅检测与展示逻辑 |
| `apps/admin/public/assets/locales/zh-CN/user.json` | 修改 | 新增共享订阅相关中文翻译 |
| `apps/admin/public/assets/locales/en-US/user.json` | 修改 | 新增共享订阅相关英文翻译 |
## 风险与缓解
| 风险 | 缓解措施 |
|------|----------|
| 设备组 API 调用失败 | 捕获异常,静默降级为显示空列表(现有行为) |
| 所有者订阅也为空 | 正常显示空列表,不展示共享订阅横幅 |
| 用户同时有自身订阅和设备组成员身份 | 优先显示自身订阅(按描述,加入时会删除,不应同时存在) |
| 多个设备组 | 取第一个活跃的设备组即可(一个用户通常只属于一个组) |
## 边界情况
1. 用户无订阅 + 不在设备组 → 正常空列表
2. 用户无订阅 + 在设备组但为所有者 → 正常空列表(所有者自己订阅为空说明确实没有)
3. 用户无订阅 + 在设备组但组已禁用 → 正常空列表
4. 用户无订阅 + 在设备组且为活跃成员 → 显示所有者共享订阅 + 横幅提示
## SESSION_ID
- CODEX_SESSION: N/A(纯前端方案,未调用外部模型)
- GEMINI_SESSION: N/A