This commit is contained in:
朱潮 2025-11-15 23:51:08 +08:00
parent 871b4baadc
commit d1d786078c
14 changed files with 1580 additions and 30456 deletions

40
add_phone_column.py Normal file
View File

@ -0,0 +1,40 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sqlite3
import os
def add_phone_column():
"""为现有的students表添加phone列"""
db_path = '/Users/moshui/Documents/survey/data/survey.db'
if not os.path.exists(db_path):
print(f"数据库不存在: {db_path}")
return
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
try:
# 检查phone列是否已存在
cursor.execute("PRAGMA table_info(students)")
columns = [column[1] for column in cursor.fetchall()]
if 'phone' not in columns:
# 添加phone列
cursor.execute("ALTER TABLE students ADD COLUMN phone TEXT")
print("已成功添加phone列到students表")
# 提交更改
conn.commit()
else:
print("phone列已存在无需添加")
except Exception as e:
print(f"添加phone列时出错: {e}")
conn.rollback()
finally:
conn.close()
if __name__ == "__main__":
add_phone_column()

View File

@ -5,13 +5,13 @@ meta {
}
post {
url: http://120.26.23.172:5678/webhook/survey_report
url: http://120.26.23.172:5678/webhook-test/survey_report
body: json
auth: inherit
}
body:json {
{"analysis_text":"#\n 考试答题情况分析\n\n| 题目 | 题型 | 用户答案 | 正确答案 |\n 是否正确\n |\n|------|------|----------|----------|----------|\n| 姓名 |\n 填空题 | 小王 | 无标准答案 | 无法判断 |\n| 学校 | 填空题 |\n 时代小学 | 无标准答案 | 无法判断 |\n|\n 测量书本长度时,读数视线应与刻度尺: | 单选题 | 垂直 | 垂直 |\n 正确 |\n| 制作过山车时,连接处不牢固会导致? | 单选题 |\n 轨道倒塌 | 小球运动受阻 | 错误 |\n|\n 光从空气斜射入水中时,传播方向会? | 单选题 | 发生偏折 |\n 发生偏折 | 正确 |\n| 血液循环为身体细胞提供? | 单选题 |\n 情绪和感觉 | 氧气和营养物质 | 错误 |\n|\n 用气球驱动小车时,小车运动的方向是? | 单选题 |\n 与气球喷气方向相反 | 与气球喷气方向相反 | 正确 |\n|\n 工程设计过程的第一步通常是: | 单选题 | 明确问题和限制条件 |\n 明确问题和限制条件 | 正确 |\n| 用来判断一个物体是否运动的另一\n 个物体,叫做参照物。下列有关参照物的说法正确的是: | 单选题 |\n 参照物的选择是任意的 | 参照物的选择是任意的 | 正确 |\n|\n 食物在胃里会发生什么变化? | 单选题 | 被吸收大部分营养 |\n 被胃液分解 | 错误 |\n| 要使弦发出的声音变高,应该怎么做? |\n 单选题 | 加粗弦 | 拉紧弦 | 错误 |\n|\n 下列水污染源中,属于点污染源的是: | 单选题 | 城市街道的雨水 |\n 某化工厂的排污口 | 错误 |\n|\n 地球板块构造理论认为,全球岩石圈主要由多大板块组成? | 单选题\n | 六大板块 | 六大板块 | 正确 |\n| 我们吃花生是吃植物的哪部分?\n | 单选题 | 种子 | 种子 | 正确 |\n|\n 如果我们想提高作物产量,可以采取什么措施? | 单选题 | 适时灌溉\n | 以上都是 | 错误 |\n| 植物进行光合作用需要什么? | 单选题 |\n 水、土壤、肥料 | 阳光、水、二氧化碳 | 错误 |\n|\n 同一斜面,哪种情况机械效率最高? | 单选题 | 粗糙表面 |\n 光滑表面 | 错误 |\n| 为什么结晶是重要的分离提纯方法? | 单选题\n | 操作相对简单 | 以上都是 | 错误 |\n"}
{"analysis_text":"#\n 考试答题情况分析\n\n| 题目 | 题型 | 用户答案 | 正确答案 |\n 是否正确\n |\n|------|------|----------|----------|----------|\n| 姓名 |\n 填空题 | 小王 | 无标准答案 | 无法判断 |\n| 学校 | 填空题 |\n 时代小学 | 无标准答案 | 无法判断 |\n| 班级 | 填空题 |\n 三年级一班 | 无标准答案 | 无法判断 |\n| 手机号 | 填空题 |\n 13800138000 | 无标准答案 | 无法判断 |\n|\n 测量书本长度时,读数视线应与刻度尺: | 单选题 | 垂直 | 垂直 |\n 正确 |\n| 制作过山车时,连接处不牢固会导致? | 单选题 |\n 轨道倒塌 | 小球运动受阻 | 错误 |\n|\n 光从空气斜射入水中时,传播方向会? | 单选题 | 发生偏折 |\n 发生偏折 | 正确 |\n| 血液循环为身体细胞提供? | 单选题 |\n 情绪和感觉 | 氧气和营养物质 | 错误 |\n|\n 用气球驱动小车时,小车运动的方向是? | 单选题 |\n 与气球喷气方向相反 | 与气球喷气方向相反 | 正确 |\n|\n 工程设计过程的第一步通常是: | 单选题 | 明确问题和限制条件 |\n 明确问题和限制条件 | 正确 |\n| 用来判断一个物体是否运动的另一\n 个物体,叫做参照物。下列有关参照物的说法正确的是: | 单选题 |\n 参照物的选择是任意的 | 参照物的选择是任意的 | 正确 |\n|\n 食物在胃里会发生什么变化? | 单选题 | 被吸收大部分营养 |\n 被胃液分解 | 错误 |\n| 要使弦发出的声音变高,应该怎么做? |\n 单选题 | 加粗弦 | 拉紧弦 | 错误 |\n|\n 下列水污染源中,属于点污染源的是: | 单选题 | 城市街道的雨水 |\n 某化工厂的排污口 | 错误 |\n|\n 地球板块构造理论认为,全球岩石圈主要由多大板块组成? | 单选题\n | 六大板块 | 六大板块 | 正确 |\n| 我们吃花生是吃植物的哪部分?\n | 单选题 | 种子 | 种子 | 正确 |\n|\n 如果我们想提高作物产量,可以采取什么措施? | 单选题 | 适时灌溉\n | 以上都是 | 错误 |\n| 植物进行光合作用需要什么? | 单选题 |\n 水、土壤、肥料 | 阳光、水、二氧化碳 | 错误 |\n|\n 同一斜面,哪种情况机械效率最高? | 单选题 | 粗糙表面 |\n 光滑表面 | 错误 |\n| 为什么结晶是重要的分离提纯方法? | 单选题\n | 操作相对简单 | 以上都是 | 错误 |\n","session_id":"session_id"}
}
settings {

View File

@ -68,7 +68,12 @@ class ExcelQuestionReader:
# 添加额外的字段以保持兼容性
if "标签" in question:
question["题目标签"] = question["标签"]
question["题目类型"] = self._determine_question_type(question["标签"])
# 使用题型字段确定题目类型,如果题型字段为空则使用标签
question_type = question.get("题型", "")
if not question_type and "标签" in question:
question_type = self._determine_question_type(question["标签"])
question["题目类型"] = question_type or "基础题"
# 根据题目类型分类
q_type = question.get("题目类型", "基础题")
@ -122,11 +127,13 @@ class ExcelQuestionReader:
return sorted(list(all_tags))
def get_questions_by_tag(self, selected_tag: str) -> Dict[str, Any]:
"""根据标签筛选题目"""
if not selected_tag or selected_tag == "全部题目":
return self.get_questions()
def get_questions_by_filters(
self,
subject: str = "",
grade: str = "", # 完整的年级册次信息(如:一年级上册)
unit: str = ""
) -> Dict[str, Any]:
"""根据学科、年级册次、单元筛选题目"""
questions = self.get_questions()
filtered_questions = {
"基础题": [],
@ -134,14 +141,68 @@ class ExcelQuestionReader:
"竞赛题": []
}
for category, category_questions in questions.items():
# 如果没有提供任何筛选条件,返回全部题目
if not any([subject, grade, unit]):
return questions
for category_name, category_questions in questions.items():
for question in category_questions:
question_tag = question.get("标签", "")
if selected_tag in question_tag:
filtered_questions[category].append(question)
# 检查是否符合所有筛选条件
match = True
# 学科筛选
if subject and question.get("学科", "") != subject:
match = False
# 年级册次筛选
if grade and question.get("年级", "") != grade:
match = False
# 单元筛选(包括期中、期末的特殊处理)
if unit:
if unit == "期中":
# 期中:选择前一半的单元
if not self._is_in_midterm_units(question, subject, grade):
match = False
elif unit == "期末":
# 期末:选择所有单元(已经通过了年级册次筛选)
pass # 不需要额外筛选
elif question.get("单元", "") != unit:
# 具体单元筛选
match = False
if match:
filtered_questions[category_name].append(question)
return filtered_questions
def _is_in_midterm_units(self, question: Dict[str, Any], subject: str, grade: str) -> bool:
"""判断题目是否属于期中考试范围(前一半单元)"""
unit = question.get("单元", "")
if not unit:
return False
# 获取当前年级册次的所有单元
all_questions = self.get_questions()
grade_units = set()
for category_questions in all_questions.values():
for q in category_questions:
if q.get("学科", "") == subject and q.get("年级", "") == grade:
unit_name = q.get("单元", "")
if unit_name:
grade_units.add(unit_name)
# 按单元数字排序
sorted_units = sorted(list(grade_units), key=lambda x: int(x.split('-')[0]) if x.split('-')[0].isdigit() else 0)
# 确定期中包含的单元(前一半)
mid_term_count = (len(sorted_units) + 1) // 2 # 向上取整
mid_term_units = set(sorted_units[:mid_term_count])
# 检查当前题目的单元是否在期中范围内
return unit in mid_term_units
def clear_cache(self):
"""清除缓存"""
self._cache = None
@ -162,6 +223,67 @@ def get_questions_by_tag(selected_tag: str) -> Dict[str, Any]:
"""根据标签筛选题目(全局函数)"""
return _excel_reader.get_questions_by_tag(selected_tag)
def get_questions_by_filters(
subject: str = "",
grade: str = "", # 完整的年级册次信息(如:一年级上册)
unit: str = ""
) -> Dict[str, Any]:
"""根据学科、年级册次、单元筛选题目(全局函数)"""
return _excel_reader.get_questions_by_filters(
subject=subject,
grade=grade,
unit=unit
)
def get_available_filters() -> Dict[str, List[str]]:
"""获取所有可用的筛选条件(全局函数)"""
questions = _excel_reader.get_questions()
subjects = set()
grades = set()
units = set()
for category_questions in questions.values():
for question in category_questions:
# 收集所有学科
subject = question.get("学科", "")
if subject:
subjects.add(subject)
# 收集所有年级
grade = question.get("年级", "")
if grade:
grades.add(grade)
# 收集所有单元
unit = question.get("单元", "")
if unit:
units.add(unit)
return {
"subjects": sorted(list(subjects)),
"grades": _sort_grades_by_chinese_numbers(list(grades)),
"units": sorted(list(units))
}
def _sort_grades_by_chinese_numbers(grades):
"""按中文数字顺序排序年级"""
chinese_number_map = {
'': 1, '': 2, '': 3, '': 4, '': 5, '': 6, '': 7, '': 8,
'': 9, '': 10, '十一': 11, '十二': 12
}
import re
def extract_chinese_number(grade_text):
"""从年级文本中提取中文数字"""
match = re.match(r'^([一二三四五六七八九十]+)', grade_text)
if match:
return chinese_number_map.get(match.group(1), 999)
return 999
return sorted(grades, key=extract_chinese_number)
def clear_cache():
"""清除缓存(全局函数)"""
_excel_reader.clear_cache()

View File

@ -26,6 +26,7 @@ def init_database():
name TEXT NOT NULL,
school TEXT NOT NULL,
grade TEXT NOT NULL,
phone TEXT NOT NULL,
selected_tag TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

115
main.py
View File

@ -15,7 +15,7 @@ from typing import Dict, List, Optional, Any
import asyncio
import threading
from enhanced_survey_system import enhanced_system, get_east8_time_string, get_east8_time
from excel_reader import get_questions_data, get_all_tags, get_questions_by_tag
from excel_reader import get_questions_data, get_questions_by_tag, get_questions_by_filters, get_available_filters
app = FastAPI(title="Enhanced Survey System")
@ -40,7 +40,15 @@ class CreateSessionRequest(BaseModel):
name: str
school: str
grade: str
phone: str = ""
selectedSubject: str = "" # 学科
selectedSemester: str = "" # 册次(保留兼容性)
selectedExamType: str = "" # 考试类型(保留兼容性)
selectedUnit: str = "" # 单元
selectedCategory: str = "" # 分类
selectedQuestionType: str = "" # 题型
selectedTag: str = ""
selectedTagsList: List[str] = []
questionsConfig: Dict[str, int]
class SaveAnswersRequest(BaseModel):
@ -75,11 +83,24 @@ async def create_session(request: CreateSessionRequest):
name = request.name.strip()
school = request.school.strip()
grade = request.grade.strip()
selected_tag = request.selectedTag.strip()
phone = request.phone.strip()
selected_subject = request.selectedSubject.strip()
selected_semester = request.selectedSemester.strip()
selected_unit = request.selectedUnit.strip()
# 构建描述性标签
selected_tag = ""
filters = []
if selected_subject: filters.append(f"学科:{selected_subject}")
if selected_semester: filters.append(f"年级:{selected_semester}") # 这里实际是完整年级信息
if selected_unit: filters.append(f"单元:{selected_unit}")
selected_tag = " | ".join(filters) if filters else "全部题目"
questions_config = request.questionsConfig
if not name or not school or not grade:
raise HTTPException(status_code=400, detail="姓名、学校和年级不能为空")
if not name or not school or not grade or not phone:
raise HTTPException(status_code=400, detail="姓名、学校、年级和手机号不能为空")
# 创建学员记录
student_id = str(uuid.uuid4())
@ -87,9 +108,9 @@ async def create_session(request: CreateSessionRequest):
cursor = conn.cursor()
cursor.execute('''
INSERT INTO students (id, name, school, grade, selected_tag)
VALUES (?, ?, ?, ?, ?)
''', (student_id, name, school, grade, selected_tag))
INSERT INTO students (id, name, school, grade, phone, selected_tag)
VALUES (?, ?, ?, ?, ?, ?)
''', (student_id, name, school, grade, phone, selected_tag))
# 创建答题会话
session_id = str(uuid.uuid4())
@ -322,29 +343,40 @@ async def get_questions():
except Exception as e:
raise HTTPException(status_code=500, detail=f"加载题库失败: {str(e)}")
@app.get("/api/tags")
async def get_tags():
"""获取标签数据"""
@app.get("/api/filters")
async def get_filters():
"""获取所有可用的筛选条件"""
try:
with open('public/tags.json', 'r', encoding='utf-8') as f:
tags_data = json.load(f)
return tags_data
except FileNotFoundError:
# 如果tags.json不存在从Excel生成备用标签数据
try:
all_tags = get_all_tags()
backup_tags = {
"tags": all_tags,
"tag_counts": {},
"total_unique_tags": len(all_tags)
}
return backup_tags
except Exception as e:
raise HTTPException(status_code=500, detail=f"生成标签数据失败: {str(e)}")
filters_data = get_available_filters()
return {
"success": True,
"data": filters_data
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"加载标签数据失败: {str(e)}")
raise HTTPException(status_code=500, detail=f"获取筛选条件失败: {str(e)}")
@app.get("/api/questions-by-filters")
async def get_questions_by_filters_api(
subject: str = "",
grade: str = "",
unit: str = ""
):
"""根据筛选条件获取题目"""
try:
# 根据筛选条件获取题目
filtered_questions = get_questions_by_filters(
subject=subject,
grade=grade,
unit=unit
)
return {
"success": True,
"data": filtered_questions
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"获取筛选题目失败: {str(e)}")
@app.get("/api/questions/{session_id}")
async def get_filtered_questions(session_id: str):
@ -356,7 +388,7 @@ async def get_filtered_questions(session_id: str):
cursor = conn.cursor()
cursor.execute('''
SELECT s.selected_tag, qs.questions_config
SELECT s.*, qs.questions_config
FROM students s
JOIN quiz_sessions qs ON s.id = qs.student_id
WHERE qs.id = ?
@ -371,8 +403,29 @@ async def get_filtered_questions(session_id: str):
selected_tag = session_data['selected_tag'] or ''
questions_config = json.loads(session_data['questions_config'])
# 根据标签筛选题目直接从Excel读取
filtered_questions = get_questions_by_tag(selected_tag)
# 解析标签,提取筛选条件
subject = ""
grade = ""
unit = ""
# 如果标签包含筛选条件格式,提取它们
if selected_tag and "学科:" in selected_tag:
# 格式: "学科:科学 | 年级:一年级上册 | 单元:1-周围的植物"
filters = selected_tag.split(' | ')
for filter_item in filters:
if filter_item.startswith("学科:"):
subject = filter_item[3:]
elif filter_item.startswith("年级:"):
grade = filter_item[3:]
elif filter_item.startswith("单元:"):
unit = filter_item[3:]
# 根据筛选条件获取题目
filtered_questions = get_questions_by_filters(
subject=subject,
grade=grade,
unit=unit
)
# 根据配置选择题目
selected_questions = select_questions_by_config(filtered_questions, questions_config)

BIN
public/.~questions.xlsx Normal file

Binary file not shown.

View File

@ -760,7 +760,7 @@
const createBtn = document.getElementById('create-report-btn');
if (createBtn) {
createBtn.addEventListener('click', function() {
window.location.href = '/survey.html';
window.location.href = '/survey.html?admin=true';
});
}
});

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -23,16 +23,16 @@ class SurveyAPI:
conn.row_factory = sqlite3.Row
return conn
def create_student(self, name, school, grade, selected_tag=''):
def create_student(self, name, school, grade, phone, selected_tag=''):
"""创建学员记录"""
student_id = str(uuid.uuid4())
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute('''
INSERT INTO students (id, name, school, grade, selected_tag)
VALUES (?, ?, ?, ?, ?)
''', (student_id, name, school, grade, selected_tag))
INSERT INTO students (id, name, school, grade, phone, selected_tag)
VALUES (?, ?, ?, ?, ?, ?)
''', (student_id, name, school, grade, phone, selected_tag))
conn.commit()
conn.close()
@ -131,15 +131,30 @@ class SurveyHandler(BaseHTTPRequestHandler):
name = data.get('name', '').strip()
school = data.get('school', '').strip()
grade = data.get('grade', '').strip()
selected_tag = data.get('selectedTag', '').strip()
phone = data.get('phone', '').strip()
selected_subject = data.get('selectedSubject', '').strip()
selected_semester = data.get('selectedSemester', '').strip()
selected_exam_type = data.get('selectedExamType', '').strip()
# 构建描述性标签
selected_tag = ""
if selected_subject and grade and selected_semester and selected_exam_type:
selected_tag = f"{selected_subject}-{grade}{selected_semester}-{selected_exam_type}考试"
elif selected_subject and grade and selected_semester:
selected_tag = f"{selected_subject}-{grade}{selected_semester}"
elif selected_subject and grade:
selected_tag = f"{selected_subject}-{grade}"
elif selected_subject:
selected_tag = selected_subject
questions_config = data.get('questionsConfig', {})
if not name or not school or not grade:
self.send_error(400, "姓名、学校和年级不能为空")
if not name or not school or not grade or not phone:
self.send_error(400, "姓名、学校、年级和手机号不能为空")
return
# 创建学员记录
student_id = self.api.create_student(name, school, grade, selected_tag)
student_id = self.api.create_student(name, school, grade, phone, selected_tag)
# 创建答题会话
session_id = self.api.create_quiz_session(student_id, questions_config)
@ -240,6 +255,7 @@ class SurveyHandler(BaseHTTPRequestHandler):
student_name = session_data['name']
school = session_data['school']
grade = session_data['grade']
phone = session_data['phone']
return f'''<!DOCTYPE html>
<html lang="zh-CN">
@ -473,6 +489,10 @@ class SurveyHandler(BaseHTTPRequestHandler):
<div class="info-label">年级</div>
<div class="info-value">{grade}</div>
</div>
<div class="info-item">
<div class="info-label">手机号</div>
<div class="info-value">{phone}</div>
</div>
</div>
</div>

