关怀模式

This commit is contained in:
Developer
2026-04-02 22:30:49 +08:00
parent 09fee0694c
commit 7872f2e78a
70 changed files with 4884 additions and 2752 deletions

View File

@@ -1,525 +0,0 @@
# 诗词答题 API 文档
## 基础信息
| 项目 | 说明 |
|------|------|
| 基础URL | `/api.php` |
| 返回格式 | JSON |
| 编码 | UTF-8 |
| 请求方式 | **GET / POST 都支持** |
### 请求方式说明
| 接口 | 推荐方式 | 原因 |
|------|----------|------|
| 获取题目 (question) | **GET** | 读取操作,简单可缓存 |
| 下一题 (next) | **GET** | 读取操作 |
| 获取新题 (fetch) | **GET** | 读取操作 |
| 提交答案 (answer) | **POST** | 提交操作,更规范 |
| 获取提示 (hint) | **GET** | 读取操作 |
| 题目列表 (list) | **GET** | 读取操作 |
| 刷新缓存 (refresh) | **GET** | 管理操作 |
| 状态统计 (stats) | **GET** | 读取操作 |
## 通用返回格式
```json
{
"code": 0,
"msg": "",
"data": { ... }
}
```
| 字段 | 类型 | 说明 |
|------|------|------|
| code | int | 状态码0=成功,其他=错误 |
| msg | string | 提示信息 |
| data | object | 返回数据 |
---
## 接口列表
### 1. 获取题目
**请求**
```
GET /api.php?action=question&id=0
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| action | string | 否 | 默认为 question |
| id | int | 否 | 题目ID默认0 |
**返回**
```json
{
"code": 0,
"msg": "",
"data": {
"id": 0,
"total": 10,
"question": "欲把西湖比西子,\"________\"",
"author": "苏轼",
"type": "江南",
"grade": "小学",
"dynasty": "宋朝",
"options": [
{"index": 1, "content": "山色空蒙雨亦奇"},
{"index": 2, "content": "淡妆浓抹总相宜"},
{"index": 3, "content": "门前流水尚能西"},
{"index": 4, "content": "拄杖无时夜叩门"}
]
}
}
```
---
### 2. 获取下一题(自动进度)
**请求**
```
GET /api.php?action=next
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| action | string | 是 | 固定为 next |
**说明**
- **无需传 id 参数**,系统自动记住当前进度(使用 Session
- 每次刷新自动跳到下一题
- 如果已经是最后一题,自动回到第 0 题(循环)
**使用方式**
| 刷新次数 | 返回的 id |
|----------|-----------|
| 第 1 次 | 1 |
| 第 2 次 | 2 |
| 第 3 次 | 3 |
| ... | ... |
| 第 62 次 | 0 (循环) |
**返回**
```json
{
"code": 0,
"msg": "",
"data": {
"id": 1,
"total": 62,
"question": "人生得意须尽欢,\"__________\"",
"author": "李白",
"type": "豪放",
"grade": "高中",
"dynasty": "唐朝",
"options": [...],
"prev_id": 0
}
}
```
---
### 3. 刷新获取新题(推荐)
**请求**
```
GET /api.php?action=fetch
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| action | string | 是 | 固定为 fetch |
**说明**
- **每次刷新都从百度 API 获取新题目**
- 新题目自动写入本地缓存(去重)
- 返回本次获取的随机一题
- API 失败时自动降级使用本地缓存
**返回**
```json
{
"code": 0,
"msg": "",
"data": {
"id": null,
"total": 65,
"question": "人生得意须尽欢,\"__________\"",
"author": "李白",
"type": "豪放",
"grade": "高中",
"dynasty": "唐朝",
"options": [...],
"from_cache": false,
"new_questions": 3
}
}
```
**额外返回字段**
| 字段 | 类型 | 说明 |
|------|------|------|
| from_cache | bool | 是否来自本地缓存 |
| new_questions | int | 本次新增题目数量 |
---
### 4. 提交答案
**请求方式 1GET简单**
```
GET /api.php?action=answer&id=0&answer=2
```
**请求方式 2POST推荐**
```
POST /api.php
Content-Type: application/x-www-form-urlencoded
action=answer&id=0&answer=2
```
或使用 JSON
```
POST /api.php
Content-Type: application/json
{
"action": "answer",
"id": 0,
"answer": "2"
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| action | string | 是 | 固定为 answer |
| id | int | 是 | 题目ID |
| answer | string | 是 | 答案序号(1-4) |
**返回**
```json
{
"code": 0,
"msg": "",
"data": {
"id": 0,
"correct": true,
"your_answer": "2",
"correct_answer": "2",
"next_id": 1,
"has_next": true
}
}
```
---
### 5. 获取提示
**请求**
```
GET /api.php?action=hint&id=0
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| action | string | 是 | 固定为 hint |
| id | int | 是 | 题目ID |
**返回**
```json
{
"code": 0,
"msg": "",
"data": {
"id": 0,
"hint": "这是首描写江南的诗,你在小学学过它。",
"author": "苏轼",
"dynasty": "宋朝"
}
}
```
---
### 6. 获取题目列表
**请求**
```
GET /api.php?action=list
```
**返回**
```json
{
"code": 0,
"msg": "",
"data": {
"total": 10,
"list": [
{
"id": 0,
"question": "欲把西湖比西子,\"________\"...",
"author": "苏轼",
"dynasty": "宋朝"
}
]
}
}
```
---
### 7. 刷新缓存
**请求**
```
GET /api.php?action=refresh
```
**说明**
- 强制从百度 API 获取最新题目
- 合并到本地缓存(去重)
**返回**
```json
{
"code": 0,
"msg": "",
"data": {
"refreshed": true,
"total": 15
}
}
```
---
## 缓存机制
| 配置 | 值 | 说明 |
|------|------|------|
| 缓存文件 | `data/questions.json` | 本地JSON文件 |
| 过期时间 | **永不过期** | 永久保存 |
| 去重方式 | 按 `question_content` | 相同题目不重复存储 |
| 降级策略 | API失败自动使用本地缓存 | 原始URL失效也能使用 |
| API 限频 | 5秒内只请求一次 | 防止并发请求百度 |
### 缓存文件结构
```json
{
"updated": "2024-01-01 12:00:00",
"count": 10,
"questions": [...]
}
```
---
## 性能优化
### 已实现优化
| 优化项 | 说明 |
|--------|------|
| 静态变量缓存 | 同一请求内多次读取只加载一次文件 |
| API 请求锁 | 5秒内多人请求只发一次 API |
| 原子写入 | 先写临时文件再 rename防止数据损坏 |
| 去重优化 | 只在写入时去重,读取时不处理 |
| 超时控制 | API 超时 5 秒,连接超时 3 秒 |
### 性能预估
| 场景 | 响应时间 |
|------|----------|
| 读取本地缓存 | ~2-5ms |
| API 请求成功 | ~200-500ms |
| API 限频降级 | ~1ms直接读缓存 |
| 100人并发 | 无压力(读缓存为主) |
### 查看状态
```
GET /api.php?action=stats
```
返回:
```json
{
"code": 0,
"data": {
"total_questions": 149,
"cache_file_size": "40.5 KB",
"last_updated": "2026-03-29 05:04:04",
"memory_usage": "256 KB"
}
}
```
---
## 错误码
| code | 说明 |
|------|------|
| 0 | 成功 |
| 400 | 参数错误 |
| 404 | 题目不存在 |
---
## 使用示例
### JavaScript
```javascript
// 获取第一题
fetch('/api.php?action=question&id=0')
.then(r => r.json())
.then(d => console.log(d.data.question));
// 获取下一题(自动进度,无需传 id
fetch('/api.php?action=next')
.then(r => r.json())
.then(d => console.log(d.data.id, d.data.question));
// 提交答案
fetch('/api.php?action=answer&id=0&answer=2')
.then(r => r.json())
.then(d => {
if(d.data.correct) {
console.log('回答正确!');
}
});
```
### Flutter/Dart
```dart
import 'package:dio/dio.dart';
final dio = Dio();
// 获取题目
Future<Map> getQuestion(int id) async {
final res = await dio.get('/api.php', queryParameters: {
'action': 'question',
'id': id,
});
return res.data['data'];
}
// 获取下一题(自动进度,无需传 id
Future<Map> getNextQuestion() async {
final res = await dio.get('/api.php', queryParameters: {
'action': 'next',
});
return res.data['data'];
}
// 提交答案POST 方式)
Future<bool> checkAnswer(int id, String answer) async {
final res = await dio.post('/api.php', data: {
'action': 'answer',
'id': id,
'answer': answer,
});
return res.data['data']['correct'];
}
```
---
## App 集成建议
### 方案 1App 自己管理进度(推荐)
使用 `?action=question` 接口App 完全控制进度:
```dart
class QuizApi {
int _currentId = 0;
int _total = 0;
Future<Map> getQuestion() async {
final res = await dio.get('/api.php', queryParameters: {
'action': 'question',
'id': _currentId,
});
final data = res.data['data'];
_total = data['total'] ?? 0;
return data;
}
Future<Map> submitAnswer(String answer) async {
final res = await dio.post('/api.php', data: {
'action': 'answer',
'id': _currentId,
'answer': answer,
});
return res.data['data'];
}
void nextQuestion() {
_currentId++;
if (_currentId >= _total) {
_currentId = 0;
}
}
int get currentId => _currentId;
}
```
**优点**
- 不依赖 SessionApp 完全控制进度
- 可以随时跳转任意题目
- 适合多端同步
---
### 方案 2使用自动进度
使用 `?action=next` 接口API 自动管理:
```dart
Future<Map> getNextQuestion() async {
final res = await dio.get('/api.php', queryParameters: {
'action': 'next',
});
return res.data['data'];
}
```
**优点**
- 简单,无需管理 ID
- 自动循环
**缺点**
- 依赖 Session需要保持 cookie
- 无法自由跳转题目
---
### 方案 3不断获取新题
使用 `?action=fetch` 接口,每次都从百度获取新题:
```dart
Future<Map> getNewQuestion() async {
final res = await dio.get('/api.php', queryParameters: {
'action': 'fetch',
});
return res.data['data'];
}
```
**适用场景**
- 想要不断扩展题库
- 用户每次刷新都可能看到新题

View File

@@ -1,3 +1,8 @@
/// 时间: 2026-03-28
/// 功能: 答题记录页面
/// 介绍: 显示用户的诗词答题记录列表,包括题目、标签、是否答对等信息
/// 最新变化: 添加统计数据弹窗功能,支持主题色设置
import 'dart:convert';
import 'package:flutter/material.dart';
@@ -5,17 +10,12 @@ import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import '../../../constants/app_constants.dart';
import '../../../models/colors/app_colors.dart';
import '../../../controllers/shared_preferences_storage_controller.dart';
import '../../../controllers/history_controller.dart';
import '../../../services/network_listener_service.dart';
import '../../../services/get/theme_controller.dart';
/// 时间: 2026-03-28
/// 功能: 答题记录页面
/// 介绍: 显示用户的诗词答题记录列表,包括题目、标签、是否答对等信息
/// 最新变化: 添加统计数据弹窗功能
class DistinguishPage extends StatefulWidget {
const DistinguishPage({super.key});
@@ -142,239 +142,234 @@ class _DistinguishPageState extends State<DistinguishPage> {
}
void _showStatisticsDialog() {
final isDark = _themeController.isDarkMode;
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => _buildStatisticsSheet(isDark),
builder: (context) => _buildStatisticsSheet(),
);
}
Widget _buildStatisticsSheet(bool isDark) {
return Container(
decoration: BoxDecoration(
color: isDark ? const Color(0xFF2A2A2A) : Colors.white,
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: isDark ? Colors.grey[600] : Colors.grey[300],
borderRadius: BorderRadius.circular(2),
Widget _buildStatisticsSheet() {
return Obx(() {
final isDark = _themeController.isDarkMode;
return Container(
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: AppColors.divider,
borderRadius: BorderRadius.circular(2),
),
),
),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppConstants.primaryColor.withAlpha(20),
borderRadius: BorderRadius.circular(10),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppColors.primary.withAlpha(20),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
Icons.analytics_outlined,
color: AppColors.primary,
size: 24,
),
),
child: Icon(
Icons.analytics_outlined,
color: AppConstants.primaryColor,
size: 24,
),
),
const SizedBox(width: 12),
Text(
'本次答题记录',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: isDark ? Colors.white : Colors.black87,
),
),
],
),
if (_answerRecords.isNotEmpty)
Container(
decoration: BoxDecoration(
color: AppConstants.primaryColor.withAlpha(15),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: AppConstants.primaryColor.withAlpha(50),
width: 0.5,
),
),
child: TextButton.icon(
onPressed: _copyStatisticsContent,
icon: Icon(
Icons.copy,
size: 16,
color: Colors.orange[700],
),
label: Text(
'添加笔记',
const SizedBox(width: 12),
Text(
'本次答题记录',
style: TextStyle(
fontSize: 12,
fontSize: 22,
fontWeight: FontWeight.bold,
color: AppColors.primaryText,
),
),
],
),
if (_answerRecords.isNotEmpty)
Container(
decoration: BoxDecoration(
color: AppColors.primary.withAlpha(15),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: AppColors.primary.withAlpha(50),
width: 0.5,
),
),
child: TextButton.icon(
onPressed: _copyStatisticsContent,
icon: Icon(
Icons.copy,
size: 16,
color: Colors.orange[700],
),
),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
label: Text(
'添加笔记',
style: TextStyle(
fontSize: 12,
color: Colors.orange[700],
),
),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
),
),
],
),
const SizedBox(height: 24),
Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppConstants.primaryColor.withAlpha(10),
isDark ? const Color(0xFF2A2A2A) : Colors.white,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: AppConstants.primaryColor.withAlpha(30),
width: 1,
),
),
child: Column(
children: [
_buildStatRow('已答题', '$_totalQuestions', isDark),
_buildStatRow(
'正确',
'$_correctAnswers',
isDark,
isGreen: true,
),
_buildStatRow(
'错误',
'$_wrongAnswers',
isDark,
isRed: true,
),
_buildStatRow(
'正确率',
'${_correctRate.toStringAsFixed(1)}%',
isDark,
isGreen: _correctRate >= 60,
),
_buildStatRow(
'错误率',
'${_wrongRate.toStringAsFixed(1)}%',
isDark,
isRed: _wrongRate > 40,
),
_buildStatRow(
'平均用时',
'${_averageTime.toStringAsFixed(1)}',
isDark,
),
_buildStatRow('提示次数', '$_hintCount', isDark),
_buildStatRow('跳过次数', '$_skipCount', isDark),
Divider(
height: 24,
color: isDark ? Colors.grey[700] : Colors.grey[300],
),
_buildStatRow(
'诗词水平',
_poetryLevel,
isDark,
isHighlight: true,
),
],
),
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: Container(
const SizedBox(height: 24),
Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppConstants.primaryColor,
AppConstants.primaryColor.withAlpha(200),
AppColors.primary.withAlpha(10),
AppColors.surface,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: AppConstants.primaryColor.withAlpha(80),
blurRadius: 12,
offset: const Offset(0, 4),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: AppColors.primary.withAlpha(30),
width: 1,
),
),
child: Column(
children: [
_buildStatRow('已答题', '$_totalQuestions'),
_buildStatRow(
'正确',
'$_correctAnswers',
isGreen: true,
),
_buildStatRow(
'错误',
'$_wrongAnswers',
isRed: true,
),
_buildStatRow(
'正确率',
'${_correctRate.toStringAsFixed(1)}%',
isGreen: _correctRate >= 60,
),
_buildStatRow(
'错误率',
'${_wrongRate.toStringAsFixed(1)}%',
isRed: _wrongRate > 40,
),
_buildStatRow(
'平均用时',
'${_averageTime.toStringAsFixed(1)}',
),
_buildStatRow('提示次数', '$_hintCount'),
_buildStatRow('跳过次数', '$_skipCount'),
Divider(
height: 24,
color: AppColors.divider,
),
_buildStatRow(
'诗词水平',
_poetryLevel,
isHighlight: true,
),
],
),
child: ElevatedButton(
onPressed: _copyStatisticsToClipboard,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppColors.primary,
AppColors.primary.withAlpha(200),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.copy, color: Colors.white, size: 20),
SizedBox(width: 8),
Text(
'复制数据发送给AI评估',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: AppColors.primary.withAlpha(80),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: ElevatedButton(
onPressed: _copyStatisticsToClipboard,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.copy, color: Colors.white, size: 20),
SizedBox(width: 8),
Text(
'复制数据发送给AI评估',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
],
),
),
),
),
),
const SizedBox(height: 16),
],
const SizedBox(height: 16),
],
),
),
),
),
);
);
});
}
Widget _buildStatRow(
String label,
String value,
bool isDark, {
String value, {
bool isGreen = false,
bool isRed = false,
bool isHighlight = false,
}) {
Color valueColor = isDark ? Colors.white : Colors.black87;
if (isGreen) valueColor = Colors.green;
if (isRed) valueColor = Colors.red;
if (isHighlight) valueColor = AppConstants.primaryColor;
Color valueColor = AppColors.primaryText;
if (isGreen) valueColor = AppColors.iosGreen;
if (isRed) valueColor = AppColors.iosRed;
if (isHighlight) valueColor = AppColors.primary;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
@@ -385,7 +380,7 @@ class _DistinguishPageState extends State<DistinguishPage> {
label,
style: TextStyle(
fontSize: 15,
color: isDark ? Colors.grey[400] : Colors.grey[700],
color: AppColors.secondaryText,
),
),
Text(
@@ -456,16 +451,17 @@ $_poetryLevel
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认清空'),
content: const Text('确定要清空所有答题记录吗?此操作不可恢复。'),
backgroundColor: AppColors.surface,
title: Text('确认清空', style: TextStyle(color: AppColors.primaryText)),
content: Text('确定要清空所有答题记录吗?此操作不可恢复。', style: TextStyle(color: AppColors.secondaryText)),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
child: Text('取消', style: TextStyle(color: AppColors.primary)),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('确定', style: TextStyle(color: Colors.red)),
child: Text('确定', style: TextStyle(color: AppColors.iosRed)),
),
],
),
@@ -511,18 +507,18 @@ $_poetryLevel
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
color: isDark ? Colors.white : Colors.white,
color: Colors.white,
),
),
backgroundColor: AppConstants.primaryColor,
backgroundColor: AppColors.primary,
foregroundColor: Colors.white,
elevation: 0,
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppConstants.primaryColor,
AppConstants.primaryColor.withAlpha(180),
AppColors.primary,
AppColors.primary.withAlpha(180),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
@@ -544,239 +540,246 @@ $_poetryLevel
],
),
body: Container(
color: isDark ? const Color(0xFF1A1A1A) : Colors.grey[50],
color: AppColors.background,
child: SafeArea(
child: _isLoading
? const Center(child: CircularProgressIndicator())
: _answerRecords.isEmpty
? _buildEmptyView(isDark)
: _buildRecordList(isDark),
? _buildEmptyView()
: _buildRecordList(),
),
),
);
});
}
Widget _buildEmptyView(bool isDark) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.menu_book_outlined,
size: 80,
color: isDark ? Colors.grey[600] : Colors.grey[300],
),
const SizedBox(height: 16),
Text(
'暂无答题记录',
style: TextStyle(
fontSize: 18,
color: isDark ? Colors.grey[400] : Colors.grey[500],
fontWeight: FontWeight.w500,
Widget _buildEmptyView() {
return Obx(() {
final isDark = _themeController.isDarkMode;
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.menu_book_outlined,
size: 80,
color: AppColors.tertiaryText,
),
),
const SizedBox(height: 8),
Text(
'快去答题吧!',
style: TextStyle(
fontSize: 14,
color: isDark ? Colors.grey[500] : Colors.grey[400],
const SizedBox(height: 16),
Text(
'暂无答题记录',
style: TextStyle(
fontSize: 18,
color: AppColors.secondaryText,
fontWeight: FontWeight.w500,
),
),
),
],
),
);
const SizedBox(height: 8),
Text(
'快去答题吧!',
style: TextStyle(
fontSize: 14,
color: AppColors.tertiaryText,
),
),
],
),
);
});
}
Widget _buildRecordList(bool isDark) {
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _answerRecords.length,
itemBuilder: (context, index) {
final record = _answerRecords[index];
return _buildRecordCard(record, index, isDark);
},
);
Widget _buildRecordList() {
return Obx(() {
final isDark = _themeController.isDarkMode;
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _answerRecords.length,
itemBuilder: (context, index) {
final record = _answerRecords[index];
return _buildRecordCard(record, index);
},
);
});
}
Widget _buildRecordCard(Map<String, dynamic> record, int index, bool isDark) {
Widget _buildRecordCard(Map<String, dynamic> record, int index) {
final question = record['question'] ?? '未知题目';
final author = record['author'] ?? '未知作者';
final tags = (record['tags'] as List<dynamic>?)?.cast<String>() ?? [];
final isCorrect = record['isCorrect'] ?? false;
final answerTime = _formatTime(record['answerTime']);
return Container(
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: isDark ? const Color(0xFF2A2A2A) : Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: isDark
? Colors.black.withAlpha(30)
: Colors.black.withAlpha(10),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 28,
height: 28,
decoration: BoxDecoration(
color: AppConstants.primaryColor.withAlpha(20),
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Text(
'${index + 1}',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: AppConstants.primaryColor,
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
question,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: isDark ? Colors.white : Colors.black87,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
'—— $author',
style: TextStyle(
fontSize: 12,
color: isDark ? Colors.grey[400] : Colors.grey[600],
fontStyle: FontStyle.italic,
),
),
],
),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 4,
),
decoration: BoxDecoration(
color: isCorrect
? Colors.green.withAlpha(isDark ? 40 : 20)
: Colors.red.withAlpha(isDark ? 40 : 20),
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
isCorrect ? Icons.check_circle : Icons.cancel,
size: 14,
color: isCorrect ? Colors.green : Colors.red,
),
const SizedBox(width: 4),
Text(
isCorrect ? '答对' : '答错',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: isCorrect ? Colors.green : Colors.red,
),
),
],
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: tags.isNotEmpty
? Wrap(
spacing: 6,
runSpacing: 6,
children: tags.map((tag) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
),
decoration: BoxDecoration(
color: AppConstants.primaryColor.withAlpha(15),
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: AppConstants.primaryColor.withAlpha(
50,
),
width: 0.5,
),
),
child: Text(
tag,
style: TextStyle(
fontSize: 11,
color: AppConstants.primaryColor,
fontWeight: FontWeight.w500,
),
),
);
}).toList(),
)
: Text(
'暂无标签',
style: TextStyle(
fontSize: 12,
color: isDark ? Colors.grey[500] : Colors.grey[400],
fontStyle: FontStyle.italic,
),
),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.access_time,
size: 12,
color: isDark ? Colors.grey[500] : Colors.grey[400],
),
const SizedBox(width: 4),
Text(
answerTime,
style: TextStyle(
fontSize: 12,
color: isDark ? Colors.grey[400] : Colors.grey[500],
),
),
],
),
],
return Obx(() {
final isDark = _themeController.isDarkMode;
return Container(
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(isDark ? 30 : 10),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
),
);
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 28,
height: 28,
decoration: BoxDecoration(
color: AppColors.primary.withAlpha(20),
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Text(
'${index + 1}',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: AppColors.primary,
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
question,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.primaryText,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
'—— $author',
style: TextStyle(
fontSize: 12,
color: AppColors.secondaryText,
fontStyle: FontStyle.italic,
),
),
],
),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 4,
),
decoration: BoxDecoration(
color: isCorrect
? AppColors.iosGreen.withAlpha(20)
: AppColors.iosRed.withAlpha(20),
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
isCorrect ? Icons.check_circle : Icons.cancel,
size: 14,
color: isCorrect ? AppColors.iosGreen : AppColors.iosRed,
),
const SizedBox(width: 4),
Text(
isCorrect ? '答对' : '答错',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: isCorrect ? AppColors.iosGreen : AppColors.iosRed,
),
),
],
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: tags.isNotEmpty
? Wrap(
spacing: 6,
runSpacing: 6,
children: tags.map((tag) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
),
decoration: BoxDecoration(
color: AppColors.primary.withAlpha(15),
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: AppColors.primary.withAlpha(
50,
),
width: 0.5,
),
),
child: Text(
tag,
style: TextStyle(
fontSize: 11,
color: AppColors.primary,
fontWeight: FontWeight.w500,
),
),
);
}).toList(),
)
: Text(
'暂无标签',
style: TextStyle(
fontSize: 12,
color: AppColors.tertiaryText,
fontStyle: FontStyle.italic,
),
),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.access_time,
size: 12,
color: AppColors.tertiaryText,
),
const SizedBox(width: 4),
Text(
answerTime,
style: TextStyle(
fontSize: 12,
color: AppColors.secondaryText,
),
),
],
),
],
),
],
),
),
);
});
}
// 写入统计数据到笔记

View File

@@ -1,8 +1,9 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constants/app_constants.dart';
import '../../../services/get/theme_controller.dart';
/// 流动边框装饰器
class FlowingBorderDecoration extends Decoration {
@@ -61,7 +62,7 @@ class _FlowingBorderPainter extends BoxPainter {
class FlowingBorderContainer extends StatefulWidget {
final Widget child;
final Duration duration;
final Color color;
final Color? color;
final double width;
final bool autoStart;
@@ -69,7 +70,7 @@ class FlowingBorderContainer extends StatefulWidget {
super.key,
required this.child,
this.duration = const Duration(seconds: 10),
this.color = AppConstants.primaryColor,
this.color,
this.width = 4.0,
this.autoStart = true,
});
@@ -82,6 +83,7 @@ class _FlowingBorderContainerState extends State<FlowingBorderContainer>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
final ThemeController _themeController = Get.find<ThemeController>();
@override
void initState() {
@@ -105,19 +107,22 @@ class _FlowingBorderContainerState extends State<FlowingBorderContainer>
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Container(
padding: EdgeInsets.all(widget.width),
decoration: FlowingBorderDecoration(
animation: _animation,
color: widget.color,
width: widget.width,
),
child: widget.child,
);
},
);
return Obx(() {
final color = widget.color ?? _themeController.currentThemeColor;
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Container(
padding: EdgeInsets.all(widget.width),
decoration: FlowingBorderDecoration(
animation: _animation,
color: color,
width: widget.width,
),
child: widget.child,
);
},
);
});
}
}

View File

@@ -1,10 +1,15 @@
/// 时间: 2026-03-28
/// 功能: 诗词答题页面
/// 介绍: 基于 API 接口实现的诗词答题系统,支持获取题目、提交答案、获取提示
/// 最新变化: 添加自动加载下一题开关,隐藏提示标签,使用独立逻辑管理器,支持主题色设置
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../constants/app_constants.dart';
import '../../../models/colors/app_colors.dart';
import '../../../controllers/shared_preferences_storage_controller.dart';
import '../../../services/get/theme_controller.dart';
import '../guide/tongji.dart';
@@ -13,11 +18,6 @@ import 'flow-anim.dart';
import 'distinguish.dart';
import '../settings/offline-data.dart';
/// 时间: 2026-03-28
/// 功能: 诗词答题页面
/// 介绍: 基于 API 接口实现的诗词答题系统,支持获取题目、提交答案、获取提示
/// 最新变化: 添加自动加载下一题开关,隐藏提示标签,使用独立逻辑管理器
class PoetryLevelPage extends StatefulWidget {
const PoetryLevelPage({super.key});
@@ -137,7 +137,7 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(initResult.message ?? '已加载离线缓存'),
backgroundColor: AppConstants.primaryColor,
backgroundColor: AppColors.primary,
duration: const Duration(seconds: 2),
),
);
@@ -153,14 +153,15 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('提示'),
content: const Text('当前无网络连接且无离线缓存数据,请先下载数据或检查网络设置。'),
backgroundColor: AppColors.surface,
title: Text('提示', style: TextStyle(color: AppColors.primaryText)),
content: Text('当前无网络连接且无离线缓存数据,请先下载数据或检查网络设置。', style: TextStyle(color: AppColors.secondaryText)),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('取消'),
child: Text('取消', style: TextStyle(color: AppColors.primary)),
),
TextButton(
onPressed: () {
@@ -171,7 +172,7 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
MaterialPageRoute(builder: (_) => const OfflineDataPage()),
);
},
child: const Text('去下载'),
child: Text('去下载', style: TextStyle(color: AppColors.primary)),
),
],
);
@@ -464,7 +465,7 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: AppConstants.primaryColor.withAlpha(20),
color: AppColors.primary.withAlpha(20),
borderRadius: BorderRadius.circular(4),
),
child: Column(
@@ -474,7 +475,7 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
label,
style: TextStyle(
fontSize: 10,
color: AppConstants.primaryColor,
color: AppColors.primary,
fontWeight: FontWeight.w600,
),
),
@@ -482,7 +483,7 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
value,
style: TextStyle(
fontSize: 12,
color: Colors.black87,
color: AppColors.primaryText,
fontWeight: FontWeight.w500,
),
),
@@ -500,150 +501,152 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
final isWrong =
_showFeedback && !_isAnswerCorrect && _selectedAnswer == optionNum;
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
child: Container(
decoration: BoxDecoration(
gradient: isSelected
? LinearGradient(
colors: isCorrect
? [Colors.green[400]!, Colors.green[300]!]
: isWrong
? [Colors.red[400]!, Colors.red[300]!]
: [
AppConstants.primaryColor,
AppConstants.primaryColor.withAlpha(200),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
)
: null,
color: isSelected ? null : Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isSelected
? Colors.transparent
: AppConstants.primaryColor.withAlpha(50),
width: 2,
),
boxShadow: isSelected
? [
BoxShadow(
color:
(isCorrect
? Colors.green
: isWrong
? Colors.red
: AppConstants.primaryColor)
.withAlpha(80),
blurRadius: 12,
offset: const Offset(0, 4),
),
]
: [
BoxShadow(
color: Colors.black.withAlpha(5),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: _isSubmitting || (_showFeedback && _isAnswerCorrect)
? null
: () {
if (_showFeedback) {
// 重置状态,允许重新选择
setState(() {
_showFeedback = false;
_selectedAnswer = null;
_feedbackMessage = null;
});
}
_submitAnswer(optionNum);
},
return Obx(() {
final isDark = _themeController.isDarkMode;
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
child: Container(
decoration: BoxDecoration(
gradient: isSelected
? LinearGradient(
colors: isCorrect
? [AppColors.iosGreen, AppColors.iosGreen.withAlpha(200)]
: isWrong
? [AppColors.iosRed, AppColors.iosRed.withAlpha(200)]
: [
AppColors.primary,
AppColors.primary.withAlpha(200),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
)
: null,
color: isSelected ? null : AppColors.surface,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 300),
width: 32,
height: 32,
decoration: BoxDecoration(
gradient: isSelected
? LinearGradient(
colors: isCorrect
? [Colors.white, Colors.white.withAlpha(230)]
: isWrong
? [Colors.white, Colors.white.withAlpha(230)]
: [Colors.white, Colors.white.withAlpha(230)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
)
: null,
color: isSelected
? null
: AppConstants.primaryColor.withAlpha(20),
shape: BoxShape.circle,
boxShadow: isSelected
? [
BoxShadow(
color: Colors.black.withAlpha(20),
blurRadius: 4,
offset: const Offset(0, 2),
),
]
: null,
border: Border.all(
color: isSelected
? Colors.transparent
: AppColors.primary.withAlpha(50),
width: 2,
),
boxShadow: isSelected
? [
BoxShadow(
color: (isCorrect
? AppColors.iosGreen
: isWrong
? AppColors.iosRed
: AppColors.primary)
.withAlpha(80),
blurRadius: 12,
offset: const Offset(0, 4),
),
child: Center(
child: Text(
'$optionNum',
style: TextStyle(
color: isSelected
? (isCorrect
? Colors.green
]
: [
BoxShadow(
color: Colors.black.withAlpha(isDark ? 10 : 5),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: _isSubmitting || (_showFeedback && _isAnswerCorrect)
? null
: () {
if (_showFeedback) {
// 重置状态,允许重新选择
setState(() {
_showFeedback = false;
_selectedAnswer = null;
_feedbackMessage = null;
});
}
_submitAnswer(optionNum);
},
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 300),
width: 32,
height: 32,
decoration: BoxDecoration(
gradient: isSelected
? LinearGradient(
colors: isCorrect
? [Colors.white, Colors.white.withAlpha(230)]
: isWrong
? Colors.red
: AppConstants.primaryColor)
: AppConstants.primaryColor,
fontWeight: FontWeight.bold,
fontSize: 16,
? [Colors.white, Colors.white.withAlpha(230)]
: [Colors.white, Colors.white.withAlpha(230)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
)
: null,
color: isSelected
? null
: AppColors.primary.withAlpha(20),
shape: BoxShape.circle,
boxShadow: isSelected
? [
BoxShadow(
color: Colors.black.withAlpha(20),
blurRadius: 4,
offset: const Offset(0, 2),
),
]
: null,
),
child: Center(
child: Text(
'$optionNum',
style: TextStyle(
color: isSelected
? (isCorrect
? AppColors.iosGreen
: isWrong
? AppColors.iosRed
: AppColors.primary)
: AppColors.primary,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
),
),
),
const SizedBox(width: 16),
Expanded(
child: Text(
option['content'] ?? option['text'] ?? '',
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.w500,
color: isSelected ? Colors.white : Colors.black87,
const SizedBox(width: 16),
Expanded(
child: Text(
option['content'] ?? option['text'] ?? '',
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.w500,
color: isSelected ? Colors.white : AppColors.primaryText,
),
),
),
),
if (isSelected)
Icon(
isCorrect
? Icons.check_circle
: isWrong
? Icons.cancel
: Icons.radio_button_checked,
color: Colors.white,
size: 28,
),
],
if (isSelected)
Icon(
isCorrect
? Icons.check_circle
: isWrong
? Icons.cancel
: Icons.radio_button_checked,
color: Colors.white,
size: 28,
),
],
),
),
),
),
),
),
);
);
});
}
@override
@@ -660,15 +663,15 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
color: Colors.white,
),
),
backgroundColor: AppConstants.primaryColor,
backgroundColor: AppColors.primary,
foregroundColor: Colors.white,
elevation: 0,
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppConstants.primaryColor,
AppConstants.primaryColor.withAlpha(180),
AppColors.primary,
AppColors.primary.withAlpha(180),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
@@ -707,7 +710,7 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
],
),
body: Container(
color: isDark ? const Color(0xFF1A1A1A) : Colors.white,
color: AppColors.background,
child: SafeArea(
child: Padding(
padding: const EdgeInsets.only(
@@ -737,8 +740,8 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppConstants.primaryColor,
AppConstants.primaryColor.withAlpha(200),
AppColors.primary,
AppColors.primary.withAlpha(200),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
@@ -746,7 +749,7 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: AppConstants.primaryColor.withAlpha(
color: AppColors.primary.withAlpha(
80,
),
blurRadius: 12,
@@ -841,7 +844,7 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
children: [
CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
AppConstants.primaryColor,
AppColors.primary,
),
strokeWidth: 3,
),
@@ -850,9 +853,7 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
'加载题目中...',
style: TextStyle(
fontSize: 16,
color: isDark
? Colors.grey[400]
: Colors.grey,
color: AppColors.tertiaryText,
),
),
],
@@ -870,14 +871,14 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: isDark
? Colors.red[900]!.withAlpha(30)
: Colors.red[50],
? AppColors.iosRed.withAlpha(30)
: AppColors.iosRed.withAlpha(10),
shape: BoxShape.circle,
),
child: Icon(
Icons.error_outline,
size: 64,
color: Colors.red[400],
color: AppColors.iosRed,
),
),
const SizedBox(height: 24),
@@ -886,9 +887,7 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
color: isDark
? Colors.red[300]
: Colors.red,
color: AppColors.iosRed,
),
),
const SizedBox(height: 32),
@@ -896,8 +895,8 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppConstants.primaryColor,
AppConstants.primaryColor.withAlpha(
AppColors.primary,
AppColors.primary.withAlpha(
200,
),
],
@@ -905,7 +904,7 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: AppConstants.primaryColor
color: AppColors.primary
.withAlpha(80),
blurRadius: 8,
offset: const Offset(0, 4),
@@ -967,15 +966,9 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
BorderRadius.circular(
16,
),
color: isDark
? const Color(
0xFF2A2A2A,
)
: Colors.white,
color: AppColors.surface,
),
),
color: AppConstants
.primaryColor,
width: 4,
),
),
@@ -986,19 +979,10 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
isDark
? const Color(
0xFF2A2A2A,
)
: Colors.white,
AppConstants
.primaryColor
AppColors.surface,
AppColors.primary
.withAlpha(5),
isDark
? const Color(
0xFF2A2A2A,
)
: Colors.white,
AppColors.surface,
],
begin: Alignment.topLeft,
end:
@@ -1023,8 +1007,8 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
width: 4,
height: 20,
decoration: BoxDecoration(
color: AppConstants
.primaryColor,
color: AppColors
.primary,
borderRadius:
BorderRadius.circular(
2,
@@ -1041,8 +1025,8 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
fontWeight:
FontWeight
.w600,
color: AppConstants
.primaryColor,
color: AppColors
.primary,
),
),
],
@@ -1058,9 +1042,7 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
fontWeight:
FontWeight.bold,
height: 1.5,
color: isDark
? Colors.white
: Colors.black87,
color: AppColors.primaryText,
),
),
// 标签信息
@@ -1082,8 +1064,8 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
12,
),
decoration: BoxDecoration(
color: AppConstants
.primaryColor
color: AppColors
.primary
.withAlpha(
10,
),
@@ -1141,7 +1123,7 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
color: AppColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
@@ -1162,8 +1144,8 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.white,
Colors.grey[50]!,
AppColors.surface,
AppColors.background,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
@@ -1171,7 +1153,7 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
borderRadius:
BorderRadius.circular(12),
border: Border.all(
color: AppConstants.primaryColor
color: AppColors.primary
.withAlpha(50),
width: 1,
),
@@ -1195,18 +1177,18 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
children: [
Icon(
Icons.arrow_back,
color: AppConstants
.primaryColor,
color: AppColors
.primary,
size: 20,
),
const SizedBox(width: 8),
const Text(
Text(
'上一题',
style: TextStyle(
fontSize: 14,
fontWeight:
FontWeight.w500,
color: Colors.black87,
color: AppColors.primaryText,
),
),
],
@@ -1221,24 +1203,20 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.white,
Colors.grey[50]!,
AppColors.primary,
AppColors.primary.withAlpha(
200,
),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius:
BorderRadius.circular(12),
border: Border.all(
color:
AppConstants.primaryColor,
width: 2,
),
boxShadow: [
BoxShadow(
color: AppConstants
.primaryColor
.withAlpha(30),
color: AppColors.primary
.withAlpha(80),
blurRadius: 8,
offset: const Offset(0, 4),
),
@@ -1247,8 +1225,7 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
child: ElevatedButton(
onPressed: _getHint,
style: ElevatedButton.styleFrom(
backgroundColor:
Colors.transparent,
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
padding:
const EdgeInsets.symmetric(
@@ -1263,10 +1240,9 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Icon(
const Icon(
Icons.lightbulb_outline,
color: AppConstants
.primaryColor,
color: Colors.white,
size: 20,
),
const SizedBox(width: 8),
@@ -1275,8 +1251,8 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
style: TextStyle(
fontSize: 14,
fontWeight:
FontWeight.w600,
color: Colors.black87,
FontWeight.w500,
color: Colors.white,
),
),
],
@@ -1291,31 +1267,24 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppConstants.primaryColor,
AppConstants.primaryColor
.withAlpha(200),
AppColors.surface,
AppColors.background,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius:
BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: AppConstants
.primaryColor
.withAlpha(80),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
border: Border.all(
color: AppColors.primary
.withAlpha(50),
width: 1,
),
),
child: ElevatedButton(
child: OutlinedButton(
onPressed: _nextQuestion,
style: ElevatedButton.styleFrom(
backgroundColor:
Colors.transparent,
shadowColor: Colors.transparent,
style: OutlinedButton.styleFrom(
side: BorderSide.none,
padding:
const EdgeInsets.symmetric(
vertical: 14,
@@ -1329,19 +1298,20 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
mainAxisAlignment:
MainAxisAlignment.center,
children: [
const Text(
Text(
'下一题',
style: TextStyle(
fontSize: 14,
fontWeight:
FontWeight.w600,
color: Colors.white,
FontWeight.w500,
color: AppColors.primaryText,
),
),
const SizedBox(width: 8),
Icon(
Icons.arrow_forward,
color: Colors.white,
color: AppColors
.primary,
size: 20,
),
],
@@ -1351,6 +1321,38 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
),
],
),
if (_showFeedback && _feedbackMessage != null)
Padding(
padding: const EdgeInsets.only(top: 16),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: _isAnswerCorrect
? AppColors.iosGreen
.withAlpha(20)
: AppColors.iosRed
.withAlpha(20),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: _isAnswerCorrect
? AppColors.iosGreen
: AppColors.iosRed,
width: 1,
),
),
child: Text(
_feedbackMessage!,
style: TextStyle(
color: _isAnswerCorrect
? AppColors.iosGreen
: AppColors.iosRed,
fontSize: 14,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
),
),
],
),
),
@@ -1359,65 +1361,6 @@ class _PoetryLevelPageState extends State<PoetryLevelPage>
),
],
),
// 反馈信息气泡(不占用布局)
if (_showFeedback && _feedbackMessage != null)
Positioned(
top: 0,
left: 16,
right: 16,
child: AnimatedContainer(
duration: const Duration(milliseconds: 500),
curve: Curves.easeOut,
transform: Matrix4.translationValues(0, 0, 0),
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 12,
),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: _isAnswerCorrect
? [Colors.green[400]!, Colors.green[300]!]
: [Colors.orange[400]!, Colors.orange[300]!],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color:
(_isAnswerCorrect
? Colors.green
: Colors.orange)
.withAlpha(80),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
Icon(
_isAnswerCorrect
? Icons.celebration
: Icons.lightbulb_outline,
color: Colors.white,
size: 24,
),
const SizedBox(width: 12),
Expanded(
child: Text(
_feedbackMessage!,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
],
),
),
),
],
),
),