基础功能

This commit is contained in:
DelLevin-Home
2026-03-05 10:06:32 +08:00
commit adad6372a3
55 changed files with 6036 additions and 0 deletions

View File

@@ -0,0 +1,293 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/app_provider.dart';
import '../models/data_models.dart';
import '../utils/app_theme.dart';
/// 书籍详情页
class BookDetailPage extends StatefulWidget {
final Book book;
const BookDetailPage({super.key, required this.book});
@override
State<BookDetailPage> createState() => _BookDetailPageState();
}
class _BookDetailPageState extends State<BookDetailPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.book.title),
actions: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: () => _navigateToEdit(context),
),
IconButton(
icon: const Icon(Icons.delete_outline),
onPressed: () => _showDeleteDialog(context),
),
],
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 封面区域
_buildCoverSection(context),
// 基本信息
_buildInfoSection(context),
// 笔记区域
if (widget.book.note != null && widget.book.note!.isNotEmpty)
_buildNoteSection(context),
],
),
),
);
}
/// 构建封面区域
Widget _buildCoverSection(BuildContext context) {
return Container(
width: double.infinity,
height: 250,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Theme.of(context).colorScheme.primaryContainer,
Theme.of(context).colorScheme.surface,
],
),
),
child: Stack(
children: [
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.menu_book,
size: 80,
color: Theme.of(context).colorScheme.primary.withOpacity(0.6),
),
const SizedBox(height: 16),
Text(
widget.book.title,
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onSurface,
),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
Positioned(
top: 16,
right: 16,
child: _buildStatusTag(context),
),
],
),
);
}
/// 构建状态标签
Widget _buildStatusTag(BuildContext context) {
Color statusColor;
String statusText;
switch (widget.book.status) {
case 'read':
statusColor = AppTheme.readColor;
statusText = '读完';
break;
case 'reading':
statusColor = AppTheme.readingColor;
statusText = '在读';
break;
case 'want_to_read':
statusColor = AppTheme.wantToReadColor;
statusText = '准备读';
break;
default:
statusColor = Colors.grey;
statusText = '未知';
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.9),
borderRadius: BorderRadius.circular(20),
),
child: Text(
statusText,
style: const TextStyle(
fontSize: 14,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
);
}
/// 构建信息区域
Widget _buildInfoSection(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 作者
if (widget.book.author != null && widget.book.author!.isNotEmpty) ...[
_buildInfoItem(
context,
icon: Icons.person,
label: widget.book.author!,
),
const SizedBox(height: 12),
],
// 评分
if (widget.book.rating != null) ...[
_buildInfoItem(
context,
icon: Icons.star,
label: widget.book.rating.toString(),
iconColor: Colors.amber[700],
textColor: Colors.amber[700],
),
const SizedBox(height: 12),
],
// 阅读日期
if (widget.book.readDate != null)
_buildInfoItem(
context,
icon: Icons.event,
label: '阅读日期:${_formatDate(widget.book.readDate!)}',
),
],
),
);
}
/// 构建信息项
Widget _buildInfoItem(
BuildContext context, {
required IconData icon,
required String label,
Color? iconColor,
Color? textColor,
}) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 18, color: iconColor),
const SizedBox(width: 4),
Text(
label,
style: TextStyle(
fontSize: 14,
color: textColor ?? Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
);
}
/// 构建笔记区域
Widget _buildNoteSection(BuildContext context) {
return Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.edit_note,
size: 20,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 8),
Text(
'笔记',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 12),
Text(
widget.book.note!,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
);
}
/// 格式化日期
String _formatDate(DateTime date) {
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
}
/// 跳转到编辑页面
void _navigateToEdit(BuildContext context) {
Navigator.pushNamed(context, '/book-form', arguments: widget.book).then((_) {
context.read<AppProvider>().loadBooks();
});
}
/// 显示删除对话框
void _showDeleteDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认删除'),
content: Text('确定要删除"${widget.book.title}"吗?此操作不可恢复。'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: () async {
await context.read<AppProvider>().removeBook(widget.book.id);
if (!mounted) return;
Navigator.pop(context);
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('已删除'),
behavior: SnackBarBehavior.floating,
),
);
},
child: const Text(
'删除',
style: TextStyle(color: Colors.red),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,287 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/app_provider.dart';
import '../models/data_models.dart';
/// 添加/编辑书籍记录页面
class BookFormPage extends StatefulWidget {
final Book? book; // 如果为 null则是添加模式否则是编辑模式
const BookFormPage({super.key, this.book});
@override
State<BookFormPage> createState() => _BookFormPageState();
}
class _BookFormPageState extends State<BookFormPage> {
final _formKey = GlobalKey<FormState>();
late TextEditingController _titleController;
late TextEditingController _authorController;
late TextEditingController _ratingController;
late TextEditingController _noteController;
late String _status;
DateTime? _readDate;
@override
void initState() {
super.initState();
_titleController = TextEditingController(text: widget.book?.title ?? '');
_authorController = TextEditingController(text: widget.book?.author ?? '');
_ratingController = TextEditingController(text: widget.book?.rating?.toString() ?? '');
_noteController = TextEditingController(text: widget.book?.note ?? '');
_status = widget.book?.status ?? 'want_to_read';
_readDate = widget.book?.readDate;
}
@override
void dispose() {
_titleController.dispose();
_authorController.dispose();
_ratingController.dispose();
_noteController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final isEdit = widget.book != null;
return Scaffold(
appBar: AppBar(
title: Text(isEdit ? '编辑书籍' : '添加书籍'),
actions: [
IconButton(
icon: const Icon(Icons.save),
onPressed: _saveBook,
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题
TextFormField(
controller: _titleController,
decoration: const InputDecoration(
labelText: '书名 *',
hintText: '请输入书名',
prefixIcon: Icon(Icons.menu_book),
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return '请输入书名';
}
return null;
},
),
const SizedBox(height: 16),
// 作者
TextFormField(
controller: _authorController,
decoration: const InputDecoration(
labelText: '作者',
hintText: '请输入作者',
prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
// 年份和评分
Row(
children: [
Expanded(
child: TextFormField(
controller: _ratingController,
decoration: const InputDecoration(
labelText: '评分',
hintText: '0-10',
prefixIcon: Icon(Icons.star),
border: OutlineInputBorder(),
),
keyboardType: const TextInputType.numberWithOptions(decimal: true),
validator: (value) {
if (value != null && value.isNotEmpty) {
final rating = double.tryParse(value);
if (rating == null || rating < 0 || rating > 10) {
return '评分必须在 0-10 之间';
}
}
return null;
},
),
),
],
),
const SizedBox(height: 16),
// 状态选择
DropdownButtonFormField<String>(
value: _status,
decoration: const InputDecoration(
labelText: '状态',
prefixIcon: Icon(Icons.check_circle_outline),
border: OutlineInputBorder(),
),
items: const [
DropdownMenuItem(value: 'read', child: Text('读完')),
DropdownMenuItem(value: 'reading', child: Text('在读')),
DropdownMenuItem(value: 'want_to_read', child: Text('准备读')),
],
onChanged: (value) {
setState(() {
_status = value!;
});
},
),
const SizedBox(height: 16),
// 阅读日期选择
InkWell(
onTap: _selectReadDate,
child: InputDecorator(
decoration: const InputDecoration(
labelText: '阅读日期',
prefixIcon: Icon(Icons.event),
border: OutlineInputBorder(),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_readDate != null
? '${_readDate!.year}-${_readDate!.month.toString().padLeft(2, '0')}-${_readDate!.day.toString().padLeft(2, '0')}'
: '选择日期',
style: TextStyle(
color: _readDate != null
? Theme.of(context).colorScheme.onSurface
: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
if (_readDate != null)
IconButton(
icon: const Icon(Icons.clear, size: 20),
onPressed: () {
setState(() {
_readDate = null;
});
},
),
],
),
),
),
const SizedBox(height: 16),
// 笔记
TextFormField(
controller: _noteController,
decoration: const InputDecoration(
labelText: '笔记',
hintText: '写下你的读后感...',
prefixIcon: Icon(Icons.edit_note),
border: OutlineInputBorder(),
alignLabelWithHint: true,
),
maxLines: 5,
),
const SizedBox(height: 32),
// 保存按钮
SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton.icon(
onPressed: _saveBook,
icon: const Icon(Icons.save),
label: Text(isEdit ? '保存修改' : '添加记录'),
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
),
],
),
),
),
);
}
/// 选择阅读日期
Future<void> _selectReadDate() async {
final picked = await showDatePicker(
context: context,
initialDate: _readDate ?? DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime.now(),
);
if (picked != null) {
setState(() {
_readDate = picked;
});
}
}
/// 保存书籍记录
Future<void> _saveBook() async {
if (!_formKey.currentState!.validate()) {
return;
}
final rating = _ratingController.text.isNotEmpty ? double.tryParse(_ratingController.text) : null;
if (widget.book == null) {
// 添加新模式
final newBook = Book(
id: DateTime.now().millisecondsSinceEpoch.toString(),
title: _titleController.text.trim(),
author: _authorController.text.trim(),
rating: rating,
status: _status,
readDate: _readDate,
note: _noteController.text.trim(),
);
await context.read<AppProvider>().addBook(newBook);
} else {
// 编辑现有模式
final updatedBook = Book(
id: widget.book!.id,
title: _titleController.text.trim(),
author: _authorController.text.trim(),
rating: rating,
status: _status,
readDate: _readDate,
note: _noteController.text.trim(),
);
await context.read<AppProvider>().updateBook(updatedBook);
}
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(widget.book == null ? '添加成功' : '更新成功'),
behavior: SnackBarBehavior.floating,
),
);
Navigator.pop(context);
}
}