77
test_filter.html Normal file
View File

@ -0,0 +1,77 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>测试筛选功能</title>
</head>
<body>
<h1>测试筛选功能</h1>
<button onclick="testFiltering()">开始测试</button>
<div id="results"></div>
<script>
async function testFiltering() {
const resultsDiv = document.getElementById('results');
resultsDiv.innerHTML = '<p>正在测试...</p>';
try {
// 测试获取筛选条件
console.log('=== 测试获取筛选条件 ===');
const filtersResponse = await fetch('/api/filters');
const filtersResult = await filtersResponse.json();
console.log('筛选条件:', filtersResult);
// 测试获取题目
console.log('=== 测试获取题目 ===');
const questionsResponse = await fetch('/api/questions');
const questionsResult = await questionsResponse.json();
console.log('题目总数:', Object.keys(questionsResult).map(type => `${type}: ${questionsResult[type].length}`).join(', '));
// 测试筛选功能
console.log('=== 测试筛选功能 ===');
if (filtersResult.success) {
const subject = filtersResult.data.subjects[0] || '';
const grade = filtersResult.data.grades[0] || '';
const unit = filtersResult.data.units[0] || '';
console.log(`测试筛选: 学科=${subject}, 年级=${grade}, 单元=${unit}`);
// 构建筛选URL
const filterParams = new URLSearchParams({
subject: subject,
grade: grade,
unit: unit
});
const filteredResponse = await fetch(`/api/questions-by-filters?${filterParams}`);
const filteredResult = await filteredResponse.json();
console.log('筛选结果:', filteredResult);
if (filteredResult.success) {
const filteredQuestions = filteredResult.data;
const totalFiltered = Object.keys(filteredQuestions).reduce((sum, type) => sum + filteredQuestions[type].length, 0);
resultsDiv.innerHTML = `
<h2>测试结果</h2>
<p>原始题目总数: ${Object.keys(questionsResult).reduce((sum, type) => sum + questionsResult[type].length, 0)}</p>
<p>筛选条件: 学科=${subject}, 年级=${grade}, 单元=${unit}</p>
<p>筛选后题目总数: ${totalFiltered}</p>
<p>筛选后分布:</p>
<ul>
${Object.keys(filteredQuestions).map(type => `<li>${type}: ${filteredQuestions[type].length}题</li>`).join('')}
</ul>
<p>查看浏览器控制台获取详细信息</p>
`;
} else {
resultsDiv.innerHTML = `<p>筛选测试失败</p>`;
}
}
} catch (error) {
console.error('测试失败:', error);
resultsDiv.innerHTML = `<p>测试失败: ${error.message}</p>`;
}
}
</script>
</body>
</html>

