feat(app): 发现页动态化 — DiscoverBloc + API 驱动 + 下拉刷新

- 新增 DiscoverBloc (LoadData/Refresh) + DiscoverModels 4 个数据类
- DiscoverPage 改为 BlocBuilder 驱动: loading/loaded/error/empty 四态
- 替换全部硬编码占位数据为 API 实时数据
- 添加 RefreshIndicator 下拉刷新
- 离线异常时保留已有数据,友好错误提示
This commit is contained in:
iven
2026-06-07 10:43:23 +08:00
parent 3bc2ca7332
commit a5d2b0409f
5 changed files with 572 additions and 81 deletions

View File

@@ -0,0 +1,78 @@
// 发现页 BLoC — 管理发现页数据加载状态
//
// 职责:调用 /diary/discover API解析响应管理加载/成功/失败状态。
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../data/remote/api_client.dart';
import '../models/discover_models.dart';
part 'discover_event.dart';
part 'discover_state.dart';
class DiscoverBloc extends Bloc<DiscoverEvent, DiscoverState> {
final ApiClient _api;
DiscoverBloc({required ApiClient api})
: _api = api,
super(const DiscoverInitial()) {
on<DiscoverLoadData>(_onLoadData);
on<DiscoverRefresh>(_onRefresh);
}
/// 首次加载 — 显示 loading 状态
Future<void> _onLoadData(
DiscoverLoadData event,
Emitter<DiscoverState> emit,
) async {
emit(const DiscoverLoading());
await _fetchData(emit);
}
/// 刷新 — 不显示 loading静默更新
Future<void> _onRefresh(
DiscoverRefresh event,
Emitter<DiscoverState> emit,
) async {
await _fetchData(emit);
}
/// 通用数据获取逻辑
Future<void> _fetchData(Emitter<DiscoverState> emit) async {
try {
final response = await _api.get('/diary/discover');
final body = response.data as Map<String, dynamic>;
// 后端信封格式: { success, data: { ... }, message }
final dataJson = body['data'] as Map<String, dynamic>? ?? {};
final discoverData = DiscoverData.fromJson(dataJson);
emit(DiscoverLoaded(discoverData));
} on OfflineException {
// 离线时,如果有已加载的数据,保留它
if (state is DiscoverLoaded) return;
emit(const DiscoverError('网络不可用,请检查网络连接'));
} catch (e) {
if (state is DiscoverLoaded) return;
emit(DiscoverError('加载失败:${_friendlyError(e)}'));
}
}
/// 将异常转换为用户友好的错误消息
String _friendlyError(Object error) {
final msg = error.toString();
if (msg.contains('SocketException') || msg.contains('Connection refused')) {
return '无法连接服务器';
}
if (msg.contains('401')) {
return '登录已过期,请重新登录';
}
if (msg.contains('403')) {
return '没有访问权限';
}
if (msg.contains('500')) {
return '服务器错误,请稍后重试';
}
return '请稍后重试';
}
}

View File

@@ -0,0 +1,16 @@
part of 'discover_bloc.dart';
/// 发现页事件
sealed class DiscoverEvent {
const DiscoverEvent();
}
/// 加载发现页数据(首次进入或重新进入页面)
final class DiscoverLoadData extends DiscoverEvent {
const DiscoverLoadData();
}
/// 下拉刷新(不显示全屏 loading避免闪烁
final class DiscoverRefresh extends DiscoverEvent {
const DiscoverRefresh();
}

View File

@@ -0,0 +1,30 @@
part of 'discover_bloc.dart';
/// 发现页状态
sealed class DiscoverState {
const DiscoverState();
}
/// 初始状态
final class DiscoverInitial extends DiscoverState {
const DiscoverInitial();
}
/// 加载中
final class DiscoverLoading extends DiscoverState {
const DiscoverLoading();
}
/// 加载成功
final class DiscoverLoaded extends DiscoverState {
final DiscoverData data;
const DiscoverLoaded(this.data);
}
/// 加载失败
final class DiscoverError extends DiscoverState {
final String message;
const DiscoverError(this.message);
}