View File

@@ -0,0 +1,142 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/app_provider.dart';
import '../models/data_models.dart';
import '../widgets/book_status_bar.dart';
import '../widgets/book_list_item.dart';
/// 阅读标签页
class BookTabPage extends StatelessWidget {
const BookTabPage({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
// 状态选择栏(读完、在读、准备读)
const BookStatusBar(),
// 书籍列表
Expanded(
child: _buildBookList(context),
),
],
);
}
/// 构建书籍列表
Widget _buildBookList(BuildContext context) {
return Consumer<AppProvider>(
builder: (context, provider, child) {
// 根据状态筛选书籍
final statusMap = {
0: 'read',
1: 'reading',
2: 'want_to_read',
};
final currentStatus = statusMap[provider.bookStatusIndex]!;
final books = provider.getBooksByStatus(currentStatus);
if (books.isEmpty) {
return _buildEmptyState(context, provider.bookStatusIndex);
}
return RefreshIndicator(
onRefresh: () async => await provider.loadBooks(),
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: books.length,
itemBuilder: (context, index) {
return BookListItem(book: books[index]);
},
),
);
},
);
}
/// 构建空状态提示
Widget _buildEmptyState(BuildContext context, int statusIndex) {
final statusText = ['读完', '在读', '准备读'][statusIndex];
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.menu_book_outlined,
size: 80,
color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.3),
),
const SizedBox(height: 16),
Text(
'暂无$statusText的书籍',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 8),
ElevatedButton.icon(
icon: const Icon(Icons.add),
label: const Text('添加记录'),
onPressed: () {
Navigator.pushNamed(context, '/book-form');
},
),
],
),
);
}
/// 获取示例书籍数据
List<Book> _getSampleBooks(int statusIndex) {
final statusMap = ['read', 'reading', 'want_to_read'];
final currentStatus = statusMap[statusIndex];
// 示例数据(实际应从数据库获取)
final allBooks = [
Book(
id: '1',
title: '活着',
author: '余华',
rating: 9.2,
status: 'read',
readDate: DateTime(2024, 1, 20),
note: '非常感人的故事,让人思考生命的意义',
),
Book(
id: '2',
title: '百年孤独',
author: '加西亚·马尔克斯',
rating: 9.3,
status: 'read',
readDate: DateTime(2024, 2, 15),
),
Book(
id: '3',
title: '人类简史',
author: '尤瓦尔·赫拉利',
rating: 9.0,
status: 'reading',
),
Book(
id: '4',
title: '三体',
author: '刘慈欣',
rating: 9.5,
status: 'want_to_read',
),
Book(
id: '5',
title: '追风筝的人',
author: '卡勒德·胡赛尼',
rating: 8.9,
status: 'read',
readDate: DateTime(2024, 3, 5),
note: '关于救赎与成长的故事',
),
];
return allBooks.where((b) => b.status == currentStatus).toList();
}
}

