feat:封装axios接口
This commit is contained in:
3
src/apis/index.ts
Normal file
3
src/apis/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import createService from '@/utils/axiosUtil.ts'
|
||||
|
||||
export const cloudService = createService('', 'study')
|
||||
40
src/apis/library.ts
Normal file
40
src/apis/library.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { cloudService } from './index'
|
||||
import type { ILibraryFile } from '@/types/library.ts'
|
||||
|
||||
export const uploadLibraryFileApi = (formData: FormData): Promise<string> =>
|
||||
cloudService({
|
||||
url: '/study-api/library/upload',
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
|
||||
export const addLibraryFileApi = (file: ILibraryFile): Promise<boolean> =>
|
||||
cloudService({
|
||||
url: '/study-api/library/files',
|
||||
method: 'post',
|
||||
data: file
|
||||
})
|
||||
|
||||
export const updateLibraryFileApi = (id: number, file: ILibraryFile): Promise<boolean> =>
|
||||
cloudService({
|
||||
url: `/study-api/library/files/${id}`,
|
||||
method: 'put',
|
||||
data: file
|
||||
})
|
||||
|
||||
|
||||
export const queryLibraryFileListApi = (): Promise<ILibraryFile[]> =>
|
||||
cloudService({
|
||||
url: '/study-api/library/files',
|
||||
method: 'get'
|
||||
})
|
||||
|
||||
export const queryLibraryFileByIdApi = (id: number): Promise<ILibraryFile> =>
|
||||
cloudService({
|
||||
url: `/study-api/library/files/${id}`,
|
||||
method: 'get'
|
||||
})
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
<template>
|
||||
<el-dialog v-model="libraryStore.fileDetailDialogVisible"
|
||||
:title="libraryStore.currentFile.name" center
|
||||
width="90%" destroy-on-close class="file-detail-dialog">
|
||||
<el-splitter>
|
||||
<el-splitter-panel size="50%">
|
||||
<div class="raw-file">
|
||||
<div v-if="libraryStore.currentFile.type === 'pdf'" class="preview">
|
||||
<p>原文预览</p>
|
||||
</div>
|
||||
<div v-else class="preview-box">
|
||||
<p>原文预览</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-splitter-panel>
|
||||
<el-splitter-panel size="50%">
|
||||
<div class="section">
|
||||
<div class="section-title">
|
||||
<el-icon><EditPen /></el-icon>
|
||||
生成题目({{ libraryStore.currentFile.questions?.length || 0 }} 道)
|
||||
</div>
|
||||
<div v-for="(q, i) in libraryStore.currentFile.questions" :key="i" class="question-card">
|
||||
<div class="q-header">
|
||||
<span class="q-num">Q{{ i + 1 }}</span>
|
||||
<el-tag size="small" type="primary" effect="dark">{{ q.type }}</el-tag>
|
||||
</div>
|
||||
<div class="q-body">{{ q.question }}</div>
|
||||
<ul v-if="q.options" class="q-options">
|
||||
<li v-for="opt in q.options" :key="opt">{{ opt }}</li>
|
||||
</ul>
|
||||
<div class="q-answer">
|
||||
<span class="label">答案:</span>{{ q.answer }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-splitter-panel>
|
||||
</el-splitter>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="libraryStore.fileDetailDialogVisible = false">关闭</el-button>
|
||||
<el-button type="primary" @click="libraryStore.fileDetailDialogVisible = false">开始练习</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { EditPen } from '@element-plus/icons-vue'
|
||||
import { useLibraryStore } from '@/stores/library.ts'
|
||||
|
||||
const libraryStore = useLibraryStore()
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.file-detail-dialog {
|
||||
.raw-file {
|
||||
height: 100%;
|
||||
padding: 10px;
|
||||
|
||||
.preview {
|
||||
height: 100%;
|
||||
padding: 10px;
|
||||
background: #f8fafc;
|
||||
border: 2px dashed #e2e8f0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 知识点 + 题目 */
|
||||
.section {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.section-title {
|
||||
align-self: center;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.question-card {
|
||||
background: #f8fafc;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
|
||||
.q-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.q-num {
|
||||
font-weight: 700;
|
||||
color: #14b8a6;
|
||||
}
|
||||
}
|
||||
|
||||
.q-body {
|
||||
font-size: 14px;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.q-options {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0 0 8px;
|
||||
|
||||
li {
|
||||
padding: 3px 0;
|
||||
font-size: 13px;
|
||||
color: #475569;
|
||||
}
|
||||
}
|
||||
|
||||
.q-answer {
|
||||
font-size: 13px;
|
||||
color: #16a34a;
|
||||
font-weight: 600;
|
||||
|
||||
.label {
|
||||
color: #64748b;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -14,17 +14,21 @@
|
||||
:rules="rules" label-width="auto">
|
||||
<el-form-item label="学科" prop="subject">
|
||||
<el-select v-model="libraryStore.currentFile.subject"
|
||||
filterable allow-create placeholder="请输入或选择学科名称">
|
||||
<el-option v-for="(item, index) in subjectOptions"
|
||||
:key="index" :label="item" :value="item"/>
|
||||
filterable allow-create
|
||||
@change="handleSubjectEvent"
|
||||
placeholder="请输入或选择学科名称">
|
||||
<el-option v-for="(item, index) in libraryStore.subjectOptions"
|
||||
:key="index" :label="item.label" :value="item.value"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="模块" prop="module">
|
||||
<el-select v-model="libraryStore.currentFile.module"
|
||||
filterable allow-create placeholder="请输入或选择模块名称">
|
||||
<el-option v-for="(item, index) in subjectOptions"
|
||||
:key="index" :label="item" :value="item"/>
|
||||
filterable allow-create
|
||||
:disabled="!libraryStore.currentFile.subject"
|
||||
placeholder="请输入或选择模块名称">
|
||||
<el-option v-for="(item, index) in libraryStore.moduleOptions"
|
||||
:key="index" :label="item.label" :value="item.value"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
@@ -50,14 +54,16 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<QuillEditor v-if="activeStep === 2" :content="fileContent" contentType="html" theme="snow"/>
|
||||
<QuillEditor v-if="activeStep === 2"
|
||||
:content="libraryStore.currentFile.content"
|
||||
contentType="html" theme="snow"/>
|
||||
|
||||
<div v-if="activeStep === 3" class="generate-question">
|
||||
<div>
|
||||
<el-button type="primary" @click="generate">生成题库</el-button>
|
||||
<el-button type="primary" @click="generateQuestion">生成题库</el-button>
|
||||
</div>
|
||||
|
||||
<div v-if="hasGenerate" class="question-list">
|
||||
<div v-if="libraryStore.hasGenerateQuestion" class="question-list">
|
||||
<el-card v-for="(q, index) in libraryStore.currentFile.questions"
|
||||
:key="q.id" class="question-card">
|
||||
<div class="question-title">
|
||||
@@ -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<FormInstance>()
|
||||
const activeStep = ref(1)
|
||||
const hasGenerate = ref(false)
|
||||
|
||||
const subjectOptions = ['中医学', '公务员']
|
||||
|
||||
const fileContent = ref<string>()
|
||||
const questionContent = ref<string>()
|
||||
|
||||
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('请先生成题库')
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<template>
|
||||
<div class="file-list">
|
||||
<div v-for="file in libraryStore.fileList" :key="file.id" class="file-card card-item">
|
||||
<div class="file-info" @click="viewDetailClick(file)">
|
||||
<library-filter v-if="libraryStore.fileList.length"/>
|
||||
|
||||
<div class="file-icon" :class="file.iconClass">
|
||||
<div v-for="file in libraryStore.fileList" :key="file.id" class="file-card card-item">
|
||||
<div class="file-info">
|
||||
|
||||
<div class="file-icon" :class="file.type === 'image' ? 'icon-image' : 'icon-pdf'">
|
||||
<el-icon v-if="file.type === 'image'" ><Picture /></el-icon>
|
||||
<el-icon v-else><Document /></el-icon>
|
||||
</div>
|
||||
@@ -14,12 +16,12 @@
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
<el-button type="primary" :icon="Edit">编辑</el-button>
|
||||
<el-button type="primary" :icon="Edit" @click="editClick(file)">编辑</el-button>
|
||||
<el-button type="danger" :icon="Delete">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-empty v-if="!libraryStore.fileList.length" description="该模块暂无文件" :image-size="80" />
|
||||
<el-empty v-if="!libraryStore.fileList.length" description="暂无文件" :image-size="80" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -2,30 +2,24 @@
|
||||
<div class="filter-bar card-item">
|
||||
<div class="filter-row">
|
||||
<span class="filter-label">学科</span>
|
||||
<el-segmented v-model="libraryStore.selectedSubject" :options="subjectOptions"/>
|
||||
<el-segmented v-model="libraryStore.selectedSubject"
|
||||
:options="libraryStore.subjectOptions"
|
||||
@change="handleSubjectEvent"/>
|
||||
</div>
|
||||
<div class="filter-row">
|
||||
<span class="filter-label">模块</span>
|
||||
<el-segmented v-model="libraryStore.selectedModule" :options="moduleOptions"/>
|
||||
<el-segmented v-model="libraryStore.selectedModule" :options="libraryStore.moduleOptions"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useLibraryStore } from '@/stores/library.ts'
|
||||
import { getModuleOptions } from '@/utils/library.ts'
|
||||
|
||||
const libraryStore = useLibraryStore()
|
||||
|
||||
const subjectOptions = computed(() => {
|
||||
return libraryStore.subjectTree.map((s) => ({ label: s.name, value: s.id }))
|
||||
})
|
||||
|
||||
const moduleOptions = computed(() => {
|
||||
const node = libraryStore.subjectTree.find((s) => s.id === libraryStore.selectedSubject)
|
||||
return (node?.children || []).map((m) => ({
|
||||
label: m.name,
|
||||
value: m.id
|
||||
}))
|
||||
})
|
||||
const handleSubjectEvent = (value: string): void => {
|
||||
libraryStore.moduleOptions = getModuleOptions(libraryStore.subjectTree, value)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -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: []
|
||||
}
|
||||
}
|
||||
|
||||
12
src/types/index.ts
Normal file
12
src/types/index.ts
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
91
src/utils/axiosUtil.ts
Normal file
91
src/utils/axiosUtil.ts
Normal file
@@ -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
|
||||
51
src/utils/library.ts
Normal file
51
src/utils/library.ts
Normal file
@@ -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<number, ITreeNode>()
|
||||
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
|
||||
}))
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<div class="library-view">
|
||||
<!-- 标题行 -->
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h2 class="page-title">知识库</h2>
|
||||
@@ -9,33 +8,29 @@
|
||||
<el-button type="primary" :icon="Plus" @click="addFileClick">新增资料</el-button>
|
||||
</div>
|
||||
|
||||
<library-filter />
|
||||
|
||||
<library-file-list />
|
||||
|
||||
<library-file-dialog />
|
||||
|
||||
<library-file-detail-dialog />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import LibraryFilter from '@/components/Library/LibraryFilter.vue'
|
||||
import LibraryFileList from '@/components/Library/LibraryFileList.vue'
|
||||
import LibraryFileDialog from '@/components/Library/LibraryFileDialog.vue'
|
||||
import LibraryFileDetailDialog from '@/components/Library/LibraryFileDetailDialog.vue'
|
||||
import { useLibraryStore } from '@/stores/library.ts'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
const libraryStore = useLibraryStore()
|
||||
|
||||
onMounted(() => {
|
||||
libraryStore.queryFileList()
|
||||
onMounted(async () => {
|
||||
await libraryStore.queryFileList()
|
||||
})
|
||||
|
||||
const addFileClick = () => {
|
||||
libraryStore.currentFile = libraryStore.getEmptyFile()
|
||||
libraryStore.fileApiType = 'ADD'
|
||||
libraryStore.hasGenerateQuestion = false
|
||||
libraryStore.fileDialogVisible = true
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -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/, '')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user