From a5d2b0409faba6c920e99903f6cc5251693ef348 Mon Sep 17 00:00:00 2001 From: iven Date: Sun, 7 Jun 2026 10:43:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(app):=20=E5=8F=91=E7=8E=B0=E9=A1=B5?= =?UTF-8?q?=E5=8A=A8=E6=80=81=E5=8C=96=20=E2=80=94=20DiscoverBloc=20+=20AP?= =?UTF-8?q?I=20=E9=A9=B1=E5=8A=A8=20+=20=E4=B8=8B=E6=8B=89=E5=88=B7?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 DiscoverBloc (LoadData/Refresh) + DiscoverModels 4 个数据类 - DiscoverPage 改为 BlocBuilder 驱动: loading/loaded/error/empty 四态 - 替换全部硬编码占位数据为 API 实时数据 - 添加 RefreshIndicator 下拉刷新 - 离线异常时保留已有数据,友好错误提示 --- .../features/discover/bloc/discover_bloc.dart | 78 ++++ .../discover/bloc/discover_event.dart | 16 + .../discover/bloc/discover_state.dart | 30 ++ .../discover/models/discover_models.dart | 160 ++++++++ .../discover/views/discover_page.dart | 369 ++++++++++++++---- 5 files changed, 572 insertions(+), 81 deletions(-) create mode 100644 app/lib/features/discover/bloc/discover_bloc.dart create mode 100644 app/lib/features/discover/bloc/discover_event.dart create mode 100644 app/lib/features/discover/bloc/discover_state.dart create mode 100644 app/lib/features/discover/models/discover_models.dart diff --git a/app/lib/features/discover/bloc/discover_bloc.dart b/app/lib/features/discover/bloc/discover_bloc.dart new file mode 100644 index 0000000..29d3ec6 --- /dev/null +++ b/app/lib/features/discover/bloc/discover_bloc.dart @@ -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 { + final ApiClient _api; + + DiscoverBloc({required ApiClient api}) + : _api = api, + super(const DiscoverInitial()) { + on(_onLoadData); + on(_onRefresh); + } + + /// 首次加载 — 显示 loading 状态 + Future _onLoadData( + DiscoverLoadData event, + Emitter emit, + ) async { + emit(const DiscoverLoading()); + await _fetchData(emit); + } + + /// 刷新 — 不显示 loading,静默更新 + Future _onRefresh( + DiscoverRefresh event, + Emitter emit, + ) async { + await _fetchData(emit); + } + + /// 通用数据获取逻辑 + Future _fetchData(Emitter emit) async { + try { + final response = await _api.get('/diary/discover'); + final body = response.data as Map; + + // 后端信封格式: { success, data: { ... }, message } + final dataJson = body['data'] as Map? ?? {}; + 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 '请稍后重试'; + } +} diff --git a/app/lib/features/discover/bloc/discover_event.dart b/app/lib/features/discover/bloc/discover_event.dart new file mode 100644 index 0000000..f00f7a4 --- /dev/null +++ b/app/lib/features/discover/bloc/discover_event.dart @@ -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(); +} diff --git a/app/lib/features/discover/bloc/discover_state.dart b/app/lib/features/discover/bloc/discover_state.dart new file mode 100644 index 0000000..1d8ad32 --- /dev/null +++ b/app/lib/features/discover/bloc/discover_state.dart @@ -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); +} diff --git a/app/lib/features/discover/models/discover_models.dart b/app/lib/features/discover/models/discover_models.dart new file mode 100644 index 0000000..ccc9bf0 --- /dev/null +++ b/app/lib/features/discover/models/discover_models.dart @@ -0,0 +1,160 @@ +// 发现页数据模型 — 手写不可变类(避免 build_runner 依赖) + +/// 发现页聚合响应 — 一次 API 返回全部板块数据 +class DiscoverData { + final InspirationItem? dailyInspiration; + final List hotTopics; + final List featuredTemplates; + final List expertDiaries; + + const DiscoverData({ + this.dailyInspiration, + this.hotTopics = const [], + this.featuredTemplates = const [], + this.expertDiaries = const [], + }); + + factory DiscoverData.fromJson(Map json) => DiscoverData( + dailyInspiration: json['daily_inspiration'] != null + ? InspirationItem.fromJson( + json['daily_inspiration'] as Map) + : null, + hotTopics: (json['hot_topics'] as List? ?? []) + .map((e) => TagCount.fromJson(e as Map)) + .toList(), + featuredTemplates: (json['featured_templates'] as List? ?? []) + .map( + (e) => DiscoverTemplateItem.fromJson(e as Map)) + .toList(), + expertDiaries: (json['expert_diaries'] as List? ?? []) + .map((e) => ExpertDiaryItem.fromJson(e as Map)) + .toList(), + ); + + /// 心情 → emoji 映射 + static String moodToEmoji(String mood) => switch (mood) { + 'happy' => '😊', + 'calm' => '😌', + 'sad' => '😢', + 'angry' => '😤', + 'thinking' => '🤔', + _ => '📝', + }; +} + +/// 每日推荐条目 +class InspirationItem { + final String journalId; + final String title; + final String authorName; + final String mood; + final DateTime date; + + const InspirationItem({ + required this.journalId, + required this.title, + required this.authorName, + required this.mood, + required this.date, + }); + + factory InspirationItem.fromJson(Map json) => + InspirationItem( + journalId: json['journal_id'] as String, + title: json['title'] as String, + authorName: json['author_name'] as String, + mood: json['mood'] as String, + date: DateTime.parse(json['date'] as String), + ); +} + +/// 热门话题 +class TagCount { + final String tag; + final int count; + + const TagCount({required this.tag, required this.count}); + + factory TagCount.fromJson(Map json) => TagCount( + tag: json['tag'] as String, + count: json['count'] as int, + ); +} + +/// 精选模板条目(轻量版,不含 layout_data) +class DiscoverTemplateItem { + final String id; + final String name; + final String? previewUrl; + final String? category; + final bool isFree; + + const DiscoverTemplateItem({ + required this.id, + required this.name, + this.previewUrl, + this.category, + this.isFree = true, + }); + + factory DiscoverTemplateItem.fromJson(Map json) => + DiscoverTemplateItem( + id: json['id'] as String, + name: json['name'] as String, + previewUrl: json['preview_url'] as String?, + category: json['category'] as String?, + isFree: json['is_free'] as bool? ?? true, + ); + + /// 分类 → emoji 映射 + String get emoji => switch (category) { + '日常' => '📖', + '旅行' => '✈️', + '校园' => '🎓', + '节日' => '🎄', + '创意' => '✨', + '心情' => '🌿', + _ => '📝', + }; + + /// 使用人数展示文本 + String get usageText => isFree ? '免费模板' : '精品模板'; +} + +/// 达人日记条目 +class ExpertDiaryItem { + final String journalId; + final String title; + final String authorId; + final String authorName; + final String authorEmoji; + final String contentPreview; + final int likeCount; + final DateTime createdAt; + + const ExpertDiaryItem({ + required this.journalId, + required this.title, + required this.authorId, + required this.authorName, + required this.authorEmoji, + required this.contentPreview, + required this.likeCount, + required this.createdAt, + }); + + factory ExpertDiaryItem.fromJson(Map json) => + ExpertDiaryItem( + journalId: json['journal_id'] as String, + title: json['title'] as String, + authorId: json['author_id'] as String, + authorName: json['author_name'] as String, + authorEmoji: json['author_emoji'] as String, + contentPreview: json['content_preview'] as String? ?? '', + likeCount: json['like_count'] as int? ?? 0, + createdAt: DateTime.parse(json['created_at'] as String), + ); + + /// 点赞数展示文本 + String get likeText => '$likeCount 赞'; +} diff --git a/app/lib/features/discover/views/discover_page.dart b/app/lib/features/discover/views/discover_page.dart index 421fcee..4949ada 100644 --- a/app/lib/features/discover/views/discover_page.dart +++ b/app/lib/features/discover/views/discover_page.dart @@ -8,8 +8,10 @@ // 5. 达人日记 expert-diaries (纵向列表) // // 注意:本页是发现/灵感浏览,区别于 /search(主动搜索) +// 数据来源:GET /diary/discover → DiscoverBloc import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import '../../../core/constants/design_tokens.dart'; @@ -17,48 +19,198 @@ import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_radius.dart'; import '../../../core/theme/app_shadows.dart'; import '../../../core/theme/app_typography.dart'; +import '../bloc/discover_bloc.dart'; +import '../models/discover_models.dart'; class DiscoverPage extends StatelessWidget { const DiscoverPage({super.key}); @override Widget build(BuildContext context) { - final theme = Theme.of(context); - final isDark = theme.brightness == Brightness.dark; - final bg = isDark ? AppColors.bgDark : AppColors.bgLight; - return Scaffold( - backgroundColor: bg, + backgroundColor: _bgColor(context), body: SafeArea( - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: DesignTokens.spacing20), + child: RefreshIndicator( + onRefresh: () async { + context.read().add(const DiscoverRefresh()); + // 等待状态变化完成 + await context.read().stream.firstWhere( + (s) => s is DiscoverLoaded || s is DiscoverError, + orElse: () => const DiscoverLoaded(DiscoverData()), + ); + }, + child: BlocBuilder( + builder: (context, state) { + return SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.spacing20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: DesignTokens.spacing12), + _SearchBar(onTap: () => context.push('/search')), + const SizedBox(height: DesignTokens.spacing20), + _buildContent(context, state), + const SizedBox(height: DesignTokens.spacing24), + ], + ), + ); + }, + ), + ), + ), + ); + } + + /// 根据状态构建主要内容 + Widget _buildContent(BuildContext context, DiscoverState state) { + return switch (state) { + DiscoverInitial() => _buildLoading(context), + DiscoverLoading() => _buildLoading(context), + DiscoverLoaded(:final data) => _buildLoaded(context, data), + DiscoverError(:final message) => _buildError(context, message), + }; + } + + /// 加载中状态 — 骨架占位 + Widget _buildLoading(BuildContext context) { + return const Column( + children: [ + _LoadingSkeleton(height: 140), + SizedBox(height: DesignTokens.spacing24), + _LoadingSkeleton(height: 44), + SizedBox(height: DesignTokens.spacing24), + _LoadingSkeleton(height: 200), + ], + ); + } + + /// 加载成功 — 渲染真实数据 + Widget _buildLoaded(BuildContext context, DiscoverData data) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 每日推荐 + _InspirationCard(item: data.dailyInspiration), + // 热门话题 + if (data.hotTopics.isNotEmpty) ...[ + const SizedBox(height: DesignTokens.spacing24), + const _SectionTitle(title: '热门话题'), + const SizedBox(height: DesignTokens.spacing12), + _HotTopicsChips(topics: data.hotTopics), + ], + // 精选模板 + if (data.featuredTemplates.isNotEmpty) ...[ + const SizedBox(height: DesignTokens.spacing24), + const _SectionTitle(title: '精选模板'), + const SizedBox(height: DesignTokens.spacing12), + _FeaturedTemplatesGrid(templates: data.featuredTemplates), + ], + // 达人日记 + if (data.expertDiaries.isNotEmpty) ...[ + const SizedBox(height: DesignTokens.spacing24), + const _SectionTitle(title: '达人日记'), + const SizedBox(height: DesignTokens.spacing12), + _ExpertDiariesList(diaries: data.expertDiaries), + ], + // 全部为空时的占位提示 + if (data.dailyInspiration == null && + data.hotTopics.isEmpty && + data.featuredTemplates.isEmpty && + data.expertDiaries.isEmpty) + _buildEmptyHint(context), + ], + ); + } + + /// 错误状态 + Widget _buildError(BuildContext context, String message) { + return Column( + children: [ + const _LoadingSkeleton(height: 140), + const SizedBox(height: DesignTokens.spacing24), + Container( + width: double.infinity, + padding: const EdgeInsets.all(DesignTokens.spacing16), + decoration: BoxDecoration( + color: AppColors.rose.withValues(alpha: 0.1), + borderRadius: AppRadius.mdBorder, + ), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ + Icon(Icons.cloud_off_rounded, + size: 32, color: AppColors.rose), + const SizedBox(height: DesignTokens.spacing8), + Text(message, + style: TextStyle( + fontSize: 13, + color: Theme.of(context).colorScheme.onSurfaceVariant), + textAlign: TextAlign.center), const SizedBox(height: DesignTokens.spacing12), - _SearchBar(onTap: () => context.push('/search')), - const SizedBox(height: DesignTokens.spacing20), - const _InspirationCard( - title: '今日推荐:图书馆的午后时光', - author: '小暖 · 5月31日', - emoji: '📚', + TextButton.icon( + onPressed: () => context + .read() + .add(const DiscoverLoadData()), + icon: const Icon(Icons.refresh_rounded, size: 18), + label: const Text('重试'), ), - const SizedBox(height: DesignTokens.spacing24), - _SectionTitle(title: '热门话题'), - const SizedBox(height: DesignTokens.spacing12), - const _HotTopicsChips(), - const SizedBox(height: DesignTokens.spacing24), - _SectionTitle(title: '精选模板'), - const SizedBox(height: DesignTokens.spacing12), - const _FeaturedTemplatesGrid(), - const SizedBox(height: DesignTokens.spacing24), - _SectionTitle(title: '达人日记'), - const SizedBox(height: DesignTokens.spacing12), - const _ExpertDiariesList(), - const SizedBox(height: DesignTokens.spacing24), ], ), ), + ], + ); + } + + /// 空数据提示 + Widget _buildEmptyHint(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: DesignTokens.spacing32), + child: Center( + child: Column( + children: [ + const Text('✨', style: TextStyle(fontSize: 40)), + const SizedBox(height: DesignTokens.spacing12), + Text('还没有发现内容', + style: TextStyle( + fontSize: 15, + color: Theme.of(context).colorScheme.onSurfaceVariant, + )), + const SizedBox(height: 4), + Text('写下你的第一篇日记,出现在这里吧!', + style: TextStyle( + fontSize: 13, + color: Theme.of(context).colorScheme.onSurfaceVariant + .withValues(alpha: 0.7), + )), + ], + ), + ), + ); + } + + Color _bgColor(BuildContext context) { + final theme = Theme.of(context); + return theme.brightness == Brightness.dark + ? AppColors.bgDark + : AppColors.bgLight; + } +} + +/// 加载骨架占位 +class _LoadingSkeleton extends StatelessWidget { + const _LoadingSkeleton({required this.height}); + final double height; + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + height: height, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainerHighest + .withValues(alpha: 0.3), + borderRadius: AppRadius.lgBorder, ), ); } @@ -77,7 +229,8 @@ class _SearchBar extends StatelessWidget { borderRadius: AppRadius.pillBorder, child: Container( height: 48, - padding: const EdgeInsets.symmetric(horizontal: DesignTokens.spacing16), + padding: + const EdgeInsets.symmetric(horizontal: DesignTokens.spacing16), decoration: BoxDecoration( color: theme.colorScheme.surface, borderRadius: AppRadius.pillBorder, @@ -85,7 +238,8 @@ class _SearchBar extends StatelessWidget { ), child: Row( children: [ - Icon(Icons.search_rounded, size: 20, color: theme.colorScheme.onSurfaceVariant), + Icon(Icons.search_rounded, + size: 20, color: theme.colorScheme.onSurfaceVariant), const SizedBox(width: DesignTokens.spacing12), Text( '搜索日记、模板、话题...', @@ -103,18 +257,49 @@ class _SearchBar extends StatelessWidget { /// 2. 每日推荐卡片(渐变背景) class _InspirationCard extends StatelessWidget { - const _InspirationCard({ - required this.title, - required this.author, - required this.emoji, - }); - - final String title; - final String author; - final String emoji; + const _InspirationCard({required this.item}); + final InspirationItem? item; @override Widget build(BuildContext context) { + if (item == null) { + // 无推荐日记时的占位卡片 + return Container( + width: double.infinity, + padding: const EdgeInsets.all(DesignTokens.spacing20), + decoration: BoxDecoration( + gradient: const LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [AppColors.accent, AppColors.tertiary], + ), + borderRadius: AppRadius.lgBorder, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('今日推荐', + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + color: Colors.white.withValues(alpha: 0.85), + letterSpacing: 0.5, + )), + const SizedBox(height: DesignTokens.spacing12), + const Text('今天还没有推荐日记', + style: TextStyle(fontSize: 16, color: Colors.white)), + const SizedBox(height: 4), + Text('写下你的日记,可能出现在这里哦 ✨', + style: TextStyle( + fontSize: 12, + color: Colors.white.withValues(alpha: 0.7))), + ], + ), + ); + } + + final emoji = DiscoverData.moodToEmoji(item!.mood); + return Container( width: double.infinity, padding: const EdgeInsets.all(DesignTokens.spacing20), @@ -191,17 +376,19 @@ class _InspirationCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - title, + item!.title, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w700, color: Colors.white, height: 1.25, ), + maxLines: 2, + overflow: TextOverflow.ellipsis, ), const SizedBox(height: 6), Text( - author, + '${item!.authorName} · ${_formatDate(item!.date)}', style: TextStyle( fontSize: 12, color: Colors.white.withValues(alpha: 0.75), @@ -218,6 +405,10 @@ class _InspirationCard extends StatelessWidget { ), ); } + + String _formatDate(DateTime date) { + return '${date.month}月${date.day}日'; + } } class _SectionTitle extends StatelessWidget { @@ -241,12 +432,8 @@ class _SectionTitle extends StatelessWidget { /// 3. 热门话题(横向滚动 chips) class _HotTopicsChips extends StatelessWidget { - const _HotTopicsChips(); - - static const _topics = [ - '#期末备考', '#读书笔记', '#旅行手账', '#美食日记', - '#校园生活', '#自我成长', '#心情日记', '#手写摘抄', - ]; + const _HotTopicsChips({required this.topics}); + final List topics; @override Widget build(BuildContext context) { @@ -255,24 +442,31 @@ class _HotTopicsChips extends StatelessWidget { height: 44, child: ListView.separated( scrollDirection: Axis.horizontal, - itemCount: _topics.length, - separatorBuilder: (_, __) => const SizedBox(width: DesignTokens.spacing8), + itemCount: topics.length, + separatorBuilder: (_, __) => + const SizedBox(width: DesignTokens.spacing8), itemBuilder: (context, index) { final isHot = index < 3; return Container( - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6), + padding: + const EdgeInsets.symmetric(horizontal: 14, vertical: 6), decoration: BoxDecoration( - color: isHot ? theme.colorScheme.primary : theme.colorScheme.surface, + color: + isHot ? theme.colorScheme.primary : theme.colorScheme.surface, borderRadius: AppRadius.pillBorder, - border: isHot ? null : Border.all(color: theme.colorScheme.outlineVariant), + border: isHot + ? null + : Border.all(color: theme.colorScheme.outlineVariant), ), alignment: Alignment.center, child: Text( - _topics[index], + '#${topics[index].tag}', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, - color: isHot ? theme.colorScheme.onPrimary : theme.colorScheme.onSurface, + color: isHot + ? theme.colorScheme.onPrimary + : theme.colorScheme.onSurface, ), ), ); @@ -284,14 +478,8 @@ class _HotTopicsChips extends StatelessWidget { /// 4. 精选模板(2 列网格) class _FeaturedTemplatesGrid extends StatelessWidget { - const _FeaturedTemplatesGrid(); - - static const _templates = [ - ('📖', '每日心情日记', '2.3k 人使用', AppColors.secondarySoftLight), - ('🎓', '期末复习计划', '1.8k 人使用', AppColors.tertiarySoftLight), - ('🌿', '植物观察日记', '956 人使用', AppColors.roseSoftLight), - ('✈️', '旅行手账本', '742 人使用', AppColors.secondarySoftLight), - ]; + const _FeaturedTemplatesGrid({required this.templates}); + final List templates; @override Widget build(BuildContext context) { @@ -304,13 +492,28 @@ class _FeaturedTemplatesGrid extends StatelessWidget { crossAxisSpacing: DesignTokens.spacing12, childAspectRatio: 0.85, ), - itemCount: _templates.length, + itemCount: templates.length, itemBuilder: (context, index) { - final t = _templates[index]; - return _TemplateCard(emoji: t.$1, name: t.$2, usage: t.$3, bg: t.$4); + final t = templates[index]; + return _TemplateCard( + emoji: t.emoji, + name: t.name, + usage: t.usageText, + bg: _categoryColor(t.category), + ); }, ); } + + Color _categoryColor(String? category) { + return switch (category) { + '日常' => AppColors.secondarySoftLight, + '校园' => AppColors.tertiarySoftLight, + '心情' => AppColors.roseSoftLight, + '旅行' => AppColors.secondarySoftLight, + _ => AppColors.secondarySoftLight, + }; + } } class _TemplateCard extends StatelessWidget { @@ -368,7 +571,9 @@ class _TemplateCard extends StatelessWidget { const SizedBox(height: 2), Text( usage, - style: TextStyle(fontSize: 11, color: theme.colorScheme.onSurfaceVariant), + style: TextStyle( + fontSize: 11, + color: theme.colorScheme.onSurfaceVariant), ), ], ), @@ -380,19 +585,14 @@ class _TemplateCard extends StatelessWidget { /// 5. 达人日记(纵向列表) class _ExpertDiariesList extends StatelessWidget { - const _ExpertDiariesList(); - - static const _experts = [ - ('🌸', '小桃子', '春日漫步手账', '记录春天的每一朵花开...', '342 赞'), - ('☕', '咖啡少年', '咖啡馆日记', '今天尝试了一家新店...', '218 赞'), - ('📝', '学习达人', '考研倒计时30天', '坚持就是胜利...', '556 赞'), - ]; + const _ExpertDiariesList({required this.diaries}); + final List diaries; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Column( - children: _experts.map((e) { + children: diaries.map((diary) { return Container( margin: const EdgeInsets.only(bottom: DesignTokens.spacing12), padding: const EdgeInsets.all(DesignTokens.spacing16), @@ -412,7 +612,8 @@ class _ExpertDiariesList extends StatelessWidget { shape: BoxShape.circle, ), alignment: Alignment.center, - child: Text(e.$1, style: const TextStyle(fontSize: 20)), + child: Text(diary.authorEmoji, + style: const TextStyle(fontSize: 20)), ), const SizedBox(width: DesignTokens.spacing12), Expanded( @@ -422,7 +623,7 @@ class _ExpertDiariesList extends StatelessWidget { Row( children: [ Text( - e.$2, + diary.authorName, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, @@ -432,12 +633,13 @@ class _ExpertDiariesList extends StatelessWidget { const SizedBox(width: DesignTokens.spacing8), Text( '·', - style: TextStyle(color: theme.colorScheme.onSurfaceVariant), + style: TextStyle( + color: theme.colorScheme.onSurfaceVariant), ), const SizedBox(width: DesignTokens.spacing8), Expanded( child: Text( - e.$3, + diary.title, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( @@ -452,7 +654,9 @@ class _ExpertDiariesList extends StatelessWidget { ), const SizedBox(height: 4), Text( - e.$4, + diary.contentPreview.isNotEmpty + ? diary.contentPreview + : '...', maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle( @@ -468,11 +672,14 @@ class _ExpertDiariesList extends StatelessWidget { Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.favorite_rounded, size: 14, color: AppColors.rose), + Icon(Icons.favorite_rounded, + size: 14, color: AppColors.rose), const SizedBox(width: 4), Text( - e.$5, - style: TextStyle(fontSize: 11, color: theme.colorScheme.onSurfaceVariant), + diary.likeText, + style: TextStyle( + fontSize: 11, + color: theme.colorScheme.onSurfaceVariant), ), ], ),