247
lib/pages/home_page.dart Normal file
View File

@@ -0,0 +1,247 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/app_provider.dart';
import '../widgets/custom_drawer.dart';
import '../widgets/bottom_nav_bar.dart';
import 'movie_tab_page.dart';
import 'book_tab_page.dart';
import 'note_tab_page.dart';
/// 主页 - 包含顶部菜单、三个标签页、底部导航
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
// 左侧弹出菜单
drawer: const CustomDrawer(),
// 主体内容
body: Column(
children: [
// 顶部 AppBar
_buildAppBar(),
// 三个标签页的标题栏
_buildTabBar(),
// 标签页内容
Expanded(
child: _buildTabContent(),
),
],
),
// 底部导航栏
bottomNavigationBar: const CustomBottomNavBar(),
);
}
/// 构建顶部 AppBar
Widget _buildAppBar() {
return Consumer<AppProvider>(
builder: (context, provider, child) {
return AppBar(
title: Text(_getAppBarTitle(provider)),
actions: [
// 添加按钮
IconButton(
icon: const Icon(Icons.add),
onPressed: () => _showAddDialog(context, provider),
),
IconButton(
icon: const Icon(Icons.search),
onPressed: () {
// TODO: 搜索功能
},
),
],
);
},
);
}
/// 获取 AppBar 标题
String _getAppBarTitle(AppProvider provider) {
switch (provider.mainTabIndex) {
case 0:
return '观影';
case 1:
return '阅读';
case 2:
return '笔记';
default:
return 'MookNote';
}
}
/// 构建标签栏
Widget _buildTabBar() {
return Consumer<AppProvider>(
builder: (context, provider, child) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Row(
children: [
// 观影
_buildTabItem(
context,
'观影',
Icons.movie,
0,
provider.mainTabIndex,
() => provider.setMainTabIndex(0),
),
// 阅读
_buildTabItem(
context,
'阅读',
Icons.menu_book,
1,
provider.mainTabIndex,
() => provider.setMainTabIndex(1),
),
// 笔记
_buildTabItem(
context,
'笔记',
Icons.note,
2,
provider.mainTabIndex,
() => provider.setMainTabIndex(2),
),
],
),
);
},
);
}
/// 构建单个标签项
Widget _buildTabItem(
BuildContext context,
String label,
IconData icon,
int index,
int currentIndex,
VoidCallback onTap,
) {
final isSelected = index == currentIndex;
final colorScheme = Theme.of(context).colorScheme;
return Expanded(
child: InkWell(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
color: isSelected ? colorScheme.primary : colorScheme.onSurfaceVariant,
size: 24,
),
const SizedBox(height: 4),
Text(
label,
style: TextStyle(
fontSize: 14,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
color: isSelected ? colorScheme.primary : colorScheme.onSurfaceVariant,
),
),
if (isSelected)
Container(
margin: const EdgeInsets.only(top: 8),
width: 32,
height: 3,
decoration: BoxDecoration(
color: colorScheme.primary,
borderRadius: BorderRadius.circular(2),
),
),
],
),
),
),
);
}
/// 构建标签页内容
Widget _buildTabContent() {
return Consumer<AppProvider>(
builder: (context, provider, child) {
switch (provider.mainTabIndex) {
case 0:
return const MovieTabPage();
case 1:
return const BookTabPage();
case 2:
return const NoteTabPage();
default:
return const MovieTabPage();
}
},
);
}
/// 显示添加对话框
void _showAddDialog(BuildContext context, AppProvider provider) {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return SafeArea(
child: Wrap(
children: [
ListTile(
leading: const Icon(Icons.movie, color: Colors.green),
title: const Text('添加观影'),
subtitle: const Text('记录你看过的电影'),
onTap: () {
Navigator.pop(context);
Navigator.pushNamed(context, '/movie-form');
},
),
ListTile(
leading: const Icon(Icons.menu_book, color: Colors.orange),
title: const Text('添加阅读'),
subtitle: const Text('记录你读过的书'),
onTap: () {
Navigator.pop(context);
Navigator.pushNamed(context, '/book-form');
},
),
ListTile(
leading: const Icon(Icons.note, color: Colors.blue),
title: const Text('添加笔记'),
subtitle: const Text('记录你的想法和笔记'),
onTap: () {
Navigator.pop(context);
Navigator.pushNamed(context, '/note-form');
},
),
],
),
);
},
);
}
}

