# 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 subscribers; } ``` - [ ] **Step 3: 更新 fromJson** ```dart factory ScheduleItemModel.fromJson(Map json) { return ScheduleItemModel( // ... existing fields ..., subscribers: (json['subscribers'] as List?) ?.map((s) => Subscriber.fromJson(s as Map)) .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` - [ ] 手动测试分享流程