90
test_grade_sorting.html Normal file
View File

@ -0,0 +1,90 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>年级册次排序测试</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.test-container { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
.success { border-color: #28a745; background-color: #f8fff9; }
select { width: 200px; padding: 8px; margin: 5px 0; }
label { display: block; margin: 10px 0 5px 0; font-weight: bold; }
</style>
</head>
<body>
<h1>年级册次排序测试</h1>
<div class="test-container">
<h2>排序验证</h2>
<div id="sortResult"></div>
</div>
<div class="test-container">
<h2>前端选择器预览</h2>
<label for="gradeSelect">年级册次选择:</label>
<select id="gradeSelect"></select>
<div id="selectorResult"></div>
</div>
<script>
async function testSorting() {
const sortResult = document.getElementById('sortResult');
const gradeSelect = document.getElementById('gradeSelect');
const selectorResult = document.getElementById('selectorResult');
try {
// 获取筛选条件
const response = await fetch('/api/filters');
const result = await response.json();
if (result.success) {
const grades = result.data.grades;
const correctOrder = ['一年级上册', '二年级上册', '三年级上册', '四年级上册', '五年级上册', '六年级上册', '七年级上册', '八年级上册'];
// 验证排序是否正确
const isCorrect = JSON.stringify(grades) === JSON.stringify(correctOrder);
if (isCorrect) {
sortResult.innerHTML = '<div class="success">✓ 年级册次排序正确!</div>';
sortResult.innerHTML += '<h3>正确的排序顺序:</h3>';
correctOrder.forEach((grade, index) => {
sortResult.innerHTML += `<div>${index + 1}. ${grade}</div>`;
});
} else {
sortResult.innerHTML = '<div>✗ 年级册次排序不正确</div>';
sortResult.innerHTML += '<h3>当前排序:</h3>';
grades.forEach((grade, index) => {
sortResult.innerHTML += `<div>${index + 1}. ${grade}</div>`;
});
sortResult.innerHTML += '<h3>正确排序:</h3>';
correctOrder.forEach((grade, index) => {
sortResult.innerHTML += `<div>${index + 1}. ${grade}</div>`;
});
}
// 填充选择器
gradeSelect.innerHTML = '<option value="">选择年级册次...</option>';
grades.forEach(grade => {
const option = document.createElement('option');
option.value = grade;
option.textContent = grade;
gradeSelect.appendChild(option);
});
selectorResult.innerHTML = '<div class="success">✓ 前端选择器已正确填充</div>';
} else {
sortResult.innerHTML = '<div>✗ 获取筛选条件失败</div>';
}
} catch (error) {
console.error('测试失败:', error);
sortResult.innerHTML = `<div>测试失败: ${error.message}</div>`;
}
}
// 页面加载完成后运行测试
window.onload = testSorting;
</script>
</body>
</html>

103
test_midterm_final.html Normal file
View File

@ -0,0 +1,103 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>期中期末功能测试</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.test-result { margin: 10px 0; padding: 10px; border: 1px solid #ccc; }
.success { background-color: #d4edda; }
.error { background-color: #f8d7da; }
</style>
</head>
<body>
<h1>期中期末功能测试</h1>
<h2>测试结果:</h2>
<div id="testResults"></div>
<script>
async function runTests() {
const resultsDiv = document.getElementById('testResults');
resultsDiv.innerHTML = '<p>正在测试...</p>';
try {
// 测试1: 获取筛选条件
console.log('测试1: 获取筛选条件');
const filtersResponse = await fetch('/api/filters');
const filtersResult = await filtersResponse.json();
if (filtersResult.success) {
resultsDiv.innerHTML += '<div class="test-result success">✓ 筛选条件API正常</div>';
} else {
resultsDiv.innerHTML += '<div class="test-result error">✗ 筛选条件API失败</div>';
}
// 测试2: 期中考试筛选
console.log('测试2: 期中考试筛选');
const midtermResponse = await fetch('/api/questions-by-filters?subject=科学&grade=一年级上册&unit=期中');
const midtermResult = await midtermResponse.json();
if (midtermResult.success) {
const midtermTotal = Object.values(midtermResult.data).reduce((sum, questions) => sum + questions.length, 0);
resultsDiv.innerHTML += `<div class="test-result success">✓ 期中考试筛选正常: ${midtermTotal}题</div>`;
resultsDiv.innerHTML += `<div class="test-result"> - 基础题: ${midtermResult.data['基础题'].length}题</div>`;
resultsDiv.innerHTML += `<div class="test-result"> - 进阶题: ${midtermResult.data['进阶题'].length}题</div>`;
resultsDiv.innerHTML += `<div class="test-result"> - 竞赛题: ${midtermResult.data['竞赛题'].length}题</div>`;
} else {
resultsDiv.innerHTML += '<div class="test-result error">✗ 期中考试筛选失败</div>';
}
// 测试3: 期末考试筛选
console.log('测试3: 期末考试筛选');
const finalResponse = await fetch('/api/questions-by-filters?subject=科学&grade=一年级上册&unit=期末');
const finalResult = await finalResponse.json();
if (finalResult.success) {
const finalTotal = Object.values(finalResult.data).reduce((sum, questions) => sum + questions.length, 0);
resultsDiv.innerHTML += `<div class="test-result success">✓ 期末考试筛选正常: ${finalTotal}题</div>`;
resultsDiv.innerHTML += `<div class="test-result"> - 基础题: ${finalResult.data['基础题'].length}题</div>`;
resultsDiv.innerHTML += `<div class="test-result"> - 进阶题: ${finalResult.data['进阶题'].length}题</div>`;
resultsDiv.innerHTML += `<div class="test-result"> - 竞赛题: ${finalResult.data['竞赛题'].length}题</div>`;
} else {
resultsDiv.innerHTML += '<div class="test-result error">✗ 期末考试筛选失败</div>';
}
// 测试4: 对比期中期末题目数量
console.log('测试4: 对比期中期末题目数量');
if (midtermResult.success && finalResult.success) {
const midtermCount = Object.values(midtermResult.data).reduce((sum, questions) => sum + questions.length, 0);
const finalCount = Object.values(finalResult.data).reduce((sum, questions) => sum + questions.length, 0);
if (finalCount > midtermCount) {
resultsDiv.innerHTML += '<div class="test-result success">✓ 期末题目数量大于期中,符合预期</div>';
} else {
resultsDiv.innerHTML += '<div class="test-result error">✗ 期末题目数量应该大于期中</div>';
}
}
// 测试5: 具体单元筛选
console.log('测试5: 具体单元筛选');
const unitResponse = await fetch('/api/questions-by-filters?subject=科学&grade=一年级上册&unit=1-周围的植物');
const unitResult = await unitResponse.json();
if (unitResult.success) {
const unitTotal = Object.values(unitResult.data).reduce((sum, questions) => sum + questions.length, 0);
resultsDiv.innerHTML += `<div class="test-result success">✓ 具体单元筛选正常: ${unitTotal}题</div>`;
} else {
resultsDiv.innerHTML += '<div class="test-result error">✗ 具体单元筛选失败</div>';
}
resultsDiv.innerHTML += '<div class="test-result success"><strong>所有测试完成!</strong></div>';
} catch (error) {
console.error('测试失败:', error);
resultsDiv.innerHTML = `<div class="test-result error">测试失败: ${error.message}</div>`;
}
}
// 页面加载完成后运行测试
window.onload = runTests;
</script>
</body>
</html>