Some checks failed
Build and Release / Build (push) Has been cancelled
- 新增家庭共享订阅管理 - 新增用户邀请统计 - 新增签名和订阅模式设置表单 - 更新 API 服务层和国际化文件 - UI 组件优化(enhanced-input、pro-table)
137 lines
5.7 KiB
Markdown
137 lines
5.7 KiB
Markdown
# 实施计划:后台管理 - 共享订阅显示
|
||
|
||
## 问题描述
|
||
|
||
设备组成员加入后,其原始订阅被删除,使用所有者的共享订阅。
|
||
在后台管理的用户订阅面板中,查看设备组成员的订阅时显示为空,因为数据已在合并时被删除。
|
||
需要在后台自动检测并显示共享订阅信息。
|
||
|
||
## 技术方案
|
||
|
||
**纯前端方案**,不需要后端 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 (
|
||
<div>
|
||
{isSharedView && <SharedSubscriptionBanner ownerUserId={ownerUserId} familyId={family.family_id} />}
|
||
<ProTable
|
||
data={displayData}
|
||
actions={isSharedView ? { render: () => [只读操作] } : { render: () => [完整操作] }}
|
||
...
|
||
/>
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
**关键变更点**:
|
||
- 将 ProTable 的 `request` 回调改为 React Query 管理数据获取
|
||
- 或者保持 ProTable request 模式,在外层用 state 管理共享视图切换
|
||
- 推荐方案:保持 ProTable 的 request 模式,但在 request 回调内部做链式检查
|
||
|
||
### Step 2: 添加共享订阅信息横幅
|
||
|
||
在 ProTable 上方显示提示信息:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ ℹ️ 该用户为设备组成员,当前显示所有者 (ID: 258) │
|
||
│ 的共享订阅。[查看设备组] [查看所有者] │
|
||
└─────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
- 使用 Alert 组件展示
|
||
- 提供跳转到设备组详情和所有者用户页面的链接
|
||
- 标题列后追加 `<Badge variant="secondary">共享</Badge>` 标识
|
||
|
||
### 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
|