diff --git a/backend/app/services/ai/knowledge_analysis_v2.py b/backend/app/services/ai/knowledge_analysis_v2.py
index eafc93a..ea0eeef 100644
--- a/backend/app/services/ai/knowledge_analysis_v2.py
+++ b/backend/app/services/ai/knowledge_analysis_v2.py
@@ -176,7 +176,7 @@ class KnowledgeAnalysisServiceV2:
"""
提取文档内容
- 支持:PDF、Word(docx)、文本文件
+ 支持:PDF、Word(docx)、Excel(xlsx/xls)、文本文件
"""
suffix = file_path.suffix.lower()
@@ -185,6 +185,8 @@ class KnowledgeAnalysisServiceV2:
return await self._extract_pdf_content(file_path)
elif suffix in ['.docx', '.doc']:
return await self._extract_docx_content(file_path)
+ elif suffix in ['.xlsx', '.xls']:
+ return await self._extract_excel_content(file_path)
elif suffix in ['.txt', '.md', '.text']:
return await self._extract_text_content(file_path)
else:
@@ -272,6 +274,35 @@ class KnowledgeAnalysisServiceV2:
logger.error(f"文本文件读取失败: {e}")
raise ValueError(f"文本文件读取失败: {e}")
+ async def _extract_excel_content(self, file_path: Path) -> str:
+ """提取 Excel 文件内容"""
+ try:
+ from openpyxl import load_workbook
+
+ wb = load_workbook(str(file_path), read_only=True, data_only=True)
+ text_parts = []
+
+ for sheet_name in wb.sheetnames:
+ sheet = wb[sheet_name]
+ text_parts.append(f"【工作表: {sheet_name}】")
+
+ for row in sheet.iter_rows(values_only=True):
+ # 过滤空行
+ row_values = [str(cell) if cell is not None else '' for cell in row]
+ if any(v.strip() for v in row_values):
+ text_parts.append(' | '.join(row_values))
+
+ wb.close()
+ content = '\n'.join(text_parts)
+ return self._clean_content(content)
+
+ except ImportError:
+ logger.error("openpyxl 未安装,无法读取 Excel 文件")
+ raise ValueError("服务器未安装 Excel 读取组件(openpyxl)")
+ except Exception as e:
+ logger.error(f"Excel 文件读取失败: {e}")
+ raise ValueError(f"Excel 文件读取失败: {e}")
+
def _clean_content(self, content: str) -> str:
"""清理和截断内容"""
# 移除多余空白
diff --git a/frontend/src/views/manager/student-scores.vue b/frontend/src/views/manager/student-scores.vue
index 0efb172..5341591 100644
--- a/frontend/src/views/manager/student-scores.vue
+++ b/frontend/src/views/manager/student-scores.vue
@@ -62,12 +62,14 @@
-
-
-
-
-
-
+
+
+
@@ -323,6 +325,7 @@
import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getStudentScores, getStudentScoresStatistics, getExamMistakes, deleteExamRecord, type StudentScoreRecord, type MistakeRecord } from '@/api/manager/scores'
+import { getManagerCourses } from '@/api/manager'
// 加载状态
const loading = ref(false)
@@ -350,10 +353,30 @@ const mistakesList = ref([])
const filterForm = reactive({
studentName: '',
position: '',
- course: '',
+ courseId: null as number | null,
scoreRange: ''
})
+// 课程列表
+const courseList = ref>([])
+
+/**
+ * 加载课程列表
+ */
+const loadCourseList = async () => {
+ try {
+ const res = await getManagerCourses({ page: 1, size: 100, status: 'published' })
+ if (res.code === 200 && res.data) {
+ courseList.value = res.data.items.map(item => ({
+ id: item.id,
+ name: item.name || item.title
+ }))
+ }
+ } catch (error) {
+ console.error('加载课程列表失败:', error)
+ }
+}
+
// 成绩统计数据
const scoreStats = ref([
{
@@ -498,6 +521,9 @@ const loadScoresList = async () => {
if (filterForm.position) {
params.position = getPositionText(filterForm.position)
}
+ if (filterForm.courseId) {
+ params.course_id = filterForm.courseId
+ }
if (filterForm.scoreRange) {
params.score_range = filterForm.scoreRange
}
@@ -568,7 +594,7 @@ const handleSearch = () => {
const handleReset = () => {
filterForm.studentName = ''
filterForm.position = ''
- filterForm.course = ''
+ filterForm.courseId = null
filterForm.scoreRange = ''
dateRange.value = [
new Date(new Date().setDate(new Date().getDate() - 30)),
@@ -700,6 +726,7 @@ const handleDeleteRecord = async (record: StudentScoreRecord) => {
// 组件挂载时初始化数据
onMounted(() => {
+ loadCourseList()
loadScoresList()
loadStatistics()
})