diff --git a/lib/apis/blog.dart b/lib/apis/blog.dart new file mode 100644 index 0000000..a0419e3 --- /dev/null +++ b/lib/apis/blog.dart @@ -0,0 +1,31 @@ +import 'package:blog_app/models/blog.dart'; +import 'package:blog_app/models/common.dart'; +import 'package:blog_app/utils/http_utils.dart'; +import 'package:blog_app/utils/index.dart'; + +Future> queryBlogByPageApi(int currentPage, int pageSize) { + return HttpUtil().get>( + "/blog/page", + queryParameters: {"currentPage": currentPage, "pageSize": pageSize}, + converter: (data) => convertPageResponse(data, Blog.fromJson), + ); +} + +Future> queryBlogByConditionApi( + String category, + String title, + int year, +) { + return HttpUtil().get>( + "/blog/condition", + queryParameters: {"category": category, "title": title, "year": year}, + converter: (data) => convertListResponse(data, Blog.fromJson), + ); +} + +Future queryBlogByIdApi(int id) { + return HttpUtil().get( + "blog/$id/content", + converter: (data) => Blog.fromJson(data), + ); +} diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 0ec7c85..f85fa85 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -3,6 +3,6 @@ class AppConfig { // 网络配置 // http://192.168.1.3:8100 // http://14.103.235.151:81/food-service - static const String baseApiUrl = "https://cxx0822.iepose.cn/blog-api/blog"; + static const String baseApiUrl = "https://cxx0822.iepose.cn/blog-api"; // static const String baseApiUrl = "http://172.29.101.108:8100"; } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index a1def36..3a6b73f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,10 @@ +import 'package:blog_app/pages/home_page.dart'; +import 'package:blog_app/utils/sp_utils.dart'; import 'package:flutter/material.dart'; -void main() { +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await SPUtil.init(); runApp(MyApp()); } @@ -8,193 +12,12 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - home: Scaffold( - appBar: AppBar( - title: Text('博客卡片Demo'), - backgroundColor: Colors.blue, - foregroundColor: Colors.white, - ), - body: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [Colors.blue.shade50, Colors.purple.shade50], - ), - ), - child: SingleChildScrollView( - padding: EdgeInsets.all(16), - child: Column( - children: [ - BlogCard( - title: 'Flutter开发实战', - description: '本文详细介绍了如何使用Flutter开发高性能的跨平台应用,包含最佳实践和性能优化技巧。通过实际案例演示了状态管理、路由导航、网络请求等核心功能的实现方式,帮助开发者快速掌握Flutter开发精髓。', - labels: [ - BlogLabel('精品', Icons.star, Colors.amber), - BlogLabel('置顶', Icons.push_pin, Colors.red), - BlogLabel('技术', Icons.category, Colors.blue), - BlogLabel('2024-01-15', Icons.calendar_today, Colors.green), - BlogLabel('2024-01-20', Icons.update, Colors.orange), - BlogLabel('2.3k', Icons.remove_red_eye, Colors.purple), - BlogLabel('3.2k字', Icons.text_fields, Colors.indigo), - BlogLabel('8分钟', Icons.access_time, Colors.brown), - BlogLabel('24', Icons.comment, Colors.teal), - ], - ), - - SizedBox(height: 20), - - BlogCard( - title: 'Dart语言特性深入解析', - description: '深入探讨Dart语言的现代特性,包括空安全、异步编程、扩展方法等高级功能。结合实例讲解如何利用Dart的特性编写更安全、更高效的代码,提升开发效率和代码质量。', - labels: [ - BlogLabel('推荐', Icons.thumb_up, Colors.pink), - BlogLabel('编程', Icons.code, Colors.blue), - BlogLabel('2024-01-10', Icons.calendar_today, Colors.green), - BlogLabel('2024-01-18', Icons.update, Colors.orange), - BlogLabel('1.5k', Icons.remove_red_eye, Colors.purple), - BlogLabel('2.1k字', Icons.text_fields, Colors.indigo), - BlogLabel('6分钟', Icons.access_time, Colors.brown), - BlogLabel('18', Icons.comment, Colors.teal), - ], - ), - ], - ), - ), - ) - ), + home: HomePage(), + debugShowCheckedModeBanner: false, theme: ThemeData( - fontFamily: 'CustomFont' + scaffoldBackgroundColor: Color(0xFFF5F5F5), + fontFamily: 'CustomFont', ), ); } } - -class BlogLabel { - final String text; - final IconData icon; - final Color color; - - BlogLabel(this.text, this.icon, this.color); -} - -class BlogCard extends StatelessWidget { - final String title; - final String description; - final List labels; - - const BlogCard({ - super.key, - required this.title, - required this.description, - required this.labels, - }); - - @override - Widget build(BuildContext context) { - return Card( - elevation: 4, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - child: Container( - width: double.infinity, // 确保宽度对齐 - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [Colors.white, Colors.grey.shade50], - ), - ), - child: Padding( - padding: EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - // 标题 - 居中 - Text( - title, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.blue.shade800, - ), - textAlign: TextAlign.center, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - - SizedBox(height: 16), - - // 单行标签 - 居中,更小的尺寸 - Container( - width: double.infinity, - child: Wrap( - spacing: 6, - runSpacing: 6, - alignment: WrapAlignment.center, - children: labels.map((label) { - return Container( - padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: label.color.withAlpha(30), - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: label.color.withAlpha(20), - width: 1, - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(label.icon, size: 12, color: label.color), - SizedBox(width: 4), - Text( - label.text, - style: TextStyle( - fontSize: 10, - color: label.color, - fontWeight: FontWeight.w500, - ), - ), - ], - ), - ); - }).toList(), - ), - ), - - SizedBox(height: 16), - - // 分隔线 - Container( - height: 1, - width: 80, - color: Colors.blue.shade200, - ), - - SizedBox(height: 16), - - // 描述 - 放在下面,大约100字 - Container( - width: double.infinity, - child: Text( - description, - style: TextStyle( - fontSize: 14, - color: Colors.grey.shade700, - height: 1.6, - ), - textAlign: TextAlign.justify, - maxLines: 4, - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/models/blog.dart b/lib/models/blog.dart new file mode 100644 index 0000000..1a11766 --- /dev/null +++ b/lib/models/blog.dart @@ -0,0 +1,62 @@ +import 'package:json_annotation/json_annotation.dart'; + +// flutter pub run build_runner build +part 'blog.g.dart'; + +@JsonSerializable() +class Blog { + @JsonKey(name: 'id') + final int id; + + @JsonKey(name: 'title') + final String title; + + @JsonKey(name: 'topValue') + final int topValue; + + @JsonKey(name: 'isGreat') + final bool isGreat; + + @JsonKey(name: 'category') + final String category; + + @JsonKey(name: 'summary') + final String summary; + + @JsonKey(name: 'content') + final String? content; + + @JsonKey(name: 'wordCount') + final int wordCount; + + @JsonKey(name: 'readDuration') + final int readDuration; + + @JsonKey(name: 'visitCount') + final int visitCount; + + @JsonKey(name: 'createTime') + final String createTime; + + @JsonKey(name: 'updateTime') + final String updateTime; + + Blog({ + required this.id, + required this.title, + required this.topValue, + required this.isGreat, + required this.category, + required this.summary, + this.content, + required this.wordCount, + required this.readDuration, + required this.visitCount, + required this.createTime, + required this.updateTime, + }); + + factory Blog.fromJson(Map json) => _$BlogFromJson(json); + + Map toJson() => _$BlogToJson(this); +} \ No newline at end of file diff --git a/lib/models/blog.g.dart b/lib/models/blog.g.dart new file mode 100644 index 0000000..43fbbdf --- /dev/null +++ b/lib/models/blog.g.dart @@ -0,0 +1,37 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'blog.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Blog _$BlogFromJson(Map json) => Blog( + id: (json['id'] as num).toInt(), + title: json['title'] as String, + topValue: (json['topValue'] as num).toInt(), + isGreat: json['isGreat'] as bool, + category: json['category'] as String, + summary: json['summary'] as String, + content: json['content'] as String?, + wordCount: (json['wordCount'] as num).toInt(), + readDuration: (json['readDuration'] as num).toInt(), + visitCount: (json['visitCount'] as num).toInt(), + createTime: json['createTime'] as String, + updateTime: json['updateTime'] as String, +); + +Map _$BlogToJson(Blog instance) => { + 'id': instance.id, + 'title': instance.title, + 'topValue': instance.topValue, + 'isGreat': instance.isGreat, + 'category': instance.category, + 'summary': instance.summary, + 'content': instance.content, + 'wordCount': instance.wordCount, + 'readDuration': instance.readDuration, + 'visitCount': instance.visitCount, + 'createTime': instance.createTime, + 'updateTime': instance.updateTime, +}; diff --git a/lib/models/common.dart b/lib/models/common.dart new file mode 100644 index 0000000..40f91d5 --- /dev/null +++ b/lib/models/common.dart @@ -0,0 +1,37 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'common.g.dart'; + +@JsonSerializable(genericArgumentFactories: true) +class PageResult { + @JsonKey(name: 'records') + final List records; + + @JsonKey(name: 'total') + final int total; + + @JsonKey(name: 'size') + final int size; + + @JsonKey(name: 'current') + final int current; + + @JsonKey(name: 'pages') + final int pages; + + const PageResult({ + required this.records, + required this.total, + required this.size, + required this.current, + required this.pages, + }); + + factory PageResult.fromJson( + Map json, + T Function(Object? json) fromJsonT, + ) => _$PageResultFromJson(json, fromJsonT); + + Map toJson(Object? Function(T value) toJsonT) => + _$PageResultToJson(this, toJsonT); +} diff --git a/lib/models/common.g.dart b/lib/models/common.g.dart new file mode 100644 index 0000000..f0f50ed --- /dev/null +++ b/lib/models/common.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'common.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PageResult _$PageResultFromJson( + Map json, + T Function(Object? json) fromJsonT, +) => PageResult( + records: (json['records'] as List).map(fromJsonT).toList(), + total: (json['total'] as num).toInt(), + size: (json['size'] as num).toInt(), + current: (json['current'] as num).toInt(), + pages: (json['pages'] as num).toInt(), +); + +Map _$PageResultToJson( + PageResult instance, + Object? Function(T value) toJsonT, +) => { + 'records': instance.records.map(toJsonT).toList(), + 'total': instance.total, + 'size': instance.size, + 'current': instance.current, + 'pages': instance.pages, +}; diff --git a/lib/pages/blog_page.dart b/lib/pages/blog_page.dart new file mode 100644 index 0000000..dac6bed --- /dev/null +++ b/lib/pages/blog_page.dart @@ -0,0 +1,38 @@ +import 'package:blog_app/apis/blog.dart'; +import 'package:blog_app/models/blog.dart'; +import 'package:blog_app/widget/blog.dart'; +import 'package:flutter/material.dart'; + +class BlogPage extends StatefulWidget { + const BlogPage({super.key}); + + @override + State createState() => _BlogPageState(); +} + +class _BlogPageState extends State { + late List blogList = []; + + @override + void initState() { + super.initState(); + // 初始加载数据 + _loadData(); + } + + Future _loadData() async { + final result = await queryBlogByPageApi(1, 10); + + setState(() { + blogList = result.records; + }); + } + + @override + Widget build(BuildContext context) { + return ListView.builder( + itemCount: blogList.length, + itemBuilder: (context, index) => BlogCard(blog: blogList[index]), + ); + } +} diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index e862e53..78c3187 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -1,4 +1,5 @@ import 'package:blog_app/pages/about_page.dart'; +import 'package:blog_app/pages/blog_page.dart'; import 'package:blog_app/pages/message_page.dart'; import 'package:blog_app/pages/profile_page.dart'; import 'package:blog_app/pages/settings_page.dart'; @@ -14,16 +15,11 @@ class _HomePageState extends State { late PageController _pageController; // 页面标题列表 - final List _pageTitles = [ - '个人资料', - '消息中心', - '设置', - '关于我们', - ]; + final List _pageTitles = ['博客', '消息中心', '设置', '关于我们']; // 页面图标列表 final List _pageIcons = [ - Icons.home, + Icons.article, Icons.person, Icons.message, Icons.settings, @@ -42,18 +38,34 @@ class _HomePageState extends State { super.dispose(); } + Widget _buildPageView() { + return PageView( + controller: _pageController, + onPageChanged: (index) { + setState(() { + _currentPageIndex = index; + }); + }, + children: [BlogPage(), MessagePage(), SettingsPage(), AboutPage()], + ); + } + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(_pageTitles[_currentPageIndex]), - backgroundColor: Colors.blue, + title: Text( + _pageTitles[_currentPageIndex], + style: TextStyle(color: Colors.white), + ), + backgroundColor: Theme.of(context).colorScheme.primary, elevation: 0, leading: Builder( - builder: (context) => IconButton( - icon: Icon(Icons.menu, color: Colors.white), - onPressed: () => Scaffold.of(context).openDrawer(), - ), + builder: + (context) => IconButton( + icon: Icon(Icons.menu, color: Colors.white), + onPressed: () => Scaffold.of(context).openDrawer(), + ), ), actions: [ IconButton( @@ -63,20 +75,7 @@ class _HomePageState extends State { ], ), drawer: _buildDrawer(), - body: PageView( - controller: _pageController, - onPageChanged: (index) { - setState(() { - _currentPageIndex = index; - }); - }, - children: [ - ProfilePage(), - MessagePage(), - SettingsPage(), - AboutPage(), - ], - ), + body: Padding(padding: EdgeInsets.all(10), child: _buildPageView()), ); } @@ -94,12 +93,13 @@ class _HomePageState extends State { child: ListView( padding: EdgeInsets.zero, children: [ - ...List.generate(_pageTitles.length, (index) => - _buildDrawerItem( - icon: _pageIcons[index], - title: _pageTitles[index], - index: index, - ) + ...List.generate( + _pageTitles.length, + (index) => _buildDrawerItem( + icon: _pageIcons[index], + title: _pageTitles[index], + index: index, + ), ), ], ), @@ -128,11 +128,7 @@ class _HomePageState extends State { CircleAvatar( radius: 40, backgroundColor: Colors.white.withOpacity(0.3), - child: Icon( - Icons.person, - size: 50, - color: Colors.white, - ), + child: Icon(Icons.person, size: 50, color: Colors.white), ), SizedBox(height: 16), Text( @@ -146,10 +142,7 @@ class _HomePageState extends State { SizedBox(height: 4), Text( 'user@example.com', - style: TextStyle( - color: Colors.white70, - fontSize: 14, - ), + style: TextStyle(color: Colors.white70, fontSize: 14), ), ], ), @@ -174,24 +167,31 @@ class _HomePageState extends State { child: ListTile( leading: Icon( icon, - color: isSelected ? Colors.blue : - isSpecialItem ? Colors.grey[600] : Colors.grey[700], + color: + isSelected + ? Colors.blue + : isSpecialItem + ? Colors.grey[600] + : Colors.grey[700], size: 24, ), title: Text( title, style: TextStyle( - color: isSelected ? Colors.blue : - isSpecialItem ? Colors.grey[600] : Colors.grey[800], + color: + isSelected + ? Colors.blue + : isSpecialItem + ? Colors.grey[600] + : Colors.grey[800], fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, fontSize: 16, ), ), - trailing: isSelected ? Icon( - Icons.arrow_forward_ios, - size: 16, - color: Colors.blue, - ) : null, + trailing: + isSelected + ? Icon(Icons.arrow_forward_ios, size: 16, color: Colors.blue) + : null, onTap: () { if (!isSpecialItem) { // 正常页面切换 @@ -228,101 +228,3 @@ class _HomePageState extends State { } } } - -// 各个页面组件 -class HomeContentPage extends StatelessWidget { - @override - Widget build(BuildContext context) { - return SingleChildScrollView( - padding: EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 欢迎卡片 - Card( - elevation: 4, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - child: Padding( - padding: EdgeInsets.all(20), - child: Row( - children: [ - Icon(Icons.waving_hand, size: 40, color: Colors.amber), - SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '欢迎回来!', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - SizedBox(height: 4), - Text( - '今天也是美好的一天', - style: TextStyle(color: Colors.grey), - ), - ], - ), - ), - ], - ), - ), - ), - - SizedBox(height: 20), - - // 功能网格 - GridView.count( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - crossAxisCount: 2, - crossAxisSpacing: 16, - mainAxisSpacing: 16, - children: [ - _buildFeatureCard('数据统计', Icons.analytics, Colors.blue), - _buildFeatureCard('文件管理', Icons.folder, Colors.green), - _buildFeatureCard('消息通知', Icons.notifications, Colors.orange), - _buildFeatureCard('系统设置', Icons.settings, Colors.purple), - ], - ), - ], - ), - ); - } - - Widget _buildFeatureCard(String title, IconData icon, Color color) { - return Card( - elevation: 2, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - child: InkWell( - borderRadius: BorderRadius.circular(12), - onTap: () {}, - child: Container( - padding: EdgeInsets.all(16), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(icon, size: 40, color: color), - SizedBox(height: 8), - Text( - title, - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14, - ), - textAlign: TextAlign.center, - ), - ], - ), - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/pages/markdown_page.dart b/lib/pages/markdown_page.dart index 9a539b8..8009723 100644 --- a/lib/pages/markdown_page.dart +++ b/lib/pages/markdown_page.dart @@ -1,4 +1,4 @@ -import 'package:blog_app/utils/http_util.dart'; +import 'package:blog_app/utils/http_utils.dart'; import 'package:flutter/material.dart'; import 'package:markdown_widget/config/toc.dart'; import 'package:markdown_widget/widget/markdown.dart'; diff --git a/lib/utils/date_utils.dart b/lib/utils/date_utils.dart new file mode 100644 index 0000000..be9d81a --- /dev/null +++ b/lib/utils/date_utils.dart @@ -0,0 +1,13 @@ +import 'package:intl/intl.dart'; + +String formatDate(DateTime date) { + return DateFormat('yyyy-MM-dd').format(date); +} + +String formatDateString(String date) { + return DateFormat('yyyy-MM-dd').format(DateTime.parse(date)); +} + +String formatTime(DateTime datetime) { + return DateFormat('yyyy-MM-dd HH:mm:ss').format(datetime); +} diff --git a/lib/utils/http_util.dart b/lib/utils/http_utils.dart similarity index 98% rename from lib/utils/http_util.dart rename to lib/utils/http_utils.dart index 0ce3afc..d1e91df 100644 --- a/lib/utils/http_util.dart +++ b/lib/utils/http_utils.dart @@ -1,8 +1,8 @@ import 'package:blog_app/config/app_config.dart'; -import 'package:blog_app/utils/sp_util.dart'; +import 'package:blog_app/utils/sp_utils.dart'; import 'package:dio/dio.dart'; -import 'log_util.dart'; +import 'log_utils.dart'; class HttpUtil { static final HttpUtil _instance = HttpUtil._internal(); diff --git a/lib/utils/index.dart b/lib/utils/index.dart new file mode 100644 index 0000000..925286c --- /dev/null +++ b/lib/utils/index.dart @@ -0,0 +1,28 @@ +import 'package:blog_app/models/common.dart'; + +List convertListResponse( + dynamic data, + T Function(Map) fromJson, +) { + if (data is List) { + return data.map((item) => fromJson(item as Map)).toList(); + } + throw FormatException( + 'Expected a list of items for conversion, but got ${data.runtimeType}', + ); +} + +PageResult convertPageResponse( + dynamic data, + T Function(Map) fromJson, +) { + if (data is Map) { + return PageResult.fromJson( + data, + (json) => fromJson(json as Map), + ); + } + throw FormatException( + 'Expected a Map for page response conversion, but got ${data.runtimeType}', + ); +} diff --git a/lib/utils/log_util.dart b/lib/utils/log_utils.dart similarity index 100% rename from lib/utils/log_util.dart rename to lib/utils/log_utils.dart diff --git a/lib/utils/sp_util.dart b/lib/utils/sp_utils.dart similarity index 100% rename from lib/utils/sp_util.dart rename to lib/utils/sp_utils.dart diff --git a/lib/widget/blog.dart b/lib/widget/blog.dart new file mode 100644 index 0000000..f5603e0 --- /dev/null +++ b/lib/widget/blog.dart @@ -0,0 +1,144 @@ +import 'package:blog_app/models/blog.dart'; +import 'package:blog_app/utils/date_utils.dart'; +import 'package:flutter/material.dart'; + +class BlogLabel { + final String text; + final IconData icon; + final Color color; + + BlogLabel(this.text, this.icon, this.color); +} + +class BlogCard extends StatelessWidget { + final Blog blog; + + const BlogCard({super.key, required this.blog}); + + Widget _buildBlogLabelItem(String text, IconData icon, Color color) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: color.withAlpha(30), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: color.withAlpha(20), width: 1), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 12, color: color), + SizedBox(width: 4), + Text( + text, + style: TextStyle( + fontSize: 10, + color: color, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ); + } + + Widget _buildBlogLabel() { + return SizedBox( + width: double.infinity, + child: Wrap( + spacing: 6, + runSpacing: 6, + alignment: WrapAlignment.center, + children: [ + if (blog.isGreat) + _buildBlogLabelItem('精品', Icons.workspace_premium, Colors.amber), + if (blog.topValue > 1) + _buildBlogLabelItem('置顶', Icons.push_pin, Colors.red), + _buildBlogLabelItem(blog.category, Icons.folder, Colors.blue), + _buildBlogLabelItem( + formatDateString(blog.createTime), + Icons.calendar_today, + Colors.green, + ), + _buildBlogLabelItem( + formatDateString(blog.updateTime), + Icons.calendar_month, + Colors.orange, + ), + _buildBlogLabelItem( + '${blog.visitCount} 次', + Icons.remove_red_eye, + Colors.purple, + ), + _buildBlogLabelItem( + '${blog.wordCount} 字', + Icons.text_fields, + Colors.indigo, + ), + _buildBlogLabelItem( + '${blog.readDuration} 分钟', + Icons.access_time, + Colors.brown, + ), + ], + ), + ); + } + + Widget _buildBlogTitle(BuildContext context) { + return Text( + blog.title, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.primary, + ), + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ); + } + + Widget _buildBlogSummary() { + return SizedBox( + width: double.infinity, + child: Text( + blog.summary, + style: TextStyle(color: Colors.grey.shade700, height: 1.6), + overflow: TextOverflow.ellipsis, + ), + ); + } + + @override + Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; + + return Card( + elevation: 0, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Container( + width: double.infinity, + decoration: BoxDecoration( + color: colors.surface, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: colors.outline.withAlpha(50), width: 1), + ), + child: Padding( + padding: EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _buildBlogTitle(context), + SizedBox(height: 16), + _buildBlogLabel(), + SizedBox(height: 16), + Container(height: 1, width: 80, color: Theme.of(context).colorScheme.primary), + SizedBox(height: 16), + _buildBlogSummary(), + ], + ), + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 8712106..8411136 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,22 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f + url: "https://pub.flutter-io.cn" + source: hosted + version: "85.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.7.1" args: dependency: transitive description: @@ -25,6 +41,70 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.4" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.2" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.1" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.4" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.4" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792" + url: "https://pub.flutter-io.cn" + source: hosted + version: "9.1.2" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d + url: "https://pub.flutter-io.cn" + source: hosted + version: "8.12.0" characters: dependency: transitive description: @@ -33,6 +113,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.3" clock: dependency: transitive description: @@ -41,6 +129,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.11.0" collection: dependency: transitive description: @@ -49,6 +145,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.7" cupertino_icons: dependency: "direct main" description: @@ -57,6 +169,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.0.8" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.1" dio: dependency: "direct main" description: @@ -97,6 +217,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -128,6 +256,30 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.3" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.2" highlight: dependency: transitive description: @@ -136,6 +288,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.7.0" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.6.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.2" http_parser: dependency: transitive description: @@ -144,6 +312,46 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "4.1.2" + intl: + dependency: "direct dev" + description: + name: intl + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.19.0" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.2" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.9.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.9.5" leak_tracker: dependency: transitive description: @@ -184,6 +392,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.6.2" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.0" markdown: dependency: transitive description: @@ -232,6 +448,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0" path: dependency: transitive description: @@ -280,6 +504,30 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.5.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.5.0" scroll_to_index: dependency: transitive description: @@ -344,11 +592,43 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.4.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.7" source_span: dependency: transitive description: @@ -373,6 +653,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.1" string_scanner: dependency: transitive description: @@ -397,6 +685,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.7.4" + timing: + dependency: transitive + description: + name: timing + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.2" typed_data: dependency: transitive description: @@ -493,6 +789,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "14.3.1" + watcher: + dependency: transitive + description: + name: watcher + sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.4" web: dependency: transitive description: @@ -501,6 +805,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.3" xdg_directories: dependency: transitive description: @@ -509,6 +829,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.3" sdks: dart: ">=3.7.0 <4.0.0" flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index 3a9d993..d245317 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,6 +38,7 @@ dependencies: dio: ^5.7.0 shared_preferences: ^2.3.0 logger: ^2.6.0 + json_annotation: ^4.9.0 dev_dependencies: flutter_test: @@ -49,6 +50,9 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^5.0.0 + build_runner: ^2.4.5 + json_serializable: ^6.7.1 + intl: ^0.19.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec