关怀模式
This commit is contained in:
@@ -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. 提交答案
|
||||
|
||||
**请求方式 1:GET(简单)**
|
||||
```
|
||||
GET /api.php?action=answer&id=0&answer=2
|
||||
```
|
||||
|
||||
**请求方式 2:POST(推荐)**
|
||||
```
|
||||
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 集成建议
|
||||
|
||||
### 方案 1:App 自己管理进度(推荐)
|
||||
|
||||
使用 `?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;
|
||||
}
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- 不依赖 Session,App 完全控制进度
|
||||
- 可以随时跳转任意题目
|
||||
- 适合多端同步
|
||||
|
||||
---
|
||||
|
||||
### 方案 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'];
|
||||
}
|
||||
```
|
||||
|
||||
**适用场景**:
|
||||
- 想要不断扩展题库
|
||||
- 用户每次刷新都可能看到新题
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 写入统计数据到笔记
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user