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:
54
enlish-vue/package-lock.json
generated
54
enlish-vue/package-lock.json
generated
@@ -13,6 +13,7 @@
|
||||
"echarts": "^6.0.0",
|
||||
"element-plus": "^2.12.0",
|
||||
"flowbite": "^1.8.1",
|
||||
"markdown-it": "^14.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^3.0.4",
|
||||
"universal-cookie": "^8.0.1",
|
||||
@@ -1413,6 +1414,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/async-validator": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz",
|
||||
@@ -2326,6 +2333,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/linkify-it": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"uc.micro": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/local-pkg": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz",
|
||||
@@ -2378,6 +2394,23 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it": {
|
||||
"version": "14.1.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
|
||||
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1",
|
||||
"entities": "^4.4.0",
|
||||
"linkify-it": "^5.0.0",
|
||||
"mdurl": "^2.0.0",
|
||||
"punycode.js": "^2.3.1",
|
||||
"uc.micro": "^2.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"markdown-it": "bin/markdown-it.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
@@ -2387,6 +2420,12 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mdurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/memoize-one": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||
@@ -2865,6 +2904,15 @@
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/punycode.js": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
|
||||
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/quansync": {
|
||||
"version": "0.2.11",
|
||||
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
|
||||
@@ -3233,6 +3281,12 @@
|
||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/uc.micro": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
|
||||
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ufo": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"echarts": "^6.0.0",
|
||||
"element-plus": "^2.12.0",
|
||||
"flowbite": "^1.8.1",
|
||||
"markdown-it": "^14.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^3.0.4",
|
||||
"universal-cookie": "^8.0.1",
|
||||
|
||||
@@ -23,4 +23,11 @@ export function deleteStudent(id) {
|
||||
return axios.post('/student/delete', {
|
||||
studentId: id
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 加一个最大响应时间 20 秒
|
||||
export function getStudentStudyAnalyze(data) {
|
||||
return axios.post('/student/analyze', data, {
|
||||
timeout: 20000
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user