View File

@@ -0,0 +1,284 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/app_provider.dart';
import '../models/data_models.dart';
import '../utils/app_theme.dart';
/// 影视详情页
class MovieDetailPage extends StatefulWidget {
final Movie movie;
const MovieDetailPage({super.key, required this.movie});
@override
State<MovieDetailPage> createState() => _MovieDetailPageState();
}
class _MovieDetailPageState extends State<MovieDetailPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.movie.title),
actions: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: () => _navigateToEdit(context),
),
IconButton(
icon: const Icon(Icons.delete_outline),
onPressed: () => _showDeleteDialog(context),
),
],
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 海报区域
_buildPosterSection(context),
// 基本信息
_buildInfoSection(context),
// 笔记区域
if (widget.movie.note != null && widget.movie.note!.isNotEmpty)
_buildNoteSection(context),
],
),
),
);
}
/// 构建海报区域
Widget _buildPosterSection(BuildContext context) {
return Container(
width: double.infinity,
height: 300,
decoration: BoxDecoration(
color: Colors.grey[300],
),
child: Stack(
children: [
Center(
child: Icon(
Icons.movie,
size: 80,
color: Colors.grey[500],
),
),
Positioned(
top: 16,
right: 16,
child: _buildStatusTag(context),
),
],
),
);
}
/// 构建状态标签
Widget _buildStatusTag(BuildContext context) {
Color statusColor;
String statusText;
switch (widget.movie.status) {
case 'watched':
statusColor = AppTheme.watchedColor;
statusText = '已看';
break;
case 'want_to_watch':
statusColor = AppTheme.wantToWatchColor;
statusText = '想看';
break;
case 'watching':
statusColor = AppTheme.watchingColor;
statusText = '在看';
break;
default:
statusColor = Colors.grey;
statusText = '未知';
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.9),
borderRadius: BorderRadius.circular(20),
),
child: Text(
statusText,
style: const TextStyle(
fontSize: 14,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
);
}
/// 构建信息区域
Widget _buildInfoSection(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题
Text(
widget.movie.title,
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
// 年份和评分
Row(
children: [
if (widget.movie.year != null) ...[
_buildInfoItem(
context,
icon: Icons.calendar_today,
label: '${widget.movie.year}',
),
const SizedBox(width: 16),
],
if (widget.movie.rating != null) ...[
_buildInfoItem(
context,
icon: Icons.star,
label: widget.movie.rating.toString(),
iconColor: Colors.amber[700],
textColor: Colors.amber[700],
),
],
],
),
const SizedBox(height: 16),
// 观看日期
if (widget.movie.watchDate != null)
_buildInfoItem(
context,
icon: Icons.event,
label: '观看日期:${_formatDate(widget.movie.watchDate!)}',
),
],
),
);
}
/// 构建信息项
Widget _buildInfoItem(
BuildContext context, {
required IconData icon,
required String label,
Color? iconColor,
Color? textColor,
}) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 18, color: iconColor),
const SizedBox(width: 4),
Text(
label,
style: TextStyle(
fontSize: 14,
color: textColor ?? Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
);
}
/// 构建笔记区域
Widget _buildNoteSection(BuildContext context) {
return Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.edit_note,
size: 20,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 8),
Text(
'笔记',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 12),
Text(
widget.movie.note!,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
);
}
/// 格式化日期
String _formatDate(DateTime date) {
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
}
/// 跳转到编辑页面
void _navigateToEdit(BuildContext context) {
Navigator.pushNamed(context, '/movie-form', arguments: widget.movie).then((_) {
// 返回后刷新数据
context.read<AppProvider>().loadMovies();
});
}
/// 显示删除对话框
void _showDeleteDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认删除'),
content: Text('确定要删除"${widget.movie.title}"吗?此操作不可恢复。'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: () async {
await context.read<AppProvider>().removeMovie(widget.movie.id);
if (!context.mounted) return;
Navigator.pop(context); // 关闭对话框
Navigator.pop(context); // 返回上一页
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('已删除'),
behavior: SnackBarBehavior.floating,
),
);
},
child: const Text(
'删除',
style: TextStyle(color: Colors.red),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,290 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/app_provider.dart';
import '../models/data_models.dart';
/// 添加/编辑影视记录页面
class MovieFormPage extends StatefulWidget {
final Movie? movie; // 如果为 null则是添加模式否则是编辑模式
const MovieFormPage({super.key, this.movie});
@override
State<MovieFormPage> createState() => _MovieFormPageState();
}
class _MovieFormPageState extends State<MovieFormPage> {
final _formKey = GlobalKey<FormState>();
late TextEditingController _titleController;
late TextEditingController _yearController;
late TextEditingController _ratingController;
late TextEditingController _noteController;
late String _status;
DateTime? _watchDate;
@override
void initState() {
super.initState();
_titleController = TextEditingController(text: widget.movie?.title ?? '');
_yearController = TextEditingController(text: widget.movie?.year?.toString() ?? '');
_ratingController = TextEditingController(text: widget.movie?.rating?.toString() ?? '');
_noteController = TextEditingController(text: widget.movie?.note ?? '');
_status = widget.movie?.status ?? 'want_to_watch';
_watchDate = widget.movie?.watchDate;
}
@override
void dispose() {
_titleController.dispose();
_yearController.dispose();
_ratingController.dispose();
_noteController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final isEdit = widget.movie != null;
return Scaffold(
appBar: AppBar(
title: Text(isEdit ? '编辑影片' : '添加影片'),
actions: [
IconButton(
icon: const Icon(Icons.save),
onPressed: _saveMovie,
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题
TextFormField(
controller: _titleController,
decoration: const InputDecoration(
labelText: '影片名称 *',
hintText: '请输入影片名称',
prefixIcon: Icon(Icons.movie),
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return '请输入影片名称';
}
return null;
},
),
const SizedBox(height: 16),
// 年份和评分
Row(
children: [
Expanded(
child: TextFormField(
controller: _yearController,
decoration: const InputDecoration(
labelText: '年份',
hintText: '例如2024',
prefixIcon: Icon(Icons.calendar_today),
border: OutlineInputBorder(),
),
keyboardType: TextInputType.number,
),
),
const SizedBox(width: 16),
Expanded(
child: TextFormField(
controller: _ratingController,
decoration: const InputDecoration(
labelText: '评分',
hintText: '0-10',
prefixIcon: Icon(Icons.star),
border: OutlineInputBorder(),
),
keyboardType: const TextInputType.numberWithOptions(decimal: true),
validator: (value) {
if (value != null && value.isNotEmpty) {
final rating = double.tryParse(value);
if (rating == null || rating < 0 || rating > 10) {
return '评分必须在 0-10 之间';
}
}
return null;
},
),
),
],
),
const SizedBox(height: 16),
// 状态选择
DropdownButtonFormField<String>(
value: _status,
decoration: const InputDecoration(
labelText: '状态',
prefixIcon: Icon(Icons.check_circle_outline),
border: OutlineInputBorder(),
),
items: const [
DropdownMenuItem(value: 'watched', child: Text('已看')),
DropdownMenuItem(value: 'want_to_watch', child: Text('想看')),
DropdownMenuItem(value: 'watching', child: Text('在看')),
],
onChanged: (value) {
setState(() {
_status = value!;
});
},
),
const SizedBox(height: 16),
// 观看日期选择
InkWell(
onTap: _selectWatchDate,
child: InputDecorator(
decoration: const InputDecoration(
labelText: '观看日期',
prefixIcon: Icon(Icons.event),
border: OutlineInputBorder(),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_watchDate != null
? '${_watchDate!.year}-${_watchDate!.month.toString().padLeft(2, '0')}-${_watchDate!.day.toString().padLeft(2, '0')}'
: '选择日期',
style: TextStyle(
color: _watchDate != null
? Theme.of(context).colorScheme.onSurface
: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
if (_watchDate != null)
IconButton(
icon: const Icon(Icons.clear, size: 20),
onPressed: () {
setState(() {
_watchDate = null;
});
},
),
],
),
),
),
const SizedBox(height: 16),
// 笔记
TextFormField(
controller: _noteController,
decoration: const InputDecoration(
labelText: '笔记',
hintText: '写下你的观后感...',
prefixIcon: Icon(Icons.edit_note),
border: OutlineInputBorder(),
alignLabelWithHint: true,
),
maxLines: 5,
),
const SizedBox(height: 32),
// 保存按钮
SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton.icon(
onPressed: _saveMovie,
icon: const Icon(Icons.save),
label: Text(isEdit ? '保存修改' : '添加记录'),
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
),
],
),
),
),
);
}
/// 选择观看日期
Future<void> _selectWatchDate() async {
final picked = await showDatePicker(
context: context,
initialDate: _watchDate ?? DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime.now(),
);
if (picked != null) {
setState(() {
_watchDate = picked;
});
}
}
/// 保存影视记录
Future<void> _saveMovie() async {
if (!_formKey.currentState!.validate()) {
return;
}
final year = _yearController.text.isNotEmpty ? int.tryParse(_yearController.text) : null;
final rating = _ratingController.text.isNotEmpty ? double.tryParse(_ratingController.text) : null;
if (widget.movie == null) {
// 添加新模式
final newMovie = Movie(
id: DateTime.now().millisecondsSinceEpoch.toString(),
title: _titleController.text.trim(),
year: year,
rating: rating,
status: _status,
watchDate: _watchDate,
note: _noteController.text.trim(),
);
await context.read<AppProvider>().addMovie(newMovie);
} else {
// 编辑现有模式
final updatedMovie = Movie(
id: widget.movie!.id,
title: _titleController.text.trim(),
year: year,
rating: rating,
status: _status,
watchDate: _watchDate,
note: _noteController.text.trim(),
);
await context.read<AppProvider>().updateMovie(updatedMovie);
}
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(widget.movie == null ? '添加成功' : '更新成功'),
behavior: SnackBarBehavior.floating,
),
);
Navigator.pop(context);
}
}

