diff --git a/package.json b/package.json index cd41615..0e908da 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 35feaa4..c3c3150 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 diff --git a/src/apis/mistake.ts b/src/apis/mistake.ts new file mode 100644 index 0000000..a20772f --- /dev/null +++ b/src/apis/mistake.ts @@ -0,0 +1,8 @@ +import { cloudService } from './index' +import type { IMistake } from '@/types/mistake.ts' + +export const queryMistakeListApi = (): Promise => + cloudService({ + url: '/study-api/mistake', + method: 'get' + }) diff --git a/src/apis/practice.ts b/src/apis/practice.ts new file mode 100644 index 0000000..77fb41f --- /dev/null +++ b/src/apis/practice.ts @@ -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 => + cloudService({ + url: `/study-api/practice/files/${id}`, + method: 'get' + }) + + +export const queryPracticeRecordApi = (id: number): Promise => + cloudService({ + url: `/study-api/practice/record/${id}`, + method: 'get' + }) + +export const checkPracticeApi = (id: number, + data: ILibraryFileQuestion[]): Promise => + cloudService({ + url: `/study-api/practice/check/${id}`, + method: 'post', + data + }) + diff --git a/src/components/Library/LibraryFileDialog.vue b/src/components/Library/LibraryFileDialog.vue index c7460cf..6b06ea4 100644 --- a/src/components/Library/LibraryFileDialog.vue +++ b/src/components/Library/LibraryFileDialog.vue @@ -3,13 +3,13 @@ title="新增资料" width="80%" center destroy-on-close class="file-dialog common-dialog"> - + - @@ -54,13 +54,19 @@ - -
-
- 生成题库 +
+
+ + {{ libraryStore.hasGenerateQuestion ? '重新生成' : '生成题库' }} + + + + 共 {{ libraryStore.currentFile.questions.length }} 道题目 +
@@ -91,14 +97,17 @@
-
+
@@ -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() -const activeStep = ref(1) -const questionContent = ref() 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 { diff --git a/src/components/Library/LibraryFileList.vue b/src/components/Library/LibraryFileList.vue index ee12fd3..5feaf70 100644 --- a/src/components/Library/LibraryFileList.vue +++ b/src/components/Library/LibraryFileList.vue @@ -1,8 +1,8 @@ @@ -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 } diff --git a/src/components/Library/LibraryFilter.vue b/src/components/Library/LibraryFilter.vue index fb36873..b50bcf1 100644 --- a/src/components/Library/LibraryFilter.vue +++ b/src/components/Library/LibraryFilter.vue @@ -15,11 +15,26 @@ + + diff --git a/src/components/Practice/PracticeQuestion.vue b/src/components/Practice/PracticeQuestion.vue new file mode 100644 index 0000000..a069d99 --- /dev/null +++ b/src/components/Practice/PracticeQuestion.vue @@ -0,0 +1,71 @@ + + + diff --git a/src/components/Practice/PracticeRecord.vue b/src/components/Practice/PracticeRecord.vue new file mode 100644 index 0000000..6641a17 --- /dev/null +++ b/src/components/Practice/PracticeRecord.vue @@ -0,0 +1,34 @@ + + + diff --git a/src/stores/library.ts b/src/stores/library.ts index b191e12..f804f2d 100644 --- a/src/stores/library.ts +++ b/src/stores/library.ts @@ -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) { diff --git a/src/stores/mistake.ts b/src/stores/mistake.ts new file mode 100644 index 0000000..34412fc --- /dev/null +++ b/src/stores/mistake.ts @@ -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() + } + } +}) diff --git a/src/stores/practice.ts b/src/stores/practice.ts new file mode 100644 index 0000000..ba90771 --- /dev/null +++ b/src/stores/practice.ts @@ -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) + } + } +}) diff --git a/src/styles/common.module.scss b/src/styles/common.module.scss index 76319a1..bc84020 100644 --- a/src/styles/common.module.scss +++ b/src/styles/common.module.scss @@ -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 { diff --git a/src/types/library.ts b/src/types/library.ts index cb8c6d0..6229de6 100644 --- a/src/types/library.ts +++ b/src/types/library.ts @@ -4,6 +4,7 @@ export interface ILibraryFileQuestion { question: string; options: string[]; answer: string[]; + select: string[]; } export interface ILibraryFile { diff --git a/src/types/mistake.ts b/src/types/mistake.ts new file mode 100644 index 0000000..9aa962e --- /dev/null +++ b/src/types/mistake.ts @@ -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; +} diff --git a/src/types/practice.ts b/src/types/practice.ts new file mode 100644 index 0000000..462442d --- /dev/null +++ b/src/types/practice.ts @@ -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; +} diff --git a/src/utils/dayUtil.ts b/src/utils/dayUtil.ts new file mode 100644 index 0000000..bc8ac4f --- /dev/null +++ b/src/utils/dayUtil.ts @@ -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() diff --git a/src/utils/library.ts b/src/utils/libraryUtil.ts similarity index 97% rename from src/utils/library.ts rename to src/utils/libraryUtil.ts index d0dfa18..e1ddfa3 100644 --- a/src/utils/library.ts +++ b/src/utils/libraryUtil.ts @@ -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, diff --git a/src/views/Library.vue b/src/views/Library.vue index e4ec928..015f188 100644 --- a/src/views/Library.vue +++ b/src/views/Library.vue @@ -1,9 +1,9 @@