docs: 更新协议文档并清理过时计划文件
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
# Calendar Detail - Show Subscribers Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 在日历详情页显示已订阅此日历的用户列表
|
||||
|
||||
**Architecture:**
|
||||
- 后端:在 `GET /api/v1/schedule-items/{id}` 响应中返回 `subscribers` 列表
|
||||
- 前端:在详情页渲染订阅者列表组件
|
||||
|
||||
**Tech Stack:** Flutter, FastAPI, PostgreSQL
|
||||
|
||||
---
|
||||
|
||||
## Task 1: 后端 - 返回订阅者列表
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/src/v1/schedule_items/schemas.py`
|
||||
- Modify: `backend/src/v1/schedule_items/service.py`
|
||||
- Verify: `backend/tests/integration/v1/test_schedule_items_routes.py`
|
||||
|
||||
- [ ] **Step 1: 新增 SubscriberInfo Schema**
|
||||
|
||||
```python
|
||||
# backend/src/v1/schedule_items/schemas.py
|
||||
|
||||
class SubscriberInfo(BaseModel):
|
||||
"""订阅者信息"""
|
||||
user_id: UUID
|
||||
username: str
|
||||
avatar_url: str | None = None
|
||||
permission: int # 位标志: 1=view, 2=invite, 4=edit
|
||||
status: str # active, pending, paused, unsubscribed
|
||||
subscribed_at: datetime
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 修改 ScheduleItemResponse**
|
||||
|
||||
```python
|
||||
class ScheduleItemResponse(BaseModel):
|
||||
# ... existing fields ...
|
||||
subscribers: List[SubscriberInfo] = [] # 新增
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 修改 service.get_by_id 填充 subscribers**
|
||||
|
||||
```python
|
||||
# backend/src/v1/schedule_items/service.py
|
||||
|
||||
async def get_by_id(self, item_id: UUID) -> ScheduleItemResponse:
|
||||
item = await self._repository.get_by_id(item_id)
|
||||
if not item:
|
||||
raise ScheduleItemNotFoundError(item_id)
|
||||
|
||||
# 获取订阅者列表
|
||||
subscriptions = await self._repository.get_subscriptions_by_item_id(item_id)
|
||||
subscribers = []
|
||||
for sub in subscriptions:
|
||||
if sub.status == 'active': # 只返回活跃订阅者
|
||||
user = await self._user_repo.get_by_id(sub.subscriber_id)
|
||||
if user:
|
||||
subscribers.append(SubscriberInfo(
|
||||
user_id=user.id,
|
||||
username=user.username,
|
||||
avatar_url=user.avatar_url,
|
||||
permission=sub.permission,
|
||||
status=sub.status,
|
||||
subscribed_at=sub.created_at,
|
||||
))
|
||||
|
||||
return ScheduleItemResponse(
|
||||
# ... existing fields ...,
|
||||
subscribers=subscribers,
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 编写集成测试**
|
||||
|
||||
```python
|
||||
# backend/tests/integration/v1/test_schedule_items_routes.py
|
||||
|
||||
async def test_get_item_returns_subscribers(self):
|
||||
"""测试获取日程时返回订阅者列表"""
|
||||
# Arrange: 创建日程,添加订阅者
|
||||
# Act: GET /api/v1/schedule-items/{id}
|
||||
# Assert: 响应包含 subscribers 字段
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: 前端 - Model 更新
|
||||
|
||||
**Files:**
|
||||
- Modify: `apps/lib/features/calendar/data/models/schedule_item_model.dart`
|
||||
|
||||
- [ ] **Step 1: 新增 Subscriber 模型**
|
||||
|
||||
```dart
|
||||
class Subscriber {
|
||||
final String userId;
|
||||
final String username;
|
||||
final String? avatarUrl;
|
||||
final int permission;
|
||||
final String status;
|
||||
final DateTime subscribedAt;
|
||||
|
||||
bool get canView => (permission & 1) != 0;
|
||||
bool get canEdit => (permission & 4) != 0;
|
||||
bool get canInvite => (permission & 2) != 0;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 在 ScheduleItemModel 中添加 subscribers 字段**
|
||||
|
||||
```dart
|
||||
class ScheduleItemModel {
|
||||
// ... existing fields ...
|
||||
final List<Subscriber> subscribers;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 更新 fromJson**
|
||||
|
||||
```dart
|
||||
factory ScheduleItemModel.fromJson(Map<String, dynamic> json) {
|
||||
return ScheduleItemModel(
|
||||
// ... existing fields ...,
|
||||
subscribers: (json['subscribers'] as List<dynamic>?)
|
||||
?.map((s) => Subscriber.fromJson(s as Map<String, dynamic>))
|
||||
.toList() ?? [],
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: 前端 - 详情页渲染订阅者
|
||||
|
||||
**Files:**
|
||||
- Modify: `apps/lib/features/calendar/presentation/screens/calendar_event_detail_screen.dart`
|
||||
|
||||
- [ ] **Step 1: 在 _buildMetaSurface 或新方法中渲染订阅者**
|
||||
|
||||
```dart
|
||||
Widget _buildSubscribersSurface(ScheduleItemModel event) {
|
||||
if (event.subscribers.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.all(AppSpacing.lg),
|
||||
decoration: BoxDecoration(
|
||||
color: _colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
border: Border.all(color: _colorScheme.outlineVariant),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
context.l10n.calendarDetailSubscribers,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: _colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
...event.subscribers.map((sub) => _buildSubscriberRow(sub)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSubscriberRow(Subscriber subscriber) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: AppSpacing.xs),
|
||||
child: Row(
|
||||
children: [
|
||||
Avatar(url: subscriber.avatarUrl, name: subscriber.username),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
Expanded(child: Text(subscriber.username)),
|
||||
if (subscriber.canEdit) Icon(Icons.edit, size: 16),
|
||||
if (subscriber.canInvite) Icon(Icons.person_add, size: 16),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 在 build 方法中添加**
|
||||
|
||||
```dart
|
||||
// 在 _buildExtraSurface 之后添加
|
||||
if (event.subscribers.isNotEmpty) [
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
_buildSubscribersSurface(event),
|
||||
],
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 添加 l10n 文本**
|
||||
|
||||
```arb
|
||||
// apps/lib/l10n/app_zh.arb
|
||||
"calendarDetailSubscribers": "已订阅 ({count}人)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: 验证
|
||||
|
||||
- [ ] 运行后端测试: `cd backend && uv run pytest -v -k "subscriber"`
|
||||
- [ ] 运行前端分析: `cd apps && flutter analyze`
|
||||
- [ ] 手动测试分享流程
|
||||
Reference in New Issue
Block a user