View File

@@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/app_provider.dart';
import '../models/data_models.dart';
import '../widgets/movie_status_bar.dart';
import '../widgets/movie_list_item.dart';
/// 观影标签页
class MovieTabPage extends StatelessWidget {
const MovieTabPage({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
// 状态选择栏(已看、想看、在看)
const MovieStatusBar(),
// 影片列表
Expanded(
child: _buildMovieList(context),
),
],
);
}
/// 构建影片列表
Widget _buildMovieList(BuildContext context) {
return Consumer<AppProvider>(
builder: (context, provider, child) {
// 根据状态筛选影片
final statusMap = {
0: 'watched',
1: 'want_to_watch',
2: 'watching',
};
final currentStatus = statusMap[provider.movieStatusIndex]!;
final movies = provider.getMoviesByStatus(currentStatus);
if (movies.isEmpty) {
return _buildEmptyState(context, provider.movieStatusIndex);
}
return RefreshIndicator(
onRefresh: () async => await provider.loadMovies(),
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: movies.length,
itemBuilder: (context, index) {
return MovieListItem(movie: movies[index]);
},
),
);
},
);
}
/// 构建空状态提示
Widget _buildEmptyState(BuildContext context, int statusIndex) {
final statusText = ['已看', '想看', '在看'][statusIndex];
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.movie_creation_outlined,
size: 80,
color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.3),
),
const SizedBox(height: 16),
Text(
'暂无$statusText的影片',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 8),
ElevatedButton.icon(
icon: const Icon(Icons.add),
label: const Text('添加记录'),
onPressed: () {
Navigator.pushNamed(context, '/movie-form');
},
),
],
),
);
}
}

