feat: 新增Excel内容提取支持 & 修复成绩查询课程筛选
Some checks failed
continuous-integration/drone/push Build is failing

- 后端 knowledge_analysis_v2.py: 新增 _extract_excel_content 方法支持xlsx/xls文件
- 前端 student-scores.vue: 课程筛选改为动态加载,修复筛选参数传递
This commit is contained in:
yuliang_guo
2026-01-29 18:06:38 +08:00
parent 3ddb1bda2d
commit 37b8d6be1a
2 changed files with 67 additions and 9 deletions

View File

@@ -176,7 +176,7 @@ class KnowledgeAnalysisServiceV2:
""" """
提取文档内容 提取文档内容
支持PDF、Worddocx、文本文件 支持PDF、WorddocxExcelxlsx/xls文本文件
""" """
suffix = file_path.suffix.lower() suffix = file_path.suffix.lower()
@@ -185,6 +185,8 @@ class KnowledgeAnalysisServiceV2:
return await self._extract_pdf_content(file_path) return await self._extract_pdf_content(file_path)
elif suffix in ['.docx', '.doc']: elif suffix in ['.docx', '.doc']:
return await self._extract_docx_content(file_path) 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']: elif suffix in ['.txt', '.md', '.text']:
return await self._extract_text_content(file_path) return await self._extract_text_content(file_path)
else: else:
@@ -272,6 +274,35 @@ class KnowledgeAnalysisServiceV2:
logger.error(f"文本文件读取失败: {e}") logger.error(f"文本文件读取失败: {e}")
raise ValueError(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: def _clean_content(self, content: str) -> str:
"""清理和截断内容""" """清理和截断内容"""
# 移除多余空白 # 移除多余空白

View File

@@ -62,12 +62,14 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="课程"> <el-form-item label="课程">
<el-select v-model="filterForm.course" placeholder="请选择" clearable> <el-select v-model="filterForm.courseId" placeholder="请选择课程" clearable style="width: 200px">
<el-option label="全部课程" value="" /> <el-option label="全部课程" :value="null" />
<el-option label="皮肤管理基础" value="skin_management" /> <el-option
<el-option label="美容产品知识" value="beauty_products" /> v-for="course in courseList"
<el-option label="客户沟通技巧" value="communication" /> :key="course.id"
<el-option label="轻医美项目" value="light_medical" /> :label="course.name"
:value="course.id"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="成绩范围"> <el-form-item label="成绩范围">
@@ -323,6 +325,7 @@
import { ref, reactive, computed, onMounted } from 'vue' import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { getStudentScores, getStudentScoresStatistics, getExamMistakes, deleteExamRecord, type StudentScoreRecord, type MistakeRecord } from '@/api/manager/scores' import { getStudentScores, getStudentScoresStatistics, getExamMistakes, deleteExamRecord, type StudentScoreRecord, type MistakeRecord } from '@/api/manager/scores'
import { getManagerCourses } from '@/api/manager'
// 加载状态 // 加载状态
const loading = ref(false) const loading = ref(false)
@@ -350,10 +353,30 @@ const mistakesList = ref<MistakeRecord[]>([])
const filterForm = reactive({ const filterForm = reactive({
studentName: '', studentName: '',
position: '', position: '',
course: '', courseId: null as number | null,
scoreRange: '' scoreRange: ''
}) })
// 课程列表
const courseList = ref<Array<{id: number, name: string}>>([])
/**
* 加载课程列表
*/
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([ const scoreStats = ref([
{ {
@@ -498,6 +521,9 @@ const loadScoresList = async () => {
if (filterForm.position) { if (filterForm.position) {
params.position = getPositionText(filterForm.position) params.position = getPositionText(filterForm.position)
} }
if (filterForm.courseId) {
params.course_id = filterForm.courseId
}
if (filterForm.scoreRange) { if (filterForm.scoreRange) {
params.score_range = filterForm.scoreRange params.score_range = filterForm.scoreRange
} }
@@ -568,7 +594,7 @@ const handleSearch = () => {
const handleReset = () => { const handleReset = () => {
filterForm.studentName = '' filterForm.studentName = ''
filterForm.position = '' filterForm.position = ''
filterForm.course = '' filterForm.courseId = null
filterForm.scoreRange = '' filterForm.scoreRange = ''
dateRange.value = [ dateRange.value = [
new Date(new Date().setDate(new Date().getDate() - 30)), new Date(new Date().setDate(new Date().getDate() - 30)),
@@ -700,6 +726,7 @@ const handleDeleteRecord = async (record: StudentScoreRecord) => {
// 组件挂载时初始化数据 // 组件挂载时初始化数据
onMounted(() => { onMounted(() => {
loadCourseList()
loadScoresList() loadScoresList()
loadStatistics() loadStatistics()
}) })