feat:增加训练场和错题集模块

This commit is contained in:
2026-07-29 19:50:40 +08:00
parent 1f88341019
commit 4d49660b18
21 changed files with 620 additions and 90 deletions

View File

@@ -11,7 +11,9 @@
"dependencies": {
"@vueup/vue-quill": "^1.5.5",
"axios": "^1.18.1",
"dayjs": "^1.11.21",
"element-plus": "^2.14.3",
"filesize": "^11.0.22",
"pinia": "^4.0.2",
"vue": "^3.5.39",
"vue-router": "^5.2.0",

12
pnpm-lock.yaml generated
View File

@@ -14,9 +14,15 @@ importers:
axios:
specifier: ^1.18.1
version: 1.18.1
dayjs:
specifier: ^1.11.21
version: 1.11.21
element-plus:
specifier: ^2.14.3
version: 2.14.3(vue@3.5.40(typescript@6.0.3))
filesize:
specifier: ^11.0.22
version: 11.0.22
pinia:
specifier: ^4.0.2
version: 4.0.2(@vue/devtools-api@8.2.1)(typescript@6.0.3)(vue@3.5.40(typescript@6.0.3))
@@ -1038,6 +1044,10 @@ packages:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0}
filesize@11.0.22:
resolution: {integrity: sha512-RlCVs9CY+oSsRnNZn95J9vDXjNjOwddKyTFjOYtA4yxYVIxBnwiVVGJX+TFhsmu3uUf81JDGyijtYL9xgawlTw==}
engines: {node: '>= 10.8.0'}
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
@@ -3219,6 +3229,8 @@ snapshots:
dependencies:
flat-cache: 3.2.0
filesize@11.0.22: {}
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1

8
src/apis/mistake.ts Normal file
View File

@@ -0,0 +1,8 @@
import { cloudService } from './index'
import type { IMistake } from '@/types/mistake.ts'
export const queryMistakeListApi = (): Promise<IMistake[]> =>
cloudService({
url: '/study-api/mistake',
method: 'get'
})

26
src/apis/practice.ts Normal file
View File

@@ -0,0 +1,26 @@
import { cloudService } from './index'
import type { ILibraryFileQuestion } from '@/types/library.ts'
import type { IPracticeCheck, IPracticeRecord } from '@/types/practice.ts'
export const queryPracticeQuestionByIdApi = (id: number): Promise<ILibraryFileQuestion[]> =>
cloudService({
url: `/study-api/practice/files/${id}`,
method: 'get'
})
export const queryPracticeRecordApi = (id: number): Promise<IPracticeRecord[]> =>
cloudService({
url: `/study-api/practice/record/${id}`,
method: 'get'
})
export const checkPracticeApi = (id: number,
data: ILibraryFileQuestion[]): Promise<IPracticeCheck[]> =>
cloudService({
url: `/study-api/practice/check/${id}`,
method: 'post',
data
})

View File