View File

@@ -0,0 +1,199 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/app_provider.dart';
import '../models/data_models.dart';
/// 笔记详情页
class NoteDetailPage extends StatefulWidget {
final Note note;
const NoteDetailPage({super.key, required this.note});
@override
State<NoteDetailPage> createState() => _NoteDetailPageState();
}
class _NoteDetailPageState extends State<NoteDetailPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.note.title),
actions: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: () => _navigateToEdit(context),
),
IconButton(
icon: const Icon(Icons.delete_outline),
onPressed: () => _showDeleteDialog(context),
),
],
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标签区域
if (widget.note.tags.isNotEmpty) _buildTagsSection(context),
// 内容区域
_buildContentSection(context),
// 时间信息
_buildTimeSection(context),
],
),
),
);
}
/// 构建标签区域
Widget _buildTagsSection(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
child: Wrap(
spacing: 8,
runSpacing: 8,
children: widget.note.tags.map((tag) {
return Chip(
label: Text(tag),
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
labelStyle: TextStyle(
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
);
}).toList(),
),
);
}
/// 构建内容区域
Widget _buildContentSection(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.note.content,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
height: 1.8,
),
),
],
),
);
}
/// 构建时间信息区域
Widget _buildTimeSection(BuildContext context) {
return Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.access_time,
size: 18,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
const SizedBox(width: 8),
Text(
'创建时间',
style: Theme.of(context).textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 8),
Text(
_formatDateTime(widget.note.createdAt),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 16),
Row(
children: [
Icon(
Icons.edit,
size: 18,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
const SizedBox(width: 8),
Text(
'更新时间',
style: Theme.of(context).textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 8),
Text(
_formatDateTime(widget.note.updatedAt),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
);
}
/// 格式化日期时间
String _formatDateTime(DateTime dateTime) {
return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')} ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}';
}
/// 跳转到编辑页面
void _navigateToEdit(BuildContext context) {
Navigator.pushNamed(context, '/note-form', arguments: widget.note).then((_) {
context.read<AppProvider>().loadNotes();
});
}
/// 显示删除对话框
void _showDeleteDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认删除'),
content: Text('确定要删除"${widget.note.title}"吗?此操作不可恢复。'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: () async {
await context.read<AppProvider>().removeNote(widget.note.id);
if (!mounted) return;
Navigator.pop(context);
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('已删除'),
behavior: SnackBarBehavior.floating,
),
);
},
child: const Text(
'删除',
style: TextStyle(color: Colors.red),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,187 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/app_provider.dart';
import '../models/data_models.dart';
/// 添加/编辑笔记页面
class NoteFormPage extends StatefulWidget {
final Note? note; // 如果为 null则是添加模式否则是编辑模式
const NoteFormPage({super.key, this.note});
@override
State<NoteFormPage> createState() => _NoteFormPageState();
}
class _NoteFormPageState extends State<NoteFormPage> {
final _formKey = GlobalKey<FormState>();
late TextEditingController _titleController;
late TextEditingController _contentController;
late TextEditingController _tagsController;
@override
void initState() {
super.initState();
_titleController = TextEditingController(text: widget.note?.title ?? '');
_contentController = TextEditingController(text: widget.note?.content ?? '');
_tagsController = TextEditingController(
text: widget.note?.tags.join(', ') ?? '',
);
}
@override
void dispose() {
_titleController.dispose();
_contentController.dispose();
_tagsController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final isEdit = widget.note != null;
return Scaffold(
appBar: AppBar(
title: Text(isEdit ? '编辑笔记' : '添加笔记'),
actions: [
IconButton(
icon: const Icon(Icons.save),
onPressed: _saveNote,
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题
TextFormField(
controller: _titleController,
decoration: const InputDecoration(
labelText: '标题 *',
hintText: '请输入笔记标题',
prefixIcon: Icon(Icons.title),
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return '请输入标题';
}
return null;
},
),
const SizedBox(height: 16),
// 标签
TextFormField(
controller: _tagsController,
decoration: const InputDecoration(
labelText: '标签',
hintText: '多个标签用逗号分隔',
prefixIcon: Icon(Icons.local_offer),
border: OutlineInputBorder(),
),
),
const SizedBox(height: 16),
// 内容
TextFormField(
controller: _contentController,
decoration: const InputDecoration(
labelText: '内容 *',
hintText: '请输入笔记内容...',
prefixIcon: Icon(Icons.edit_note),
border: OutlineInputBorder(),
alignLabelWithHint: true,
),
maxLines: 15,
validator: (value) {
if (value == null || value.trim().isEmpty) {
return '请输入内容';
}
return null;
},
),
const SizedBox(height: 32),
// 保存按钮
SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton.icon(
onPressed: _saveNote,
icon: const Icon(Icons.save),
label: Text(isEdit ? '保存修改' : '添加笔记'),
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
),
],
),
),
),
);
}
/// 保存笔记
Future<void> _saveNote() async {
if (!_formKey.currentState!.validate()) {
return;
}
// 解析标签
final tags = _tagsController.text
.split(',')
.map((tag) => tag.trim())
.where((tag) => tag.isNotEmpty)
.toList();
if (widget.note == null) {
// 添加新模式
final now = DateTime.now();
final newNote = Note(
id: now.millisecondsSinceEpoch.toString(),
title: _titleController.text.trim(),
content: _contentController.text.trim(),
tags: tags,
createdAt: now,
updatedAt: now,
);
await context.read<AppProvider>().addNote(newNote);
} else {
// 编辑现有模式
final updatedNote = Note(
id: widget.note!.id,
title: _titleController.text.trim(),
content: _contentController.text.trim(),
tags: tags,
createdAt: widget.note!.createdAt,
updatedAt: DateTime.now(),
);
await context.read<AppProvider>().updateNote(updatedNote);
}
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(widget.note == null ? '添加成功' : '更新成功'),
behavior: SnackBarBehavior.floating,
),
);
Navigator.pop(context);
}
}

