From 37b8d6be1a92f43fb9dc728d0443636ea1f8c221 Mon Sep 17 00:00:00 2001 From: yuliang_guo Date: Thu, 29 Jan 2026 18:06:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9EExcel=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E6=8F=90=E5=8F=96=E6=94=AF=E6=8C=81=20&=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E6=88=90=E7=BB=A9=E6=9F=A5=E8=AF=A2=E8=AF=BE=E7=A8=8B?= =?UTF-8?q?=E7=AD=9B=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端 knowledge_analysis_v2.py: 新增 _extract_excel_content 方法支持xlsx/xls文件 - 前端 student-scores.vue: 课程筛选改为动态加载,修复筛选参数传递 --- .../app/services/ai/knowledge_analysis_v2.py | 33 +++++++++++++- frontend/src/views/manager/student-scores.vue | 43 +++++++++++++++---- 2 files changed, 67 insertions(+), 9 deletions(-) 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() })