diff --git a/src/apis/index.ts b/src/apis/index.ts new file mode 100644 index 0000000..b8e969a --- /dev/null +++ b/src/apis/index.ts @@ -0,0 +1,3 @@ +import createService from '@/utils/axiosUtil.ts' + +export const cloudService = createService('', 'study') diff --git a/src/apis/library.ts b/src/apis/library.ts new file mode 100644 index 0000000..c58b820 --- /dev/null +++ b/src/apis/library.ts @@ -0,0 +1,40 @@ +import { cloudService } from './index' +import type { ILibraryFile } from '@/types/library.ts' + +export const uploadLibraryFileApi = (formData: FormData): Promise => + cloudService({ + url: '/study-api/library/upload', + method: 'post', + data: formData, + headers: { + 'Content-Type': 'multipart/form-data' + } + }) + +export const addLibraryFileApi = (file: ILibraryFile): Promise => + cloudService({ + url: '/study-api/library/files', + method: 'post', + data: file + }) + +export const updateLibraryFileApi = (id: number, file: ILibraryFile): Promise => + cloudService({ + url: `/study-api/library/files/${id}`, + method: 'put', + data: file + }) + + +export const queryLibraryFileListApi = (): Promise => + cloudService({ + url: '/study-api/library/files', + method: 'get' + }) + +export const queryLibraryFileByIdApi = (id: number): Promise => + cloudService({ + url: `/study-api/library/files/${id}`, + method: 'get' + }) + diff --git a/src/components/Library/LibraryFileDetailDialog.vue b/src/components/Library/LibraryFileDetailDialog.vue deleted file mode 100644 index 8b0b89b..0000000 --- a/src/components/Library/LibraryFileDetailDialog.vue +++ /dev/null @@ -1,135 +0,0 @@ - - - - - diff --git a/src/components/Library/LibraryFileDialog.vue b/src/components/Library/LibraryFileDialog.vue index 3c8c6e1..c7460cf 100644 --- a/src/components/Library/LibraryFileDialog.vue +++ b/src/components/Library/LibraryFileDialog.vue @@ -14,17 +14,21 @@ :rules="rules" label-width="auto"> - + filterable allow-create + @change="handleSubjectEvent" + placeholder="请输入或选择学科名称"> + - + filterable allow-create + :disabled="!libraryStore.currentFile.subject" + placeholder="请输入或选择模块名称"> + @@ -50,14 +54,16 @@ - +
- 生成题库 + 生成题库
-
+
@@ -102,19 +108,15 @@ import { ref } from 'vue' import { useLibraryStore } from '@/stores/library.ts' import { ElLoading, ElMessage, type FormInstance, type UploadRequestOptions } from 'element-plus' import { Document, MagicStick, Upload, UploadFilled } from '@element-plus/icons-vue' -import axios from 'axios' 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' const libraryStore = useLibraryStore() const libraryFileFormRef = ref() const activeStep = ref(1) -const hasGenerate = ref(false) - -const subjectOptions = ['中医学', '公务员'] - -const fileContent = ref() const questionContent = ref() const rules = { @@ -150,6 +152,10 @@ const nextClick = async () => { } } +const handleSubjectEvent = (value: string) => { + libraryStore.moduleOptions = getModuleOptions(libraryStore.subjectTree, value) +} + const upload = async (options: UploadRequestOptions) => { const { file } = options libraryStore.currentFile.size = file.size @@ -165,13 +171,8 @@ const upload = async (options: UploadRequestOptions) => { }) try { - const res = await axios.post('http://127.0.0.1:8000/upload', formData, { - headers: { - 'Content-Type': 'multipart/form-data' - } - }) + libraryStore.currentFile.content = await uploadLibraryFileApi(formData) ElMessage.success('上传成功') - fileContent.value = res.data.text } catch (error) { console.log(error) ElMessage.error('上传失败 请联系管理员') @@ -180,14 +181,14 @@ const upload = async (options: UploadRequestOptions) => { } } -const generate = async () => { - if (!fileContent.value) { +const generateQuestion = async () => { + if (!libraryStore.currentFile.content) { ElMessage.error('请先上传资料') return } questionContent.value = '' - hasGenerate.value = false + libraryStore.hasGenerateQuestion = false const loading = ElLoading.service({ lock: true, @@ -196,13 +197,13 @@ const generate = async () => { }) try { - const response = await fetch('http://127.0.0.1:8000/generate', { + const response = await fetch('/study-api/library/question', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - message: fileContent.value + message: libraryStore.currentFile.content }) }) @@ -220,7 +221,7 @@ const generate = async () => { } ElMessage.success('生成完成') - hasGenerate.value = true + libraryStore.hasGenerateQuestion = true libraryStore.currentFile.questions = JSON.parse(questionContent.value) } catch (error) { @@ -233,11 +234,7 @@ const generate = async () => { const submitForm = async () => { if (libraryStore.currentFile.questions.length > 0) { - const res = await axios.post('http://127.0.0.1:8000/files', libraryStore.currentFile, { - headers: { - 'Content-Type': 'application/json' - } - }) + await libraryStore.handleFileApi(libraryStore.currentFile) } else { ElMessage.error('请先生成题库') } diff --git a/src/components/Library/LibraryFileList.vue b/src/components/Library/LibraryFileList.vue index ef2b678..ee12fd3 100644 --- a/src/components/Library/LibraryFileList.vue +++ b/src/components/Library/LibraryFileList.vue @@ -1,9 +1,11 @@ @@ -27,11 +29,14 @@ 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' const libraryStore = useLibraryStore() -const viewDetailClick = (file: ILibraryFile) => { - libraryStore.currentFile = file - libraryStore.fileDetailDialogVisible = true +const editClick = async (file: ILibraryFile) => { + await libraryStore.queryFileByList(file.id) + libraryStore.fileApiType = 'UPDATE' + libraryStore.hasGenerateQuestion = true + libraryStore.fileDialogVisible = true } diff --git a/src/components/Library/LibraryFilter.vue b/src/components/Library/LibraryFilter.vue index e99decd..fb36873 100644 --- a/src/components/Library/LibraryFilter.vue +++ b/src/components/Library/LibraryFilter.vue @@ -2,30 +2,24 @@
学科 - +
模块 - +
diff --git a/src/stores/library.ts b/src/stores/library.ts index c650032..b191e12 100644 --- a/src/stores/library.ts +++ b/src/stores/library.ts @@ -1,82 +1,60 @@ import { defineStore } from 'pinia' -import type { ILibraryFile, ITreeNode } from '@/types/library.ts' +import type { ILibraryFile } from '@/types/library.ts' +import { + addLibraryFileApi, + queryLibraryFileByIdApi, + queryLibraryFileListApi, + updateLibraryFileApi +} from '@/apis/library.ts' +import type { IHandleApi, IOptions, ITreeNode } from '@/types' +import { ElMessage } from 'element-plus' +import { getModuleOptions, getSubjectOptions, getSubjectTree } from '@/utils/library.ts' export const useLibraryStore = defineStore('library', { state: () => ({ - selectedSubject: 1, - selectedModule: 101, - subjectTree: [ - { - id: 1, - name: '中医学', - children: [ - { id: 101, name: '中医基础理论' }, - { id: 102, name: '中医诊断学' }, - { id: 103, name: '中药学' }, - { id: 104, name: '方剂学' }, - { id: 105, name: '中医内科学' }, - { id: 106, name: '中医外科学' }, - { id: 107, name: '中医妇科学' }, - { id: 108, name: '中医儿科学' }, - { id: 109, name: '针灸学' }, - { id: 110, name: '推拿学' }, - { id: 111, name: '中医骨伤科学' }, - { id: 112, name: '黄帝内经' }, - { id: 113, name: '伤寒论' }, - { id: 114, name: '金匮要略' }, - { id: 115, name: '温病学' }, - { id: 116, name: '中医养生学' } - ] - }, - { - id: 2, - name: '公务员', - children: [ - { id: 201, name: '言语理解与表达' }, - { id: 202, name: '数量关系' }, - { id: 203, name: '判断推理' }, - { id: 204, name: '资料分析' }, - { id: 205, name: '常识判断' }, - { id: 206, name: '申论写作' }, - { id: 207, name: '公文写作' }, - { id: 208, name: '公共基础知识' }, - { id: 209, name: '面试技巧' }, - { id: 210, name: '时政热点' }, - { id: 211, name: '法律基础' } - ] - } - ] as ITreeNode[], + selectedSubject: '', + selectedModule: '', + subjectTree: [] as ITreeNode[], fileList: [] as ILibraryFile[], currentFile: {} as ILibraryFile, fileDetailDialogVisible: false, - fileDialogVisible: false + fileDialogVisible: false, + hasGenerateQuestion: false, + fileApiType: 'ADD' as IHandleApi, + subjectOptions: [] as IOptions[], + moduleOptions: [] as IOptions[] }), actions: { - queryFileList() { - this.fileList = [ - { - id: 1, - subject: '中医学', - module: '中医基础理疗', - name: '向量代数.pdf', - size: 2.4, - uploadTime: '2分钟前', - type: 'pdf', - questions: [ - { id: 1, type: 'single', question: '设 a=(1,2,3), b=(2,-1,1),求 a·b = ?', options: ['A. 3', 'B. 5', 'C. 7', 'D. 9'], answer: ['A'] } - ] - }, - { - id: 2, - subject: '中医学', - module: '中医基础理疗', - name: '向量代数精讲.png', - size: 156, - uploadTime: '3小时前', - type: 'image', - questions: [] - } - ] + async queryFileList() { + this.fileList = [] + this.fileList = await queryLibraryFileListApi() + this.subjectTree = getSubjectTree(this.fileList) + this.subjectOptions = getSubjectOptions(this.subjectTree) + this.selectedSubject = this.subjectOptions[0].label + this.moduleOptions = getModuleOptions(this.subjectTree, this.selectedSubject) + this.selectedModule = this.moduleOptions[0].label + }, + async queryFileByList(id: number) { + this.currentFile = await queryLibraryFileByIdApi(id) + }, + async handleFileApi(file: ILibraryFile) { + switch (this.fileApiType) { + case 'ADD': + await addLibraryFileApi(file) + ElMessage.success('新增资料成功') + break + case 'UPDATE': + await updateLibraryFileApi(file.id as number, file) + ElMessage.success('更新资料成功') + break + case 'DELETE': + ElMessage.success('删除资料成功') + break + default: + break + } + this.fileDialogVisible = false + await this.queryFileList() }, getEmptyFile(): ILibraryFile { return { @@ -87,6 +65,7 @@ export const useLibraryStore = defineStore('library', { subject: '', uploadTime: '', type: '', + content: '', questions: [] } } diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..8b0b517 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,12 @@ +export type IHandleApi = 'ADD' | 'UPDATE' | 'DELETE'; + +export interface ITreeNode { + id: number; + name: string; + children?: ITreeNode[]; +} + +export interface IOptions { + label: string; + value: string | number; +} diff --git a/src/types/library.ts b/src/types/library.ts index 85bea2d..cb8c6d0 100644 --- a/src/types/library.ts +++ b/src/types/library.ts @@ -1,9 +1,3 @@ -export interface ITreeNode { - id: number; - name: string; - children?: ITreeNode[]; -} - export interface ILibraryFileQuestion { id: number; type: string; @@ -20,5 +14,6 @@ export interface ILibraryFile { size: number; uploadTime: string; type: string; + content: string; questions: ILibraryFileQuestion[]; } diff --git a/src/utils/axiosUtil.ts b/src/utils/axiosUtil.ts new file mode 100644 index 0000000..c6f6988 --- /dev/null +++ b/src/utils/axiosUtil.ts @@ -0,0 +1,91 @@ +import axios, { AxiosError } from 'axios' +import type { InternalAxiosRequestConfig, AxiosResponse } from 'axios' + +import { ElMessage } from 'element-plus' + +const createService = (baseURL = '', prefix = '', timeout = 5000) => { + const service = axios.create({ + baseURL, + timeout + }) + + // 请求拦截器 + service.interceptors.request.use( + (config: InternalAxiosRequestConfig) => { + const token = localStorage.getItem(prefix ? `${prefix}-token` : 'token') + if (token) { + config.headers.satoken = prefix ? `${prefix} ${token}` : token + } + + return config + }, + + (error: AxiosError) => { + return Promise.reject(error) + } + ) + + // 响应拦截器 + service.interceptors.response.use( + (response: AxiosResponse) => { + return response.data + }, + (error: AxiosError) => { + const { code, message } = error + if (error.response) { + // 发送请求成功 并收到服务器的响应 + handleServiceError(error.response, message) + return Promise.reject(error) + } else if (error.request) { + // 发送请求成功 未收到服务器的响应 + handleHttpError(code, message) + return Promise.reject(error) + } else { + // 其他错误情况 如发送请求失败等 + ElMessage.error(`前端错误 请联系管理人员:${message}`) + } + } + ) + + return service +} + +/** + * 处理服务器响应错误 + * @param response 服务器响应 + * @param message 错误消息 + */ +const handleServiceError = (response: AxiosResponse, message: string) => { + const { status, data, statusText } = response + + switch (status) { + case 401: + ElMessage.error('账号已过期 请重新登录') + window.location.href = '/' + break + case 500: + ElMessage.error(`${data.message || statusText}`) + break + default: + ElMessage.error(`服务器错误 请联系管理人员:${status}:${message}`) + } +} + +/** + * 处理Http错误 + * @param code code码 + * @param message 消息 + */ +const handleHttpError = (code: string | undefined, message: string) => { + switch (code) { + case 'ECONNABORTED': + case 'ETIMEDOUT': + ElMessage.error('网络连接错误 请联系管理人员') + break + default: + ElMessage.error(`网络错误 请联系管理人员:${code}:${message}`) + break + } +} + +export default createService diff --git a/src/utils/library.ts b/src/utils/library.ts new file mode 100644 index 0000000..d0dfa18 --- /dev/null +++ b/src/utils/library.ts @@ -0,0 +1,51 @@ +import type { ILibraryFile } from '@/types/library.ts' +import type { IOptions, ITreeNode } from '@/types' + +export const getSubjectTree = (files: ILibraryFile[]): ITreeNode[] => { + const subjectMap = new Map() + let subjectIdCounter = 1 + let moduleIdCounter = 1000 + + files.forEach((f) => { + // 查找是否已存在该学科节点 + let subjectNode = Array.from(subjectMap.values()).find( + (node) => node.name === f.subject + ) + + // 不存在则创建学科节点 + if (!subjectNode) { + subjectNode = { + id: subjectIdCounter++, + name: f.subject, + children: [] + } + subjectMap.set(subjectNode.id, subjectNode) + } + + // 避免重复模块 + const exists = subjectNode.children?.some( + (c) => c.name === f.module + ) + + if (!exists) { + subjectNode.children!.push({ + id: moduleIdCounter++, + name: f.module + }) + } + }) + + return Array.from(subjectMap.values()) +} + +export const getSubjectOptions = (subjectTree: ITreeNode[]): IOptions[] => { + return subjectTree.map((s) => ({ label: s.name, value: s.name })) +} + +export const getModuleOptions = (subjectTree: ITreeNode[], subject: string): IOptions[] => { + const node = subjectTree.find((s) => s.name === subject) + return (node?.children || []).map((m) => ({ + label: m.name, + value: m.name + })) +} diff --git a/src/views/Library.vue b/src/views/Library.vue index c1916b0..e4ec928 100644 --- a/src/views/Library.vue +++ b/src/views/Library.vue @@ -1,6 +1,5 @@ diff --git a/vite.config.ts b/vite.config.ts index 8e50646..9249864 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -21,10 +21,12 @@ export default defineConfig({ } }, server: { - port: 5174, - // 服务器代理 服务器没有部署Nginx的情况下使用 proxy: { - + '/study-api': { + target: 'http://127.0.0.1:8000', + changeOrigin: true, + rewrite: (item) => item.replace(/^\/study-api/, '') + } } } })