View File

@@ -0,0 +1,124 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/app_provider.dart';
import '../models/data_models.dart';
import '../widgets/note_list_item.dart';
/// 笔记标签页
class NoteTabPage extends StatelessWidget {
const NoteTabPage({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
// 笔记列表(无状态筛选,显示所有笔记)
Expanded(
child: _buildNoteList(context),
),
],
);
}
/// 构建笔记列表
Widget _buildNoteList(BuildContext context) {
return Consumer<AppProvider>(
builder: (context, provider, child) {
final notes = provider.notes;
if (notes.isEmpty) {
return _buildEmptyState(context);
}
return RefreshIndicator(
onRefresh: () async => await provider.loadNotes(),
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: notes.length,
itemBuilder: (context, index) {
return NoteListItem(note: notes[index]);
},
),
);
},
);
}
/// 构建空状态提示
Widget _buildEmptyState(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.note_outlined,
size: 80,
color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.3),
),
const SizedBox(height: 16),
Text(
'暂无笔记',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 8),
ElevatedButton.icon(
icon: const Icon(Icons.add),
label: const Text('添加笔记'),
onPressed: () {
Navigator.pushNamed(context, '/note-form');
},
),
],
),
);
}
/// 获取示例笔记数据
List<Note> _getSampleNotes() {
// 示例数据(实际应从数据库获取)
return [
Note(
id: '1',
title: 'Flutter 学习心得',
content: '今天开始学习 Flutter 框架,感觉和 Vue 有很多相似之处,都是声明式 UI组件化开发。Widget 的概念很有趣,一切皆 Widget。',
tags: ['学习', 'Flutter', '编程'],
createdAt: DateTime(2024, 3, 1, 10, 30),
updatedAt: DateTime(2024, 3, 1, 10, 30),
),
Note(
id: '2',
title: '《活着》读后感',
content: '余华的《活着》真的是一部让人深思的作品。福贵的一生经历了太多的苦难,但他依然坚强地活着。生命的意义或许就在于活着本身。',
tags: ['阅读', '感悟', '书籍'],
createdAt: DateTime(2024, 2, 20, 15, 20),
updatedAt: DateTime(2024, 2, 20, 16, 0),
),
Note(
id: '3',
title: '电影《星际穿越》观后感',
content: '诺兰的电影总是充满想象力。《星际穿越》将科幻与亲情完美结合,五维空间的呈现方式令人震撼。配乐也是一绝。',
tags: ['观影', '科幻', '电影'],
createdAt: DateTime(2024, 2, 15, 20, 0),
updatedAt: DateTime(2024, 2, 15, 20, 30),
),
Note(
id: '4',
title: 'Python 数据分析笔记',
content: 'Pandas 库的 DataFrame 操作非常强大,可以方便地进行数据清洗和分析。需要多练习熟练掌握常用操作。',
tags: ['Python', '数据分析', '技术'],
createdAt: DateTime(2024, 1, 10, 9, 0),
updatedAt: DateTime(2024, 1, 10, 9, 30),
),
Note(
id: '5',
title: '生活随笔',
content: '春天来了,天气渐暖。周末去公园散步,看到花开得很好。生活中的小确幸值得记录。',
tags: ['生活', '随笔'],
createdAt: DateTime(2024, 3, 3, 18, 0),
updatedAt: DateTime(2024, 3, 3, 18, 0),
),
];
}
}