feat(student): 实现学生学习分析功能

- 新增AnalyzeStudentStudyReqVO用于分析请求参数封装
- StudentService接口新增analyzeStudentStudy方法及其实现
- 实现分析逻辑,查询最近7天学生考试及单词掌握记录,构造分析数据
- 通过DifyArticleClient调用外部AI服务生成学习分析结果
- 使用Redis缓存分析结果,设置3天过期
- 新增ExamWordsJudgeResultDetail和WordMasteryDetail数据模型
- Mapper新增支持根据学生ID和时间范围查询考试结果和单词掌握日志
- DifyArticleClient新增sendStudentAnalyze方法调用分析接口
- 前端学生页面新增学习分析面板及调用接口,支持超时设置
- 修改路由权限配置,允许访问学习分析接口
- 添加markdown-it库支持分析结果富文本渲染
- 移除RoleServiceImpl中redis设置过期时间,改为永久保存
This commit is contained in:
lbw
2025-12-24 15:22:18 +08:00
parent 4135b72648
commit 260c2c79f1
19 changed files with 342 additions and 11 deletions

View File

@@ -32,6 +32,20 @@
<div class="text-md font-semibold mb-3">学生学案记录</div>
<PlanHistoryChart :student-id="route.params.id" />
</div>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div class="flex items-center justify-between mb-3">
<div class="text-md font-semibold">学习分析</div>
<el-button type="primary" size="small" :loading="analyzeLoading" @click="fetchStudyAnalyze">
生成学习分析
</el-button>
</div>
<template v-if="analysisHtml">
<div class="leading-7 text-gray-700 dark:text-gray-200" v-html="analysisHtml"></div>
</template>
<template v-else>
<el-empty description="点击右上按钮生成学习分析" />
</template>
</div>
</div>
</el-main>
@@ -41,17 +55,28 @@
<script setup>
import Header from '@/layouts/components/Header.vue'
import { ref, onMounted } from 'vue'
import { ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import { getStudentDetail } from '@/api/student'
import { getStudentDetail, getStudentStudyAnalyze } from '@/api/student'
import { getStudentExamHistory } from '@/api/exam'
import ExamHistoryChart from '@/layouts/components/student/ExamHistoryChart.vue'
import PlanHistoryChart from '@/layouts/components/student/PlanHistoryChart.vue'
import MarkdownIt from 'markdown-it'
const loading = ref(false)
const detail = ref(null)
const route = useRoute()
const history = ref([])
const analyzeLoading = ref(false)
const analysisText = ref('')
const md = new MarkdownIt({
html: false,
linkify: true,
breaks: true
})
const analysisHtml = computed(() => {
return analysisText.value ? md.render(analysisText.value) : ''
})
async function fetchDetail() {
const id = route.params.id
@@ -76,6 +101,22 @@ async function fetchExamHistory() {
}) : []
}
async function fetchStudyAnalyze() {
const id = route.params.id
if (!id) return
analyzeLoading.value = true
try {
const res = await getStudentStudyAnalyze({
studentId: Number(id)
})
const d = res.data
const raw = typeof d?.data === 'string' ? d.data : ''
analysisText.value = raw.replace(/\\n/g, '\n')
} finally {
analyzeLoading.value = false
}
}
onMounted(() => {
fetchDetail()
fetchExamHistory()