This commit is contained in:
sjk
2025-11-17 14:09:17 +08:00
commit 31e46c5bf6
479 changed files with 109324 additions and 0 deletions

View File

@@ -0,0 +1,188 @@
import 'package:flutter/material.dart';
import '../../models/writing_task.dart';
class WritingFilterBar extends StatelessWidget {
final WritingType? selectedType;
final WritingDifficulty? selectedDifficulty;
final String? sortBy;
final bool isAscending;
final Function(WritingType?) onTypeChanged;
final Function(WritingDifficulty?) onDifficultyChanged;
final Function(String) onSortChanged;
final VoidCallback onClearFilters;
const WritingFilterBar({
super.key,
this.selectedType,
this.selectedDifficulty,
this.sortBy,
this.isAscending = true,
required this.onTypeChanged,
required this.onDifficultyChanged,
required this.onSortChanged,
required this.onClearFilters,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
spreadRadius: 1,
blurRadius: 3,
offset: const Offset(0, 1),
),
],
),
child: Column(
children: [
Row(
children: [
const Text(
'筛选条件',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const Spacer(),
TextButton(
onPressed: onClearFilters,
child: const Text('清除'),
),
],
),
const SizedBox(height: 12),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
_buildTypeFilter(),
const SizedBox(width: 12),
_buildDifficultyFilter(),
const SizedBox(width: 12),
_buildSortFilter(),
],
),
),
],
),
);
}
Widget _buildTypeFilter() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]!),
borderRadius: BorderRadius.circular(20),
color: selectedType != null ? Colors.blue[50] : Colors.white,
),
child: DropdownButtonHideUnderline(
child: DropdownButton<WritingType?>(
value: selectedType,
hint: const Text(
'类型',
style: TextStyle(fontSize: 14),
),
isDense: true,
items: [
const DropdownMenuItem<WritingType?>(
value: null,
child: Text('全部类型'),
),
...WritingType.values.map((type) {
return DropdownMenuItem<WritingType?>(
value: type,
child: Text(type.displayName),
);
}).toList(),
],
onChanged: onTypeChanged,
),
),
);
}
Widget _buildDifficultyFilter() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]!),
borderRadius: BorderRadius.circular(20),
color: selectedDifficulty != null ? Colors.orange[50] : Colors.white,
),
child: DropdownButtonHideUnderline(
child: DropdownButton<WritingDifficulty?>(
value: selectedDifficulty,
hint: const Text(
'难度',
style: TextStyle(fontSize: 14),
),
isDense: true,
items: [
const DropdownMenuItem<WritingDifficulty?>(
value: null,
child: Text('全部难度'),
),
...WritingDifficulty.values.map((difficulty) {
return DropdownMenuItem<WritingDifficulty?>(
value: difficulty,
child: Text(difficulty.displayName),
);
}).toList(),
],
onChanged: onDifficultyChanged,
),
),
);
}
Widget _buildSortFilter() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]!),
borderRadius: BorderRadius.circular(20),
color: sortBy != null ? Colors.green[50] : Colors.white,
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: sortBy,
hint: const Text(
'排序',
style: TextStyle(fontSize: 14),
),
isDense: true,
items: const [
DropdownMenuItem<String>(
value: 'createdAt',
child: Text('创建时间'),
),
DropdownMenuItem<String>(
value: 'difficulty',
child: Text('难度'),
),
DropdownMenuItem<String>(
value: 'timeLimit',
child: Text('时间限制'),
),
DropdownMenuItem<String>(
value: 'wordLimit',
child: Text('字数限制'),
),
],
onChanged: (value) {
if (value != null) {
onSortChanged(value);
}
},
),
),
);
}
}

View File

@@ -0,0 +1,228 @@
import 'package:flutter/material.dart';
import '../../models/writing_stats.dart';
class WritingStatsCard extends StatelessWidget {
final WritingStats stats;
final VoidCallback? onTap;
const WritingStatsCard({
super.key,
required this.stats,
this.onTap,
});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.all(8),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'写作统计',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: _buildStatItem(
'完成任务',
'${stats.completedTasks}',
Icons.task_alt,
Colors.green,
),
),
Expanded(
child: _buildStatItem(
'总字数',
'${stats.totalWords}',
Icons.text_fields,
Colors.blue,
),
),
Expanded(
child: _buildStatItem(
'平均分',
'${stats.averageScore.toStringAsFixed(1)}',
Icons.star,
Colors.orange,
),
),
],
),
const SizedBox(height: 16),
if (stats.taskTypeStats.isNotEmpty) ...[
const Text(
'任务类型分布',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 8),
Column(
children: stats.taskTypeStats.entries.map((entry) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
entry.key,
style: const TextStyle(fontSize: 12),
),
Text(
entry.value.toString(),
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
],
),
);
}).toList(),
),
const SizedBox(height: 12),
],
if (stats.difficultyStats.isNotEmpty) ...[
const Text(
'难度分布',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 8),
Column(
children: stats.difficultyStats.entries.map((entry) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
entry.key,
style: const TextStyle(fontSize: 12),
),
Text(
entry.value.toString(),
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
],
),
);
}).toList(),
),
const SizedBox(height: 12),
],
if (stats.skillAnalysis.criteriaScores.isNotEmpty) ...[
const Text(
'技能分析',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 8),
Column(
children: [
...stats.skillAnalysis.criteriaScores.entries.map((entry) =>
_buildSkillItem(entry.key, entry.value)
).toList(),
],
),
],
],
),
),
),
);
}
Widget _buildStatItem(
String label,
String value,
IconData icon,
Color color,
) {
return Column(
children: [
Icon(
icon,
color: color,
size: 24,
),
const SizedBox(height: 4),
Text(
value,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: color,
),
),
Text(
label,
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
],
);
}
Widget _buildSkillItem(String skill, double score) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
children: [
Expanded(
flex: 2,
child: Text(
skill,
style: const TextStyle(fontSize: 12),
),
),
Expanded(
flex: 3,
child: LinearProgressIndicator(
value: score / 100,
backgroundColor: Colors.grey[300],
valueColor: AlwaysStoppedAnimation<Color>(
_getScoreColor(score),
),
),
),
const SizedBox(width: 8),
Text(
'${score.toStringAsFixed(1)}',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
],
),
);
}
Color _getScoreColor(double score) {
if (score >= 80) return Colors.green;
if (score >= 60) return Colors.orange;
return Colors.red;
}
}