v0.1.6版本

This commit is contained in:
DelLevin-Home
2026-03-12 23:33:41 +08:00
parent 235b42c620
commit e181ebf9c6
36 changed files with 5450 additions and 2053 deletions

View File

@@ -12,6 +12,7 @@ class StatisticsPage extends StatefulWidget {
}
class _StatisticsPageState extends State<StatisticsPage> {
int _selectedTab = 0;
@override
Widget build(BuildContext context) {
@@ -26,21 +27,74 @@ class _StatisticsPageState extends State<StatisticsPage> {
final books = provider.books;
final notes = provider.notes;
return _buildOverviewTab(movies, books, notes);
return Column(
children: [
// 标签切换栏
_buildTabBar(),
// 内容区域
Expanded(
child: _selectedTab == 0
? _buildOverviewTab(movies, books, notes)
: _buildTrendTab(movies, books, notes),
),
],
);
},
),
);
}
/// 构建标签栏
Widget _buildTabBar() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
child: Row(
children: [
Expanded(
child: _buildTabItem('概览', 0),
),
const SizedBox(width: 12),
Expanded(
child: _buildTabItem('趋势', 1),
),
],
),
);
}
/// 构建标签项
Widget _buildTabItem(String label, int index) {
final isSelected = _selectedTab == index;
return GestureDetector(
onTap: () => setState(() => _selectedTab = index),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
color: isSelected ? const Color(0xFF1A1A1A) : const Color(0xFFF5F5F5),
borderRadius: BorderRadius.circular(8),
),
child: Text(
label,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500,
color: isSelected ? Colors.white : const Color(0xFF666666),
),
),
),
);
}
/// 概览标签页
Widget _buildOverviewTab(List<Movie> movies, List<Book> books, List<Note> notes) {
final totalMovies = movies.length;
final totalMovies = movies.where((m) => !m.isDeleted).length;
final totalBooks = books.length;
final totalNotes = notes.length;
final watchedMovies = movies.where((m) => m.status == 'watched').length;
final watchingMovies = movies.where((m) => m.status == 'watching').length;
final wantToWatchMovies = movies.where((m) => m.status == 'want_to_watch').length;
final watchedMovies = movies.where((m) => m.status == 'watched' && !m.isDeleted).length;
final watchingMovies = movies.where((m) => m.status == 'watching' && !m.isDeleted).length;
final wantToWatchMovies = movies.where((m) => m.status == 'want_to_watch' && !m.isDeleted).length;
final readBooks = books.where((b) => b.status == 'read').length;
final readingBooks = books.where((b) => b.status == 'reading').length;
@@ -52,14 +106,19 @@ class _StatisticsPageState extends State<StatisticsPage> {
// 总数据卡片
_buildSectionTitle('数据总览'),
const SizedBox(height: 16),
Row(
children: [
Expanded(child: _buildStatCard('影视', totalMovies, Icons.movie_outlined, const Color(0xFF1A1A1A))),
const SizedBox(width: 12),
Expanded(child: _buildStatCard('书籍', totalBooks, Icons.menu_book_outlined, const Color(0xFF666666))),
const SizedBox(width: 12),
Expanded(child: _buildStatCard('笔记', totalNotes, Icons.note_outlined, const Color(0xFF999999))),
],
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: const Color(0xFFF8F8F8),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Expanded(child: _buildStatItem('影视', totalMovies, Icons.movie_outlined)),
Expanded(child: _buildStatItem('书籍', totalBooks, Icons.menu_book_outlined)),
Expanded(child: _buildStatItem('笔记', totalNotes, Icons.note_outlined)),
],
),
),
const SizedBox(height: 32),
@@ -91,33 +150,179 @@ class _StatisticsPageState extends State<StatisticsPage> {
);
}
/// 构建统计卡片
Widget _buildStatCard(String title, int count, IconData icon, Color color) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFF5F5F5),
border: Border.all(color: const Color(0xFFE5E5E5)),
),
child: Column(
children: [
Icon(icon, color: color, size: 24),
const SizedBox(height: 8),
Text(
count.toString(),
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w600,
color: color,
),
/// 趋势标签页
Widget _buildTrendTab(List<Movie> movies, List<Book> books, List<Note> notes) {
final now = DateTime.now();
final thirtyDaysAgo = now.subtract(const Duration(days: 30));
// 生成最近30天的日期列表
final dates = List.generate(30, (index) {
return now.subtract(Duration(days: 29 - index));
});
// 计算每天的新增数量
final movieTrend = dates.map((date) {
return movies.where((m) {
final created = m.createdAt;
return created.year == date.year &&
created.month == date.month &&
created.day == date.day &&
!m.isDeleted;
}).length;
}).toList();
final bookTrend = dates.map((date) {
return books.where((b) {
final created = b.createdAt;
return created.year == date.year &&
created.month == date.month &&
created.day == date.day;
}).length;
}).toList();
final noteTrend = dates.map((date) {
return notes.where((n) {
final created = n.createdAt;
return created.year == date.year &&
created.month == date.month &&
created.day == date.day;
}).length;
}).toList();
// 计算总数
final totalMovies = movieTrend.reduce((a, b) => a + b);
final totalBooks = bookTrend.reduce((a, b) => a + b);
final totalNotes = noteTrend.reduce((a, b) => a + b);
return ListView(
padding: const EdgeInsets.all(24),
children: [
// 30天统计摘要
_buildSectionTitle('近30天新增'),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: const Color(0xFFF8F8F8),
borderRadius: BorderRadius.circular(12),
),
const SizedBox(height: 4),
Text(
title,
style: const TextStyle(fontSize: 12, color: Color(0xFF666666)),
child: Row(
children: [
Expanded(child: _buildTrendSummary('影视', totalMovies)),
Expanded(child: _buildTrendSummary('书籍', totalBooks)),
Expanded(child: _buildTrendSummary('笔记', totalNotes)),
],
),
],
),
),
const SizedBox(height: 32),
// 趋势说明
_buildSectionTitle('数据趋势'),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: const Color(0xFFFAFAFA),
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
_buildTrendRow('影视', movieTrend, const Color(0xFF1A1A1A)),
const SizedBox(height: 16),
_buildTrendRow('书籍', bookTrend, const Color(0xFF666666)),
const SizedBox(height: 16),
_buildTrendRow('笔记', noteTrend, const Color(0xFF999999)),
],
),
),
],
);
}
/// 构建趋势摘要项
Widget _buildTrendSummary(String label, int count) {
return Column(
children: [
Text(
count.toString(),
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.w700,
color: Color(0xFF1A1A1A),
),
),
const SizedBox(height: 4),
Text(
label,
style: const TextStyle(
fontSize: 13,
color: Color(0xFF666666),
),
),
],
);
}
/// 构建趋势行
Widget _buildTrendRow(String label, List<int> data, Color color) {
final maxValue = data.isEmpty ? 1 : data.reduce((a, b) => a > b ? a : b);
final safeMax = maxValue == 0 ? 1 : maxValue;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: color,
),
),
const SizedBox(height: 8),
SizedBox(
height: 40,
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: data.map((value) {
final height = value / safeMax * 40;
return Expanded(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 1),
height: height < 2 ? 2 : height,
decoration: BoxDecoration(
color: color.withOpacity(value == 0 ? 0.1 : 0.6),
borderRadius: BorderRadius.circular(2),
),
),
);
}).toList(),
),
),
],
);
}
/// 构建统计项
Widget _buildStatItem(String title, int count, IconData icon) {
return Column(
children: [
Icon(icon, size: 24, color: const Color(0xFF666666)),
const SizedBox(height: 8),
Text(
count.toString(),
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.w700,
color: Color(0xFF1A1A1A),
),
),
const SizedBox(height: 4),
Text(
title,
style: const TextStyle(fontSize: 13, color: Color(0xFF666666)),
),
],
);
}
@@ -125,29 +330,57 @@ class _StatisticsPageState extends State<StatisticsPage> {
Widget _buildStatusDistribution(List<_StatusData> data) {
final total = data.fold(0, (sum, item) => sum + item.count);
return Column(
children: data.map((item) {
final percentage = total > 0 ? (item.count / total * 100).toStringAsFixed(1) : '0';
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
children: [
Container(
width: 12,
height: 12,
decoration: BoxDecoration(color: item.color),
),
const SizedBox(width: 12),
Text(item.label, style: const TextStyle(fontSize: 14)),
const Spacer(),
Text(
'${item.count} ($percentage%)',
style: const TextStyle(fontSize: 14, color: Color(0xFF666666)),
),
],
),
);
}).toList(),
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: const Color(0xFFFAFAFA),
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: data.map((item) {
final percentage = total > 0 ? (item.count / total * 100).toStringAsFixed(1) : '0';
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
children: [
Container(
width: 10,
height: 10,
decoration: BoxDecoration(
color: item.color,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 12),
Text(
item.label,
style: const TextStyle(
fontSize: 14,
color: Color(0xFF1A1A1A),
),
),
const Spacer(),
Text(
'${item.count}',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF1A1A1A),
),
),
const SizedBox(width: 8),
Text(
'($percentage%)',
style: const TextStyle(
fontSize: 13,
color: Color(0xFF999999),
),
),
],
),
);
}).toList(),
),
);
}
@@ -156,21 +389,22 @@ class _StatisticsPageState extends State<StatisticsPage> {
final now = DateTime.now();
final sevenDaysAgo = now.subtract(const Duration(days: 7));
final recentMovies = movies.where((m) => m.createdAt.isAfter(sevenDaysAgo)).length;
final recentMovies = movies.where((m) => m.createdAt.isAfter(sevenDaysAgo) && !m.isDeleted).length;
final recentBooks = books.where((b) => b.createdAt.isAfter(sevenDaysAgo)).length;
final recentNotes = notes.where((n) => n.createdAt.isAfter(sevenDaysAgo)).length;
return Container(
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
border: Border.all(color: const Color(0xFFE5E5E5)),
color: const Color(0xFFFAFAFA),
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
_buildActivityRow('新增影视', recentMovies, Icons.movie_outlined),
const Divider(height: 24),
const Divider(height: 20, color: Color(0xFFE8E8E8)),
_buildActivityRow('新增书籍', recentBooks, Icons.menu_book_outlined),
const Divider(height: 24),
const Divider(height: 20, color: Color(0xFFE8E8E8)),
_buildActivityRow('新增笔记', recentNotes, Icons.note_outlined),
],
),
@@ -180,26 +414,66 @@ class _StatisticsPageState extends State<StatisticsPage> {
Widget _buildActivityRow(String label, int count, IconData icon) {
return Row(
children: [
Icon(icon, size: 20, color: const Color(0xFF666666)),
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFFE8E8E8), width: 0.5),
),
child: Icon(icon, size: 18, color: const Color(0xFF666666)),
),
const SizedBox(width: 12),
Text(label, style: const TextStyle(fontSize: 14)),
Text(
label,
style: const TextStyle(
fontSize: 14,
color: Color(0xFF1A1A1A),
),
),
const Spacer(),
Text(
'$count',
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
'$count',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Color(0xFF1A1A1A),
),
),
const SizedBox(width: 4),
const Text(
'',
style: TextStyle(
fontSize: 13,
color: Color(0xFF999999),
),
),
],
);
}
Widget _buildSectionTitle(String title) {
return Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF1A1A1A),
),
return Row(
children: [
Container(
width: 4,
height: 16,
decoration: BoxDecoration(
color: const Color(0xFF1A1A1A),
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
Text(
title,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
color: Color(0xFF1A1A1A),
),
),
],
);
}
}