@@ -3,13 +3,13 @@
title="新增资料" width="80%"
center destroy-on-close
class="file-dialog common-dialog">
<el-steps :active="activeStep" align-center>
<el-steps :active="libraryStore.activeStep" align-center>
<el-step title="上传资料" :icon="Upload" />
<el-step title="确认资料" :icon="Document " />
<el-step title="生成题库" :icon="MagicStick" />
</el-steps>
<el-form ref="libraryFileFormRef" v-if="activeStep === 1"
<el-form ref="libraryFileFormRef" v-if="libraryStore.activeStep === 1"
:model="libraryStore.currentFile"
:rules="rules" label-width="auto">
<el-form-item label="学科" prop="subject">
@@ -54,13 +54,19 @@
</el-form-item>
</el-form>
<QuillEditor v-if="activeStep === 2"
<QuillEditor v-if="libraryStore.activeStep === 2"
:content="libraryStore.currentFile.content"
contentType="html" theme="snow"/>
<div v-if="activeStep === 3" class="generate-question">
<div>
<el-button type="primary" @click="generateQuestion">生成题库</el-button>
<div v-if="libraryStore.activeStep === 3" class="generate-question">
<div class="generate-action">
<el-button type="primary" @click="generateQuestion">
{{ libraryStore.hasGenerateQuestion ? '重新生成' : '生成题库' }}
</el-button>
<span v-if="libraryStore.hasGenerateQuestion">
{{ libraryStore.currentFile.questions.length }} 道题目
</span>
</div>
<div v-if="libraryStore.hasGenerateQuestion" class="question-list">
@@ -91,14 +97,17 @@
</el-card>
</div>
<div v-else v-html="questionContent" />
<div v-else v-html="libraryStore.questionContent" />
</div>
<template #footer>
<el-button @click="libraryStore.fileDialogVisible = false">取消</el-button>
<el-button type="primary" @click="previousClick" :disabled="activeStep === 1">上一步</el-button>
<el-button type="primary" @click="nextClick" :disabled="activeStep === 3">一步</el-button>
<el-button type="primary" @click="submitForm()" :disabled="activeStep !== 3">确认</el-button>
<el-button type="primary" @click="previousClick"
:disabled="libraryStore.activeStep === 1">一步</el-button>
<el-button type="primary" @click="nextClick"
:disabled="libraryStore.activeStep === 3">下一步</el-button>
<el-button type="primary" @click="submitForm()"
:disabled="libraryStore.activeStep !== 3">确认</el-button>
</template>
</el-dialog>
</template>
@@ -111,13 +120,10 @@ import { Document, MagicStick, Upload, UploadFilled } from '@element-plus/icons-
import { QuillEditor } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'
import { uploadLibraryFileApi } from '@/apis/library.ts'
import { getModuleOptions } from '@/utils/library.ts'
import { getModuleOptions } from '@/utils/libraryUtil.ts'
const libraryStore = useLibraryStore()
const libraryFileFormRef = ref<FormInstance>()
const activeStep = ref(1)
const questionContent = ref<string>()
const rules = {
subject: [
@@ -132,13 +138,13 @@ const rules = {
}
const previousClick = () => {
if (activeStep.value > 1) {
activeStep.value--
if (libraryStore.activeStep > 1) {
libraryStore.activeStep--
}
}
const nextClick = async () => {
if (activeStep.value === 1) {
if (libraryStore.activeStep === 1) {
try {
await libraryFileFormRef.value?.validate()
} catch {
@@ -147,8 +153,8 @@ const nextClick = async () => {
}
}
if (activeStep.value < 3) {
activeStep.value++
if (libraryStore.activeStep < 3) {
libraryStore.activeStep++
}
}
@@ -187,7 +193,7 @@ const generateQuestion = async () => {
return
}
questionContent.value = ''
libraryStore.questionContent = ''
libraryStore.hasGenerateQuestion = false
const loading = ElLoading.service({
@@ -217,13 +223,13 @@ const generateQuestion = async () => {
while (true) {
const { value, done } = await reader.read()
if (done) break
questionContent.value += decoder.decode(value, { stream: true })
libraryStore.questionContent += decoder.decode(value, { stream: true })
}
ElMessage.success('生成完成')
libraryStore.hasGenerateQuestion = true
libraryStore.currentFile.questions = JSON.parse(questionContent.value)
libraryStore.currentFile.questions = JSON.parse(libraryStore.questionContent)
} catch (error) {
console.log(error)
ElMessage.error('生成失败 请联系管理员')
@@ -256,6 +262,12 @@ const submitForm = async () => {
display: flex;
flex-direction: column;
gap: 10px;
.generate-action {
display: flex;
align-items: center;
gap: 10px;
}
}
.question-list {

View File

@@ -1,8 +1,8 @@
<template>
<div class="file-list">
<library-filter v-if="libraryStore.fileList.length"/>
<library-filter v-if="filterLibraryFileList.length"/>
<div v-for="file in libraryStore.fileList" :key="file.id" class="file-card card-item">
<div v-for="file in filterLibraryFileList" :key="file.id" class="file-card card-item">
<div class="file-info">
<div class="file-icon" :class="file.type === 'image' ? 'icon-image' : 'icon-pdf'">
@@ -11,7 +11,7 @@
</div>
<div class="file-text">
<div class="file-name">{{ file.name }}</div>
<div class="file-meta">{{ file.size }} · {{ file.uploadTime }}</div>
<div class="file-meta">{{ filesize(file.size) }} · {{ fromNow(file.uploadTime)}}</div>
</div>
</div>
@@ -21,7 +21,7 @@
</div>
</div>
<el-empty v-if="!libraryStore.fileList.length" description="暂无文件" :image-size="80" />
<el-empty v-if="!filterLibraryFileList.length" description="暂无文件" :image-size="80" />
</div>
</template>
@@ -30,12 +30,22 @@ import { Delete, Edit, Picture, Document } from '@element-plus/icons-vue'
import { useLibraryStore } from '@/stores/library.ts'
import type { ILibraryFile } from '@/types/library.ts'
import LibraryFilter from '@/components/Library/LibraryFilter.vue'
import { computed } from 'vue'
import { fromNow } from '@/utils/dayUtil.ts'
import { filesize } from 'filesize'
const libraryStore = useLibraryStore()
const filterLibraryFileList = computed(() => {
const subject = libraryStore.selectedSubject
const module = libraryStore.selectedModule
return libraryStore.fileList.filter((item) => item.subject === subject && item.module === module)
})
const editClick = async (file: ILibraryFile) => {
await libraryStore.queryFileByList(file.id)
await libraryStore.queryFileById(file.id)
libraryStore.fileApiType = 'UPDATE'
libraryStore.activeStep = 1
libraryStore.hasGenerateQuestion = true
libraryStore.fileDialogVisible = true
}

View File

@@ -15,11 +15,26 @@
<script setup lang="ts">
import { useLibraryStore } from '@/stores/library.ts'
import { getModuleOptions } from '@/utils/library.ts'
import { getModuleOptions } from '@/utils/libraryUtil.ts'
const libraryStore = useLibraryStore()
const handleSubjectEvent = (value: string): void => {
libraryStore.moduleOptions = getModuleOptions(libraryStore.subjectTree, value)
libraryStore.selectedModule = libraryStore.moduleOptions[0].value
}
</script>
<style lang="scss">
.filter-bar {
display: flex;
flex-direction: column;
gap: 12px;
.filter-row {
display: flex;
align-items: center;
gap: 10px;
}
}
</style>

View File

@@ -0,0 +1,71 @@
<template>
<div class="practice-question">
<div v-if="practiceStore.hasStart" class="question-list">
<div v-for="(q, idx) in practiceStore.questionList" :key="q.id" class="question-card">
<div class="q-header">
<el-tag :type="q.type === 'single' ? 'primary' : 'warning'" effect="dark">
{{ q.type === 'single' ? '单选' : '多选' }}
</el-tag>
<span class="q-num">{{ idx + 1 }}</span>
</div>
<div class="q-stem">{{ q.question }}</div>
<div class="q-options">
<el-radio-group v-if="q.type === 'single'" v-model="q.select[0]">
<el-radio v-for="(opt, oi) in q.options" :key="oi" :value="optLetter(oi)">
{{ optLetter(oi) }}. {{ opt }}
</el-radio>
</el-radio-group>
<!-- 多选 -->
<el-checkbox-group v-if="q.type === 'multiple'" v-model="q.select">
<el-checkbox v-for="(opt, oi) in q.options" :key="oi" :value="optLetter(oi)">
{{ optLetter(oi) }}. {{ opt }}
</el-checkbox>
</el-checkbox-group>
</div>
<div v-if="practiceStore.checkList.length > 0" class="q-check">
<span style="font-weight: bold;"
:style="{color: practiceStore.checkList[idx].isCorrect ? 'green' : 'red'}">
正确答案 {{ practiceStore.checkList[idx].answer.join('、') }}
</span>
</div>
</div>
</div>
<div v-if="practiceStore.hasStart" class="question-action">
<el-button type="success" @click="submitAnswerClick">
{{ practiceStore.hasSubmit ? '重新练习' : '提交答案' }}
</el-button>
</div>
</div>
</template>
<script setup lang="ts">
import { usePracticeStore } from '@/stores/practice.ts'
import { ElMessage } from 'element-plus'
const practiceStore = usePracticeStore()
const optLetter = (index: number): string => {
return String.fromCharCode(65 + index)
}
const submitAnswerClick = async () => {
if (practiceStore.hasSubmit) {
practiceStore.hasSubmit = false
practiceStore.checkList = []
await practiceStore.queryQuestionById()
} else {
practiceStore.hasSubmit = true
if (practiceStore.questionList.find((item) => item.select.length === 0)) {
ElMessage.error('还有未完成的题目')
return
}
await practiceStore.checkQuestion()
}
}
</script>

View File

@@ -0,0 +1,34 @@
<template>
<div class="practice-record">
<div v-if="practiceStore.recordList.length > 0" class="record-list">
<div v-for="r in practiceStore.recordList" :key="r.id" class="record-card">
<div class="record-main">
<span class="record-status" :style="{color: getCorrectRate(r) < 50 ? 'red' : 'green'}">
正确率 {{ r.totalCount ? getCorrectRate(r) : 0 }}%
</span>
<span class="record-time">{{ fromNow(r.createTime) }}</span>
</div>
<div class="record-stats">
<span> {{ r.totalCount }}</span>
<span class="text-success"> {{ r.correctCount }}</span>
<span class="text-error"> {{ r.wrongCount }}</span>
</div>
</div>
</div>
<el-empty v-else description="暂无数据"></el-empty>
</div>
</template>
<script setup lang="ts">
import { usePracticeStore } from '@/stores/practice.ts'
import { fromNow } from '@/utils/dayUtil.ts'
import type { IPracticeRecord } from '@/types/practice.ts'
const practiceStore = usePracticeStore()
const getCorrectRate = (record: IPracticeRecord) => {
return Math.round((record.correctCount / record.totalCount) * 100)
}
</script>

View File

@@ -8,21 +8,33 @@ import {
} from '@/apis/library.ts'
import type { IHandleApi, IOptions, ITreeNode } from '@/types'
import { ElMessage } from 'element-plus'
import { getModuleOptions, getSubjectOptions, getSubjectTree } from '@/utils/library.ts'
import { getModuleOptions, getSubjectOptions, getSubjectTree } from '@/utils/libraryUtil.ts'
export const useLibraryStore = defineStore('library', {
state: () => ({
selectedSubject: '',
selectedModule: '',
selectedSubject: '' as number | string,
selectedModule: '' as number | string,
subjectTree: [] as ITreeNode[],
fileList: [] as ILibraryFile[],
currentFile: {} as ILibraryFile,
currentFile: {
id: 0,
subject: '',
module: '',
name: '',
size: 0,
uploadTime: '',
type: '',
content: '',
questions: []
} as ILibraryFile,
fileDetailDialogVisible: false,
fileDialogVisible: false,
hasGenerateQuestion: false,
fileApiType: 'ADD' as IHandleApi,
subjectOptions: [] as IOptions[],
moduleOptions: [] as IOptions[]
moduleOptions: [] as IOptions[],
activeStep: 1,
questionContent: ''
}),
actions: {
async queryFileList() {
@@ -30,11 +42,11 @@ export const useLibraryStore = defineStore('library', {
this.fileList = await queryLibraryFileListApi()
this.subjectTree = getSubjectTree(this.fileList)
this.subjectOptions = getSubjectOptions(this.subjectTree)
this.selectedSubject = this.subjectOptions[0].label
this.selectedSubject = this.subjectOptions[0].value
this.moduleOptions = getModuleOptions(this.subjectTree, this.selectedSubject)
this.selectedModule = this.moduleOptions[0].label
this.selectedModule = this.moduleOptions[0].value
},
async queryFileByList(id: number) {
async queryFileById(id: number) {
this.currentFile = await queryLibraryFileByIdApi(id)
},
async handleFileApi(file: ILibraryFile) {

14
src/stores/mistake.ts Normal file
View File

@@ -0,0 +1,14 @@
import { defineStore } from 'pinia'
import type { IMistake } from '@/types/mistake.ts'
import { queryMistakeListApi } from '@/apis/mistake.ts'
export const useMistakeStore = defineStore('mistake', {
state: () => ({
mistakeList: [] as IMistake[]
}),
actions: {
async queryMistakeList() {
this.mistakeList = await queryMistakeListApi()
}
}
})

26
src/stores/practice.ts Normal file
View File

@@ -0,0 +1,26 @@
import { defineStore } from 'pinia'
import type { ILibraryFileQuestion } from '@/types/library.ts'
import { checkPracticeApi, queryPracticeQuestionByIdApi, queryPracticeRecordApi } from '@/apis/practice.ts'
import type { IPracticeCheck, IPracticeRecord } from '@/types/practice.ts'
export const usePracticeStore = defineStore('practice', {
state: () => ({
hasStart: false,
hasSubmit: false,
libraryFileId: 0,
questionList: [] as ILibraryFileQuestion[],
recordList: [] as IPracticeRecord[],
checkList: [] as IPracticeCheck[]
}),
actions: {
async queryQuestionById() {
this.questionList = await queryPracticeQuestionByIdApi(this.libraryFileId)
},
async queryRecord() {
this.recordList = await queryPracticeRecordApi(this.libraryFileId)
},
async checkQuestion() {
this.checkList = await checkPracticeApi(this.libraryFileId, this.questionList)
}
}
})

View File

@@ -1,5 +1,27 @@
.common-view {
display: flex;
flex-direction: column;
gap: 10px;
.view-header {
display: flex;
justify-content: space-between;
align-items: center;
.view-content {
.view-title {
margin: 0;
font-size: 22px;
font-weight: 700;
color: #0f172a;
}
.view-subtitle {
font-size: 13px;
color: #94a3b8;
}
}
}
}
.common-dialog {

View File

@@ -4,6 +4,7 @@ export interface ILibraryFileQuestion {
question: string;
options: string[];
answer: string[];
select: string[];
}
export interface ILibraryFile {

11
src/types/mistake.ts Normal file
View File

@@ -0,0 +1,11 @@
export interface IMistake {
id: number;
subject: string;
module: string;
name: string;
type: string;
question: string;
options: string[];
answer: string[];
wrongCount: number;
}

15
src/types/practice.ts Normal file
View File

@@ -0,0 +1,15 @@
export interface IPracticeCheck {
id: number;
answer: string[];
select: string[];
isCorrect: boolean;
}
export interface IPracticeRecord {
id: number;
libraryFileId:number;
totalCount: number;
correctCount: number;
wrongCount: number;
createTime: string;
}

8
src/utils/dayUtil.ts Normal file
View File

@@ -0,0 +1,8 @@
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import 'dayjs/locale/zh-cn'
dayjs.extend(relativeTime)
dayjs.locale('zh-cn')
export const fromNow = (time: Date | string | number) => dayjs(time).fromNow()

View File

@@ -42,7 +42,7 @@ export const getSubjectOptions = (subjectTree: ITreeNode[]): IOptions[] => {
return subjectTree.map((s) => ({ label: s.name, value: s.name }))
}
export const getModuleOptions = (subjectTree: ITreeNode[], subject: string): IOptions[] => {
export const getModuleOptions = (subjectTree: ITreeNode[], subject: string | number): IOptions[] => {
const node = subjectTree.find((s) => s.name === subject)
return (node?.children || []).map((m) => ({
label: m.name,

View File

@@ -1,9 +1,9 @@
<template>
<div class="library-view">
<div class="page-header">
<div>
<h2 class="page-title">知识库</h2>
<p class="page-subtitle">管理学习资料查看 AI 解析结果</p>
<div class="library-view common-view">
<div class="view-header">
<div class="view-content">
<h2 class="view-title">知识库</h2>
<p class="view-subtitle">管理学习资料查看 AI 解析结果</p>
</div>
<el-button type="primary" :icon="Plus" @click="addFileClick">新增资料</el-button>
</div>
@@ -30,49 +30,15 @@ onMounted(async () => {
const addFileClick = () => {
libraryStore.currentFile = libraryStore.getEmptyFile()
libraryStore.fileApiType = 'ADD'
libraryStore.activeStep = 1
libraryStore.hasGenerateQuestion = false
libraryStore.questionContent = ''
libraryStore.fileDialogVisible = true
}
</script>
<style lang="scss">
.library-view {
display: flex;
flex-direction: column;
gap: 10px;
/* 标题行 */
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
.page-title {
font-size: 22px;
font-weight: 700;
color: #0f172a;
}
.page-subtitle {
font-size: 13px;
color: #94a3b8;
}
}
/* 筛选栏 */
.filter-bar {
display: flex;
flex-direction: column;
gap: 12px;
.filter-row {
display: flex;
align-items: center;
gap: 10px;
}
}
/* 文件列表 */
.file-list {
display: flex;
flex-direction: column;

View File

@@ -1,11 +1,105 @@
<template>
<div>
<h2>错题本</h2>
<p style="color:#94a3b8;margin-top:8px;">回顾错题查漏补缺</p>
<el-divider />
<el-empty description="错题本页面 - 待开发" />
<div class="mistake-view common-view">
<div class="view-header">
<div class="view-content">
<h2 class="view-title">错题本</h2>
<p class="view-subtitle">回顾错题查漏补缺</p>
</div>
</div>
<div class="mistake-record">
<div class="mistake-list">
<el-card
v-for="item in mistakeStore.mistakeList"
:key="item.id"
class="mistake-card"
shadow="never"
>
<div class="library">
<el-tag type="primary" effect="dark">{{ item.subject }} · {{ item.module }}</el-tag>
<el-tag type="danger" effect="dark"> {{ item.wrongCount }} </el-tag>
</div>
<div class="question">{{ item.question }}</div>
<div class="option-list">
<div v-for="(opt, index) in item.options" :key="index" class="option"
:class="optionClass(item, index)">
{{ letter(index) }}{{ opt }}
</div>
</div>
</el-card>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { useMistakeStore } from '@/stores/mistake.ts'
const mistakeStore = useMistakeStore()
onMounted(async () => {
await mistakeStore.queryMistakeList()
})
const letter = (i: number) => String.fromCharCode(65 + i)
const optionClass = (item: any, index: number) => {
const letters = ['A', 'B', 'C', 'D']
const isCorrect = item.answer.includes(letters[index])
return {
correct: isCorrect
}
}
</script>
<style lang="scss">
.mistake-list {
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px;
background: #f7f8fa;
}
.mistake-card {
.el-card__body {
display: flex;
flex-direction: column;
gap: 10px;
}
.library {
display: flex;
justify-content: space-between;
}
.question {
font-size: 15px;
font-weight: 500;
line-height: 1.6;
}
.option-list {
display: flex;
flex-direction: column;
gap: 8px;
.option {
padding: 10px 12px;
border-radius: 8px;
border: 1px solid #e5e6eb;
font-size: 14px;
background: #fff;
}
.option.correct {
border-color: #52c41a;
background: #f6ffed;
color: #389e0d;
}
}
}
</style>

View File

@@ -1,11 +1,182 @@
<template>
<div>
<h2>训练场</h2>
<p style="color:#94a3b8;margin-top:8px;">专项练习巩固知识点</p>
<el-divider />
<el-empty description="训练场页面 - 待开发" />
<div class="practice-view common-view">
<div class="view-header">
<div class="view-content">
<h2 class="view-title">训练场</h2>
<p class="view-subtitle">专项练习巩固知识点</p>
</div>
<div class="view-action">
<el-button v-if="!practiceStore.hasStart" type="primary" :icon="Aim"
@click="startPracticeClick">开始练习</el-button>
</div>
</div>
<library-filter />
<practice-question v-if="practiceStore.hasStart"/>
<practice-record v-else/>
</div>
</template>
<script setup lang="ts">
import LibraryFilter from '@/components/Library/LibraryFilter.vue'
import PracticeQuestion from '@/components/Practice/PracticeQuestion.vue'
import PracticeRecord from '@/components/Practice/PracticeRecord.vue'
import { onMounted, watch } from 'vue'
import { useLibraryStore } from '@/stores/library'
import { usePracticeStore } from '@/stores/practice.ts'
import { Aim } from '@element-plus/icons-vue'
const libraryStore = useLibraryStore()
const practiceStore = usePracticeStore()
onMounted(async () => {
practiceStore.hasStart = false
await libraryStore.queryFileList()
})
watch(() => libraryStore.selectedModule, async () => {
practiceStore.hasStart = false
const subject = libraryStore.selectedSubject
const module = libraryStore.selectedModule
const fileList = libraryStore.fileList.filter((item) => {
return item.subject === subject && item.module === module
})
if (fileList.length > 0) {
const { id } = fileList[0]
practiceStore.libraryFileId = id
await practiceStore.queryRecord()
await practiceStore.queryQuestionById()
}
}, {
immediate: true
})
const startPracticeClick = () => {
practiceStore.hasStart = true
practiceStore.checkList = []
}
</script>
<style lang="scss">
.practice-view {
.practice-question {
display: flex;
flex-direction: column;
gap: 16px;
.question-action {
align-self: center;
display: flex;
align-items: center;
gap: 10px;
}
.question-list {
background: #fff;
border-radius: 12px;
display: flex;
flex-direction: column;
.question-card {
padding: 16px;
display: flex;
flex-direction: column;
gap: 10px;
border-bottom: 1px solid lightgray;
&:last-child {
border-bottom: none;
}
.q-header {
display: flex;
align-items: center;
gap: 10px;
.q-num {
font-size: 13px;
}
}
.q-stem {
font-size: 16px;
font-weight: 500;
line-height: 1.7;
}
.q-options {
display: flex;
flex-direction: column;
gap: 8px;
}
.q-check {
font-size: 13px;
}
}
}
}
.practice-record {
.record-list {
background: #fff;
border-radius: 12px;
display: flex;
flex-direction: column;
.record-card {
padding: 16px;
border-bottom: 1px solid lightgray;
&:last-child {
border-bottom: none;
}
display: flex;
justify-content: space-between;
align-items: center;
.record-main {
display: flex;
flex-direction: column;
gap: 5px;
.record-time {
font-size: 13px;
color: #999;
}
.record-status {
font-size: 15px;
font-weight: 600;
}
}
.record-stats {
display: flex;
gap: 12px;
font-size: 14px;
color: #555;
.text-success {
color: #52c41a;
}
.text-error {
color: #ff4d4f;
}
}
}
}
}
}
</style>