feat: 课程章节部分接口对接(编辑,查询,删除章节);阅卷中心接口对接: 获取阅卷列表, 获取班级列表;试卷管理接口对接: 获取试卷列表

This commit is contained in:
QDKF 2025-09-12 19:50:04 +08:00
commit e645a190dd
11 changed files with 1160 additions and 241 deletions

View File

@ -93,13 +93,8 @@ export const searchCoursesExample = async () => {
try { try {
const response = await CourseApi.searchCourses({ const response = await CourseApi.searchCourses({
keyword: 'Vue.js', keyword: 'Vue.js',
category: '前端开发', limit: '20',
level: 'intermediate', page: 1
price: 'paid',
rating: 4,
sortBy: 'newest',
page: 1,
pageSize: 10
}) })
if (response.code === 200) { if (response.code === 200) {

View File

@ -23,7 +23,7 @@ import type {
CourseComment, CourseComment,
Quiz, Quiz,
LearningProgress, LearningProgress,
SearchRequest,
Instructor, Instructor,
} from '../types' } from '../types'

View File

@ -333,6 +333,422 @@ export class ExamApi {
console.log('✅ 批量添加题目答案成功:', responses) console.log('✅ 批量添加题目答案成功:', responses)
return responses return responses
} }
// ========== 试卷管理相关接口 ==========
/**
*
*/
static async getExamPaperList(params: {
page?: number
pageSize?: number
keyword?: string
category?: string
status?: string
difficulty?: string
creator?: string
} = {}): Promise<ApiResponse<{
result: {
records: any[]
total: number
current: number
size: number
}
}>> {
console.log('🚀 获取试卷列表:', params)
const response = await ApiRequest.get<{
result: {
records: any[]
total: number
current: number
size: number
}
}>('/aiol/aiolPaper/list', { params })
console.log('✅ 获取试卷列表成功:', response)
return response
}
/**
*
*/
static async getExamPaperDetail(id: string): Promise<ApiResponse<any>> {
console.log('🚀 获取试卷详情:', id)
const response = await ApiRequest.get<any>(`/aiol/aiolExam/paperDetail/${id}`)
console.log('✅ 获取试卷详情成功:', response)
return response
}
/**
*
*/
static async createExamPaper(data: {
name: string
category: string
description?: string
totalScore: number
difficulty: string
duration: number
questions: any[]
}): Promise<ApiResponse<string>> {
console.log('🚀 创建试卷:', data)
const response = await ApiRequest.post<string>('/aiol/aiolPaper/add', data)
console.log('✅ 创建试卷成功:', response)
return response
}
/**
*
*/
static async updateExamPaper(id: string, data: {
name?: string
category?: string
description?: string
totalScore?: number
difficulty?: string
duration?: number
questions?: any[]
}): Promise<ApiResponse<string>> {
console.log('🚀 更新试卷:', { id, data })
const response = await ApiRequest.put<string>(`/aiol/aiolExam/paperUpdate/${id}`, data)
console.log('✅ 更新试卷成功:', response)
return response
}
/**
*
*/
static async deleteExamPaper(id: string): Promise<ApiResponse<string>> {
console.log('🚀 删除试卷:', id)
const response = await ApiRequest.delete<string>(`/aiol/aiolExam/paperDelete/${id}`)
console.log('✅ 删除试卷成功:', response)
return response
}
/**
*
*/
static async batchDeleteExamPapers(ids: string[]): Promise<ApiResponse<string>> {
console.log('🚀 批量删除试卷:', ids)
const response = await ApiRequest.post<string>('/aiol/aiolExam/paperBatchDelete', { ids })
console.log('✅ 批量删除试卷成功:', response)
return response
}
/**
*
*/
static async publishExamPaper(id: string, data: {
startTime: string
endTime: string
classIds?: string[]
}): Promise<ApiResponse<string>> {
console.log('🚀 发布试卷:', { id, data })
const response = await ApiRequest.post<string>(`/aiol/aiolExam/paperPublish/${id}`, data)
console.log('✅ 发布试卷成功:', response)
return response
}
/**
*
*/
static async unpublishExamPaper(id: string): Promise<ApiResponse<string>> {
console.log('🚀 取消发布试卷:', id)
const response = await ApiRequest.post<string>(`/aiol/aiolExam/paperUnpublish/${id}`)
console.log('✅ 取消发布试卷成功:', response)
return response
}
/**
*
*/
static async endExamPaper(id: string): Promise<ApiResponse<string>> {
console.log('🚀 结束试卷:', id)
const response = await ApiRequest.post<string>(`/aiol/aiolExam/paperEnd/${id}`)
console.log('✅ 结束试卷成功:', response)
return response
}
/**
*
*/
static async importExamPaper(file: File): Promise<ApiResponse<string>> {
console.log('🚀 导入试卷:', file.name)
const formData = new FormData()
formData.append('file', file)
const response = await ApiRequest.post<string>('/aiol/aiolExam/paperImport', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
console.log('✅ 导入试卷成功:', response)
return response
}
/**
*
*/
static async exportExamPaper(id: string): Promise<ApiResponse<Blob>> {
console.log('🚀 导出试卷:', id)
const response = await ApiRequest.get<Blob>(`/aiol/aiolExam/paperExport/${id}`, {
responseType: 'blob'
})
console.log('✅ 导出试卷成功:', response)
return response
}
/**
*
*/
static async batchExportExamPapers(ids: string[]): Promise<ApiResponse<Blob>> {
console.log('🚀 批量导出试卷:', ids)
const response = await ApiRequest.post<Blob>('/aiol/aiolExam/paperBatchExport', { ids }, {
responseType: 'blob'
})
console.log('✅ 批量导出试卷成功:', response)
return response
}
/**
*
*/
static async getExamPaperAnalysis(id: string): Promise<ApiResponse<{
totalStudents: number
submittedCount: number
averageScore: number
passRate: number
scoreDistribution: Array<{ score: number; count: number }>
questionAnalysis: Array<{
questionId: string
correctRate: number
averageScore: number
}>
}>> {
console.log('🚀 获取试卷分析:', id)
const response = await ApiRequest.get<{
totalStudents: number
submittedCount: number
averageScore: number
passRate: number
scoreDistribution: Array<{ score: number; count: number }>
questionAnalysis: Array<{
questionId: string
correctRate: number
averageScore: number
}>
}>(`/aiol/aiolExam/paperAnalysis/${id}`)
console.log('✅ 获取试卷分析成功:', response)
return response
}
// ========== 阅卷中心相关接口 ==========
/**
*
*/
static async getMarkingList(params: {
page?: number
pageSize?: number
status?: 'all' | 'not-started' | 'in-progress' | 'completed'
examType?: string
className?: string
keyword?: string
} = {}): Promise<ApiResponseWithResult<{
list: any[]
total: number
page: number
pageSize: number
}>> {
console.log('🚀 获取阅卷列表:', params)
const response = await ApiRequest.get<{
result: {
list: any[]
total: number
page: number
pageSize: number
}
}>('/aiol/aiolExam/list', { params })
console.log('✅ 获取阅卷列表成功:', response)
return response
}
/**
*
*/
static async getExamStats(examId: string): Promise<ApiResponse<{
totalStudents: number
submittedCount: number
gradedCount: number
averageScore: number
passRate: number
}>> {
console.log('🚀 获取考试统计:', { examId })
const response = await ApiRequest.get<{
totalStudents: number
submittedCount: number
gradedCount: number
averageScore: number
passRate: number
}>(`/aiol/aiolExam/stats/${examId}`)
console.log('✅ 获取考试统计成功:', response)
return response
}
/**
*
*/
static async getStudentAnswers(examId: string, params: {
page?: number
pageSize?: number
status?: 'all' | 'submitted' | 'not-submitted'
className?: string
keyword?: string
} = {}): Promise<ApiResponseWithResult<{
list: any[]
total: number
page: number
pageSize: number
}>> {
console.log('🚀 获取学生答题列表:', { examId, params })
const response = await ApiRequest.get<{
result: {
list: any[]
total: number
page: number
pageSize: number
}
}>(`/aiol/aiolExam/students/${examId}`, { params })
console.log('✅ 获取学生答题列表成功:', response)
return response
}
/**
*
*/
static async getStudentAnswerDetail(examId: string, studentId: string): Promise<ApiResponse<{
studentInfo: {
id: string
name: string
studentId: string
className: string
avatar?: string
examDuration: string
submitTime: string
}
questions: Array<{
id: string
number: number
type: string
content: string
score: number
options?: Array<{ key: string; text: string }>
correctAnswer: string[]
correctAnswerText?: string
explanation?: string
studentAnswer?: string[]
studentTextAnswer?: string
isCorrect?: boolean | null
studentScore?: number
}>
gradingComments?: string
}>> {
console.log('🚀 获取学生答题详情:', { examId, studentId })
const response = await ApiRequest.get<{
studentInfo: any
questions: any[]
gradingComments?: string
}>(`/aiol/aiolExam/student/${examId}/${studentId}/answer`)
console.log('✅ 获取学生答题详情成功:', response)
return response
}
/**
*
*/
static async gradeExam(examId: string, studentId: string, data: {
questions: Array<{
id: string
isCorrect: boolean | null
studentScore: number
}>
gradingComments?: string
totalScore: number
}): Promise<ApiResponse<string>> {
console.log('🚀 批阅试卷:', { examId, studentId, data })
const response = await ApiRequest.post<string>(`/aiol/aiolExam/grade/${examId}/${studentId}`, data)
console.log('✅ 批阅试卷成功:', response)
return response
}
/**
*
*/
static async getClassList(): Promise<ApiResponse<Array<{ id: string; name: string }>>> {
console.log('🚀 获取班级列表')
try {
// 尝试多个可能的接口路径
const possiblePaths = [
'/aiol/aiolClass/list',
'/aiol/aiolClass/queryList',
'/aiol/aiolClass/page',
'/aiol/aiolClass/queryPage',
'/aiol/aiolExam/classes',
'/aiol/aiolExam/classList'
]
for (const path of possiblePaths) {
try {
console.log(`尝试接口路径: ${path}`)
const response = await ApiRequest.get<Array<{ id: string; name: string }>>(path)
console.log(`✅ 获取班级列表成功 (${path}):`, response)
return response
} catch (pathError) {
console.warn(`接口 ${path} 不存在:`, pathError)
continue
}
}
// 如果所有接口都不存在,返回空数组
console.warn('所有班级列表接口都不存在,返回空数组')
return {
code: 200,
message: 'success',
data: []
}
} catch (error) {
console.error('获取班级列表失败:', error)
throw error
}
}
/**
*
*/
static async exportExamResults(examId: string, params: {
className?: string
status?: string
} = {}): Promise<Blob> {
console.log('🚀 导出考试结果:', { examId, params })
const response = await ApiRequest.get<Blob>(`/aiol/aiolExam/export/${examId}`, {
params,
responseType: 'blob'
})
console.log('✅ 导出考试结果成功:', response)
return response.data
}
/**
*
*/
static async publishRetakeExam(examId: string, data: {
studentIds: string[]
retakeTime: string
retakeDuration: number
}): Promise<ApiResponse<string>> {
console.log('🚀 发布补考:', { examId, data })
const response = await ApiRequest.post<string>(`/aiol/aiolExam/retake/${examId}`, data)
console.log('✅ 发布补考成功:', response)
return response
}
} }
export default ExamApi export default ExamApi

View File

@ -416,11 +416,11 @@ const handleOfflineCourse = (course: CourseDisplayItem) => {
name: course.name, name: course.name,
description: course.description, description: course.description,
status: 2, // 2= status: 2, // 2=
pause_exit: '0', // pause_exit: '1',
allow_speed: '0', // allow_speed: '1',
show_subtitle: '0' // show_subtitle: '1'
}; };
await TeachCourseApi.editCourse(updatedData); await TeachCourseApi.editCourse(updatedData);
// //

View File

@ -188,6 +188,7 @@ import {
import '@wangeditor/editor/dist/css/style.css' import '@wangeditor/editor/dist/css/style.css'
// @ts-ignore // @ts-ignore
import { Editor, Toolbar } from '@wangeditor/editor-for-vue' import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
// import TeachCourseApi from '@/api/modules/teachCourse'
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()

View File

@ -289,6 +289,7 @@ import TrueFalseQuestion from '@/components/teacher/TrueFalseQuestion.vue';
import FillBlankQuestion from '@/components/teacher/FillBlankQuestion.vue'; import FillBlankQuestion from '@/components/teacher/FillBlankQuestion.vue';
import ShortAnswerQuestion from '@/components/teacher/ShortAnswerQuestion.vue'; import ShortAnswerQuestion from '@/components/teacher/ShortAnswerQuestion.vue';
import CompositeQuestion from '@/components/teacher/CompositeQuestion.vue'; import CompositeQuestion from '@/components/teacher/CompositeQuestion.vue';
import { ExamApi } from '@/api/modules/exam';
// dialog API // dialog API
const { dialog } = createDiscreteApi(['dialog']) const { dialog } = createDiscreteApi(['dialog'])
@ -998,7 +999,7 @@ const getQuestionTypeFromString = (typeString: string) => {
}; };
// //
const saveExam = () => { const saveExam = async () => {
// //
if (!examForm.title.trim()) { if (!examForm.title.trim()) {
dialog.warning({ dialog.warning({
@ -1035,13 +1036,68 @@ const saveExam = () => {
return; return;
} }
// try {
console.log('保存试卷数据:', examForm); // API
dialog.success({ const apiData = {
title: '保存成功', name: examForm.title,
content: '试卷保存成功!', category: examForm.type === 1 ? '考试' : '练习',
positiveText: '确定' description: examForm.description || '',
}); totalScore: examForm.totalScore,
difficulty: getDifficultyLevel(examForm.totalScore),
duration: examForm.duration,
questions: formatQuestionsForAPI(examForm.questions)
};
console.log('🚀 准备保存试卷数据:', apiData);
// API
const response = await ExamApi.createExamPaper(apiData);
console.log('✅ 创建试卷成功:', response);
dialog.success({
title: '保存成功',
content: '试卷保存成功!',
positiveText: '确定',
onPositiveClick: () => {
//
router.back();
}
});
} catch (error) {
console.error('创建试卷失败:', error);
dialog.error({
title: '保存失败',
content: '试卷保存失败,请重试',
positiveText: '确定'
});
}
}
//
const getDifficultyLevel = (totalScore: number): string => {
if (totalScore <= 60) return 'easy';
if (totalScore <= 100) return 'medium';
return 'hard';
}
// API
const formatQuestionsForAPI = (questions: any[]): any[] => {
return questions.map((bigQuestion, index) => ({
id: bigQuestion.id,
title: bigQuestion.title,
description: bigQuestion.description,
sort: index + 1,
totalScore: bigQuestion.totalScore,
subQuestions: bigQuestion.subQuestions.map((subQuestion: any, subIndex: number) => ({
id: subQuestion.id,
title: subQuestion.title,
type: subQuestion.type,
options: subQuestion.options || [],
correctAnswer: subQuestion.correctAnswer,
score: subQuestion.score,
sort: subIndex + 1
}))
}));
} }
// //

View File

@ -7,28 +7,29 @@
<n-button ghost>导入</n-button> <n-button ghost>导入</n-button>
<n-button ghost>导出</n-button> <n-button ghost>导出</n-button>
<n-button type="error" ghost>删除</n-button> <n-button type="error" ghost>删除</n-button>
<n-input placeholder="请输入想要搜索的内容" /> <n-input v-model:value="searchKeyword" placeholder="请输入想要搜索的内容" @keyup.enter="handleSearch" />
<n-button type="primary">搜索</n-button> <n-button type="primary" @click="handleSearch">搜索</n-button>
</n-space> </n-space>
</div> </div>
<n-data-table :columns="columns" :data="examData" :row-key="(row: Exam) => row.id" <n-data-table :columns="columns" :data="examData" :loading="loading" :row-key="(row: Exam) => row.id"
@update:checked-row-keys="handleCheck" class="exam-table" :single-line="false" @update:checked-row-keys="handleCheck" class="exam-table" :single-line="false"
:pagination="paginationConfig" /> :pagination="paginationConfig" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { h, ref, VNode, computed } from 'vue'; import { h, ref, VNode, computed, onMounted } from 'vue';
import { NButton, NSpace, useMessage, NDataTable, NInput } from 'naive-ui'; import { NButton, NSpace, useMessage, NDataTable, NInput } from 'naive-ui';
import type { DataTableColumns } from 'naive-ui'; import type { DataTableColumns } from 'naive-ui';
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
import { ExamApi } from '@/api/modules/exam';
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
// //
type Exam = { type Exam = {
id: number; id: string;
name: string; name: string;
category: '练习' | '考试'; category: '练习' | '考试';
questionCount: number; questionCount: number;
@ -44,6 +45,17 @@ type Exam = {
const message = useMessage(); const message = useMessage();
//
const loading = ref(false);
const examData = ref<Exam[]>([]);
const searchKeyword = ref('');
const filters = ref({
category: '',
status: '',
difficulty: '',
creator: ''
});
// //
const createColumns = ({ const createColumns = ({
handleAction, handleAction,
@ -133,20 +145,119 @@ const createColumns = ({
]; ];
}; };
// //
const examData = ref<Exam[]>([ const loadExamPaperList = async () => {
{ id: 1, name: '试卷名称试卷名称', category: '练习', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '发布中', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' }, loading.value = true;
{ id: 2, name: '试卷名称试卷名称', category: '考试', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '未发布', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' }, try {
{ id: 3, name: '试卷名称试卷名称', category: '练习', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '发布中', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' }, const params: any = {
{ id: 4, name: '试卷名称试卷名称', category: '考试', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '发布中', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' }, page: currentPage.value,
{ id: 5, name: '试卷名称试卷名称', category: '练习', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '发布中', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' }, pageSize: pageSize.value
{ id: 6, name: '试卷名称试卷名称', category: '考试', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '未发布', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' }, };
{ id: 7, name: '试卷名称试卷名称', category: '练习', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '发布中', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' },
{ id: 8, name: '试卷名称试卷名称', category: '考试', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '未发布', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' },{ id: 7, name: '试卷名称试卷名称', category: '练习', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '发布中', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' }, if (searchKeyword.value) {
{ id: 9, name: '试卷名称试卷名称', category: '考试', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '未发布', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' }, params.keyword = searchKeyword.value;
{ id: 10, name: '试卷名称试卷名称', category: '练习', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '发布中', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' }, }
{ id: 11, name: '试卷名称试卷名称', category: '考试', questionCount: 100, chapter: '第一节 开课前准备', totalScore: 150, difficulty: '易', status: '未发布', startTime: '2025.07.25 09:20', endTime: '2025.07.25 09:20', creator: '王建国', creationTime: '2025.07.25 9:20' }, if (filters.value.category) {
]); params.category = filters.value.category;
}
if (filters.value.status) {
params.status = filters.value.status;
}
if (filters.value.difficulty) {
params.difficulty = filters.value.difficulty;
}
if (filters.value.creator) {
params.creator = filters.value.creator;
}
console.log('🔍 获取试卷列表参数:', params);
const response = await ExamApi.getExamPaperList(params);
console.log('✅ 获取试卷列表成功:', response);
let listData: any[] = [];
let totalCount = 0;
if (response.data) {
const data = response.data as any;
if (data.result) {
// API result.records
listData = data.result.records || [];
totalCount = data.result.total || 0;
} else if (Array.isArray(data)) {
listData = data;
totalCount = data.length;
} else if (data.list) {
listData = data.list;
totalCount = data.total || data.totalCount || 0;
} else if (data.records) {
listData = data.records;
totalCount = data.total || data.totalCount || 0;
}
}
if (!Array.isArray(listData)) {
console.warn('API返回的数据不是数组格式:', listData);
listData = [];
}
//
const mappedList = listData.map((item: any) => {
const statusMap: { [key: number]: string } = {
0: '未发布',
1: '发布中',
2: '已结束'
};
const categoryMap: { [key: number]: string } = {
0: '练习',
1: '考试'
};
const difficultyMap: { [key: number]: string } = {
0: '易',
1: '中',
2: '难'
};
//
const formatTime = (startTime: string, endTime: string) => {
if (startTime && endTime) {
return `${startTime} - ${endTime}`;
} else if (startTime) {
return startTime;
} else if (endTime) {
return endTime;
}
return '-';
};
return {
id: item.id || item.paperId || '',
name: item.name || '未命名试卷',
category: (categoryMap[item.type] || '练习') as '练习' | '考试',
questionCount: 0, // 0
chapter: '未分类', //
totalScore: 0, // 0
difficulty: (difficultyMap[item.difficulty] || '易') as '易' | '中' | '难',
status: (statusMap[item.status] || '未发布') as '发布中' | '未发布' | '已结束',
startTime: formatTime(item.startTime, item.endTime),
endTime: item.endTime || '',
creator: item.createBy || '未知',
creationTime: item.createTime || ''
};
});
examData.value = mappedList;
totalItems.value = totalCount;
} catch (error) {
console.error('加载试卷列表失败:', error);
message.error('加载试卷列表失败');
examData.value = [];
totalItems.value = 0;
} finally {
loading.value = false;
}
};
const columns = createColumns({ const columns = createColumns({
handleAction: (action, row) => { handleAction: (action, row) => {
@ -195,7 +306,7 @@ const handleCheck = (rowKeys: Array<string | number>) => {
// //
const currentPage = ref(1); const currentPage = ref(1);
const pageSize = ref(10); const pageSize = ref(10);
const totalItems = ref(examData.value.length); const totalItems = ref(0);
// //
const paginationConfig = computed(() => ({ const paginationConfig = computed(() => ({
@ -211,13 +322,22 @@ const paginationConfig = computed(() => ({
}, },
onUpdatePage: (page: number) => { onUpdatePage: (page: number) => {
currentPage.value = page; currentPage.value = page;
loadExamPaperList();
}, },
onUpdatePageSize: (newPageSize: number) => { onUpdatePageSize: (newPageSize: number) => {
pageSize.value = newPageSize; pageSize.value = newPageSize;
currentPage.value = 1; currentPage.value = 1;
loadExamPaperList();
} }
})); }));
//
const handleSearch = () => {
currentPage.value = 1;
loadExamPaperList();
};
const handleAddExam = () => { const handleAddExam = () => {
// //
const currentRoute = route.path; const currentRoute = route.path;
@ -231,6 +351,11 @@ const handleAddExam = () => {
} }
}; };
//
onMounted(() => {
loadExamPaperList();
});
</script> </script>
<style scoped> <style scoped>

View File

@ -2,12 +2,7 @@
<div class="marking-center"> <div class="marking-center">
<!-- Tab切换 --> <!-- Tab切换 -->
<div class="tab-container"> <div class="tab-container">
<n-tabs <n-tabs v-model:value="activeTab" type="line" animated @update:value="handleTabChange">
v-model:value="activeTab"
type="line"
animated
@update:value="handleTabChange"
>
<n-tab-pane name="all" tab="全部"> <n-tab-pane name="all" tab="全部">
</n-tab-pane> </n-tab-pane>
<n-tab-pane name="not-started" tab="未开始"> <n-tab-pane name="not-started" tab="未开始">
@ -21,122 +16,100 @@
<!-- 筛选栏 --> <!-- 筛选栏 -->
<div class="filter-container"> <div class="filter-container">
<div class="filter-left">
<n-input v-model:value="searchKeyword" placeholder="搜索考试名称" clearable style="width: 200px; margin-right: 16px;"
@keyup.enter="handleSearch">
<template #prefix>
<n-icon :component="SearchOutline" />
</template>
</n-input>
<n-button type="primary" @click="handleSearch">搜索</n-button>
</div>
<div class="filter-right"> <div class="filter-right">
<n-select <n-select v-model:value="examFilter" :options="examFilterOptions" placeholder="考试类型"
v-model:value="examFilter" style="width: 120px; margin-right: 16px;" clearable />
:options="examFilterOptions" <n-select v-model:value="gradeFilter" :options="gradeFilterOptions" placeholder="班级名称" style="width: 120px;"
placeholder="考试" clearable />
style="width: 120px; margin-right: 16px;"
/>
<n-select
v-model:value="gradeFilter"
:options="gradeFilterOptions"
placeholder="班级名称"
style="width: 120px;"
/>
</div> </div>
</div> </div>
<!-- 试卷列表 --> <!-- 试卷列表 -->
<div class="exam-list"> <div class="exam-list">
<div <n-spin :show="loading">
v-for="exam in filteredExams" <div v-for="exam in filteredExams" :key="exam.id" class="exam-item"
:key="exam.id" :class="{ 'completed': exam.status === 'completed' }">
class="exam-item" <div class="exam-content">
:class="{ 'completed': exam.status === 'completed' }" <div class="exam-header">
> <n-tag :type="getStatusType(exam.status)" :bordered="false" size="small" class="status-tag">
<div class="exam-content"> {{ getStatusText(exam.status) }}
<div class="exam-header"> </n-tag>
<n-tag <span class="exam-title">{{ exam.title }}</span>
:type="getStatusType(exam.status)"
:bordered="false"
size="small"
class="status-tag"
>
{{ getStatusText(exam.status) }}
</n-tag>
<span class="exam-title">{{ exam.title }}</span>
</div>
<div class="exam-description">
{{ exam.description }}
</div>
<div class="exam-meta">
<div class="meta-item">
<n-icon :component="PersonOutline" />
发布人
<span>{{ exam.creator }}</span>
</div> </div>
<div class="meta-item">
<n-icon :component="CalendarOutline" /> <div class="exam-description">
<span>{{ exam.duration }}</span> {{ exam.description }}
</div>
<div class="exam-meta">
<div class="meta-item">
<n-icon :component="PersonOutline" />
发布人
<span>{{ exam.creator }}</span>
</div>
<div class="meta-item">
<n-icon :component="CalendarOutline" />
<span>{{ exam.duration }}</span>
</div>
</div>
<div class="exam-actions">
<n-button text type="primary" @click="handleViewDetails(exam)">
试卷设置
</n-button>
<n-button text type="primary" @click="handleDelete(exam)">
删除
</n-button>
</div> </div>
</div> </div>
<div class="exam-actions"> <div class="exam-stats">
<n-button <div class="stats-item">
text <div class="stats-number">{{ exam.totalQuestions }}</div>
type="primary" <div class="stats-label">试题</div>
@click="handleViewDetails(exam)" </div>
> <div class="stats-item">
试卷设置 <div class="stats-number">{{ exam.submittedCount }}</div>
</n-button> <div class="stats-label">已交</div>
<n-button </div>
text <div class="stats-item">
type="primary" <div class="stats-number">{{ exam.submittedCount - exam.gradedCount }}</div>
@click="handleDelete(exam)" <div class="stats-label">未批</div>
> </div>
删除 </div>
<div class="exam-action-button">
<n-button :type="exam.status === 'completed' ? 'default' : 'primary'" @click="handleAction(exam)">
{{ exam.status === 'completed' ? '查看' : (exam.status === 'in-progress' ? '批阅' : '查看') }}
</n-button> </n-button>
</div> </div>
</div> </div>
</n-spin>
<div class="exam-stats">
<div class="stats-item">
<div class="stats-number">{{ exam.totalQuestions }}</div>
<div class="stats-label">试题</div>
</div>
<div class="stats-item">
<div class="stats-number">{{ exam.submittedCount }}</div>
<div class="stats-label">已交</div>
</div>
<div class="stats-item">
<div class="stats-number">{{ exam.gradedCount }}</div>
<div class="stats-label">{{ exam.status === 'in-progress' ? '0未交' : '0未交' }}</div>
</div>
</div>
<div class="exam-action-button">
<n-button
:type="exam.status === 'completed' ? 'default' : 'primary'"
@click="handleAction(exam)"
>
{{ exam.status === 'completed' ? '查看' : (exam.status === 'in-progress' ? '批阅' : '查看') }}
</n-button>
</div>
</div>
</div> </div>
<!-- 分页 --> <!-- 分页 -->
<div class="pagination-container"> <div class="pagination-container">
<n-pagination <n-pagination v-model:page="currentPage" :page-size="pageSize" show-size-picker :page-sizes="[10, 20, 50]"
v-model:page="currentPage" :item-count="totalItems" @update:page="handlePageChange" @update:page-size="handlePageSizeChange" />
:page-size="pageSize"
show-size-picker
:page-sizes="[10, 20, 50]"
:item-count="totalItems"
@update:page="handlePageChange"
@update:page-size="handlePageSizeChange"
/>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import { PersonOutline, CalendarOutline } from '@vicons/ionicons5' import { PersonOutline, CalendarOutline, SearchOutline } from '@vicons/ionicons5'
import { useMessage } from 'naive-ui'
import { ExamApi } from '@/api/modules/exam'
// //
interface ExamItem { interface ExamItem {
@ -149,18 +122,24 @@ interface ExamItem {
totalQuestions: number totalQuestions: number
submittedCount: number submittedCount: number
gradedCount: number gradedCount: number
examType?: string
paperId?: string
} }
// //
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const message = useMessage()
// //
const loading = ref(false)
const activeTab = ref('all') const activeTab = ref('all')
const examFilter = ref('') const examFilter = ref('')
const gradeFilter = ref('') const gradeFilter = ref('')
const searchKeyword = ref('')
const currentPage = ref(1) const currentPage = ref(1)
const pageSize = ref(10) const pageSize = ref(10)
const totalItems = ref(0)
// //
const examFilterOptions = [ const examFilterOptions = [
@ -170,78 +149,231 @@ const examFilterOptions = [
{ label: '月考', value: 'monthly' } { label: '月考', value: 'monthly' }
] ]
const gradeFilterOptions = [ const gradeFilterOptions = ref([
{ label: '全部班级', value: '' }, { label: '全部班级', value: '' }
{ label: '一年级1班', value: 'grade1-1' },
{ label: '一年级2班', value: 'grade1-2' },
{ label: '二年级1班', value: 'grade2-1' }
]
//
const examList = ref<ExamItem[]>([
{
id: '1',
title: '试卷名称试卷名称试卷名称试卷名称试卷名称',
description: '试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明...',
creator: 'xx',
duration: '考试时间2025.6.18-2025.9.18',
status: 'not-started',
totalQuestions: 10,
submittedCount: 0,
gradedCount: 0
},
{
id: '2',
title: '试卷名称试卷名称试卷名称试卷名称试卷名称',
description: '试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明...',
creator: 'xx',
duration: '考试时间2025.6.18-2025.9.18',
status: 'in-progress',
totalQuestions: 0,
submittedCount: 0,
gradedCount: 0
},
{
id: '3',
title: '试卷名称试卷名称试卷名称试卷名称试卷名称',
description: '试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明试卷说明...',
creator: 'xx',
duration: '考试时间2025.6.18-2025.9.18',
status: 'completed',
totalQuestions: 10,
submittedCount: 0,
gradedCount: 0
}
]) ])
//
const examList = ref<ExamItem[]>([])
//
const loadClassList = async () => {
try {
const response = await ExamApi.getClassList()
console.log('班级列表API响应:', response)
//
let classList: any[] = []
if (response.data) {
const data = response.data as any
// result.records
if (data.result && data.result.records) {
classList = data.result.records
}
// result
else if (data.result && Array.isArray(data.result)) {
classList = data.result
}
//
else if (Array.isArray(data)) {
classList = data
}
// records
else if (data.records && Array.isArray(data.records)) {
classList = data.records
}
}
console.log('解析后的班级列表:', classList)
if (Array.isArray(classList)) {
gradeFilterOptions.value = [
{ label: '全部班级', value: '' },
...classList.map((item: any) => ({
label: item.name || '未命名班级',
value: item.id
}))
]
console.log('班级筛选选项:', gradeFilterOptions.value)
} else {
console.warn('班级列表数据格式不正确:', classList)
gradeFilterOptions.value = [{ label: '全部班级', value: '' }]
}
} catch (error) {
console.error('加载班级列表失败:', error)
message.error('加载班级列表失败')
}
}
//
const loadMarkingList = async () => {
loading.value = true
try {
//
const params: any = {
page: currentPage.value,
pageSize: pageSize.value
}
//
if (activeTab.value !== 'all') {
params.status = activeTab.value
}
//
if (examFilter.value) {
params.examType = examFilter.value
}
if (gradeFilter.value) {
params.className = gradeFilter.value
}
if (searchKeyword.value) {
params.keyword = searchKeyword.value
}
console.log('🔍 筛选参数:', params)
console.log('📊 当前筛选状态:', {
activeTab: activeTab.value,
examFilter: examFilter.value,
gradeFilter: gradeFilter.value,
searchKeyword: searchKeyword.value
})
const response = await ExamApi.getMarkingList(params)
console.log('阅卷列表API响应:', response)
//
let listData: any[] = []
let totalCount = 0
if (response.data) {
const data = response.data as any
// result
if (data.result) {
listData = data.result.list || data.result.records || []
totalCount = data.result.total || data.result.totalCount || 0
} else if (Array.isArray(data)) {
//
listData = data
totalCount = data.length
} else if (data.list) {
// list
listData = data.list
totalCount = data.total || data.totalCount || 0
} else if (data.records) {
// records
listData = data.records
totalCount = data.total || data.totalCount || 0
}
}
// listData
if (!Array.isArray(listData)) {
console.warn('API返回的数据不是数组格式:', listData)
listData = []
}
let mappedList = listData.map((item: any) => {
// API
const statusMap: { [key: number]: string } = {
0: 'not-started', //
1: 'in-progress', //
2: 'completed' //
}
const typeMap: { [key: number]: string } = {
0: '期中考试',
1: '期末考试',
2: '月考',
3: '随堂测验'
}
//
const formatDuration = (startTime: string, endTime: string, totalTime: number) => {
if (startTime && endTime) {
const start = new Date(startTime).toLocaleString('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
})
const end = new Date(endTime).toLocaleString('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
})
return `${start} - ${end}`
}
return totalTime ? `${totalTime}分钟` : '未设置'
}
return {
id: item.id,
title: item.name || '未命名考试',
description: item.description || item.remark || '',
creator: item.createBy || '未知',
duration: formatDuration(item.startTime, item.endTime, item.totalTime),
status: (statusMap[item.status] || 'not-started') as 'not-started' | 'in-progress' | 'completed',
totalQuestions: item.totalQuestions || 0,
submittedCount: item.submittedCount || 0,
gradedCount: item.gradedCount || 0,
//
examType: typeMap[item.type] || '未知类型',
paperId: item.paperId
}
})
//
if (activeTab.value !== 'all' || examFilter.value || gradeFilter.value || searchKeyword.value) {
console.log('🔍 后端可能不支持筛选,在前端进行筛选')
mappedList = mappedList.filter(item => {
//
if (activeTab.value !== 'all' && item.status !== activeTab.value) {
return false
}
//
if (examFilter.value && item.examType !== examFilter.value) {
return false
}
//
if (gradeFilter.value) {
//
}
//
if (searchKeyword.value && !item.title.toLowerCase().includes(searchKeyword.value.toLowerCase())) {
return false
}
return true
})
}
examList.value = mappedList
totalItems.value = totalCount
} catch (error) {
console.error('加载阅卷列表失败:', error)
message.error('加载阅卷列表失败')
} finally {
loading.value = false
}
}
// //
const filteredExams = computed(() => { const filteredExams = computed(() => examList.value)
let filtered = examList.value
// tab
if (activeTab.value !== 'all') {
filtered = filtered.filter(exam => exam.status === activeTab.value)
}
//
if (examFilter.value) {
//
}
//
if (gradeFilter.value) {
//
}
return filtered
})
const totalItems = computed(() => filteredExams.value.length)
// //
const handleTabChange = (value: string) => { const handleTabChange = (value: string) => {
activeTab.value = value activeTab.value = value
currentPage.value = 1 currentPage.value = 1
loadMarkingList()
} }
const getStatusType = (status: string) => { const getStatusType = (status: string) => {
@ -287,24 +419,43 @@ const handleAction = (exam: ExamItem) => {
router.push(`/teacher/course-editor/${courseId}/practice/review/student-list/${exam.id}`); router.push(`/teacher/course-editor/${courseId}/practice/review/student-list/${exam.id}`);
} else { } else {
// 使 // 使
router.push({ router.push({
name: 'StudentList', name: 'StudentList',
params: { paperId: exam.id } params: { paperId: exam.id }
}); });
} }
} }
const handlePageChange = (page: number) => { const handlePageChange = (page: number) => {
currentPage.value = page currentPage.value = page
loadMarkingList()
} }
const handlePageSizeChange = (size: number) => { const handlePageSizeChange = (size: number) => {
pageSize.value = size pageSize.value = size
currentPage.value = 1 currentPage.value = 1
loadMarkingList()
} }
onMounted(() => {
//
const handleSearch = () => {
currentPage.value = 1
loadMarkingList()
}
//
watch([activeTab, examFilter, gradeFilter, searchKeyword], () => {
currentPage.value = 1
loadMarkingList()
})
onMounted(async () => {
// //
await Promise.all([
loadClassList(),
loadMarkingList()
])
}) })
</script> </script>
@ -333,13 +484,17 @@ onMounted(() => {
/* 筛选栏样式 */ /* 筛选栏样式 */
.filter-container { .filter-container {
display: flex; display: flex;
justify-content: flex-end; justify-content: space-between;
align-items: center; align-items: center;
background-color: #fff; background-color: #fff;
padding: 16px 20px; padding: 16px 20px;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0;
} }
.filter-left {
display: flex;
align-items: center;
}
.filter-right { .filter-right {
display: flex; display: flex;
align-items: center; align-items: center;
@ -520,4 +675,4 @@ onMounted(() => {
font-size: 13px; font-size: 13px;
} }
} }
</style> </style>

View File

@ -320,20 +320,6 @@ const isMobile = ref(false);
const isTablet = ref(false); const isTablet = ref(false);
const sidebarCollapsed = ref(false); const sidebarCollapsed = ref(false);
// section
interface Section {
id: number;
lessonName: string;
coursewareName: string;
coursewareUploadOption: string;
coursewareFiles: any[];
selectedExamOption: string;
homeworkName: string;
uploadedFiles: any[];
homeworkFiles: any[];
contentTitle: string;
contentDescription: string;
}
// //
interface Chapter { interface Chapter {
@ -665,7 +651,7 @@ const addChapter = async () => {
const response = await ChapterApi.createChapter(chapterData); const response = await ChapterApi.createChapter(chapterData);
console.log('🔍 创建章节API完整响应:', response); console.log('🔍 创建章节API完整响应:', response);
console.log('🔍 响应状态:', response.status); console.log('🔍 响应状态:', response.code);
console.log('🔍 响应数据:', response.data); console.log('🔍 响应数据:', response.data);
console.log('🔍 响应成功标志:', response.data?.success); console.log('🔍 响应成功标志:', response.data?.success);
console.log('🔍 响应消息:', response.data?.message); console.log('🔍 响应消息:', response.data?.message);
@ -764,7 +750,7 @@ const saveChapter = async () => {
const response = await ChapterApi.editChapter(chapterData); const response = await ChapterApi.editChapter(chapterData);
console.log('🔍 编辑章节完整响应:', response); console.log('🔍 编辑章节完整响应:', response);
console.log('🔍 响应状态:', response.status); console.log('🔍 响应状态:', response.code);
console.log('🔍 响应数据:', response.data); console.log('🔍 响应数据:', response.data);
console.log('🔍 响应成功标志:', response.data?.success); console.log('🔍 响应成功标志:', response.data?.success);
console.log('🔍 响应消息:', response.data?.message); console.log('🔍 响应消息:', response.data?.message);
@ -852,7 +838,7 @@ const saveSection = async (section: any) => {
// API // API
const response = await ChapterApi.editChapter(sectionData); const response = await ChapterApi.editChapter(sectionData);
console.log('🔍 编辑节完整响应:', response); console.log('🔍 编辑节完整响应:', response);
console.log('🔍 响应状态:', response.status); console.log('🔍 响应状态:', response.code);
console.log('🔍 响应数据:', response.data); console.log('🔍 响应数据:', response.data);
console.log('🔍 响应成功标志:', response.data?.success); console.log('🔍 响应成功标志:', response.data?.success);
console.log('🔍 响应消息:', response.data?.message); console.log('🔍 响应消息:', response.data?.message);
@ -1046,27 +1032,18 @@ const loadChapters = async () => {
console.log('🔍 完整API响应:', response); console.log('🔍 完整API响应:', response);
console.log('🔍 响应数据:', response.data); console.log('🔍 响应数据:', response.data);
console.log('🔍 响应状态:', response.status); console.log('🔍 响应状态:', response.code);
console.log('🔍 响应数据类型:', typeof response.data); console.log('🔍 响应数据类型:', typeof response.data);
console.log('🔍 响应数据是否为数组:', Array.isArray(response.data)); console.log('🔍 响应数据是否为数组:', Array.isArray(response.data));
// API // API
let chapterData = null; let chapterData = null;
if (response.data && response.data.success && response.data.result) { if (response.data && response.data.list) {
chapterData = response.data.result;
console.log('✅ 从服务器加载的章节数据 (result):', chapterData);
} else if (response.data && response.data.list) {
chapterData = response.data.list; chapterData = response.data.list;
console.log('✅ 从服务器加载的章节数据 (list):', chapterData); console.log('✅ 从服务器加载的章节数据 (list):', chapterData);
} else if (response.data && response.data.data && response.data.data.list) {
chapterData = response.data.data.list;
console.log('✅ 从服务器加载的章节数据 (data.list):', chapterData);
} else if (Array.isArray(response.data)) { } else if (Array.isArray(response.data)) {
chapterData = response.data; chapterData = response.data;
console.log('✅ 从服务器加载的章节数据 (array):', chapterData); console.log('✅ 从服务器加载的章节数据 (array):', chapterData);
} else if (response.data && Array.isArray(response.data)) {
chapterData = response.data;
console.log('✅ 从服务器加载的章节数据 (direct array):', chapterData);
} }
if (chapterData && Array.isArray(chapterData)) { if (chapterData && Array.isArray(chapterData)) {
@ -1083,7 +1060,7 @@ const loadChapters = async () => {
console.log('📖 节数据:', sectionsData); console.log('📖 节数据:', sectionsData);
// //
chaptersData.forEach((chapterData: any, index: number) => { chaptersData.forEach((chapterData: any) => {
const chapter: Chapter = { const chapter: Chapter = {
id: chapterData.id, id: chapterData.id,
name: chapterData.name, name: chapterData.name,

View File

@ -69,7 +69,7 @@ import type { DataTableColumns } from 'naive-ui'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import ImportModal from '@/components/common/ImportModal.vue' import ImportModal from '@/components/common/ImportModal.vue'
import { ChapterApi } from '@/api' import { ChapterApi } from '@/api'
import type { ChapterQueryParams, CourseSection } from '@/api/types' import type { ChapterQueryParams } from '@/api/types'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
const router = useRouter() const router = useRouter()

194
tatus --porcelain Normal file
View File

@ -0,0 +1,194 @@
diff --git a/src/api/examples/usage.ts b/src/api/examples/usage.ts
index 0363f0d..64dbce6 100644
--- a/src/api/examples/usage.ts
+++ b/src/api/examples/usage.ts
@@ -93,13 +93,8 @@ export const searchCoursesExample = async () => {
try {
const response = await CourseApi.searchCourses({
keyword: 'Vue.js',
- category: '前端开发',
- level: 'intermediate',
- price: 'paid',
- rating: 4,
- sortBy: 'newest',
- page: 1,
- pageSize: 10
+ limit: '20',
+ page: 1
})

if (response.code === 200) {
diff --git a/src/api/modules/course.ts b/src/api/modules/course.ts
index 9d3a3e1..e8cdde2 100644
--- a/src/api/modules/course.ts
+++ b/src/api/modules/course.ts
@@ -23,7 +23,7 @@ import type {
CourseComment,
Quiz,
LearningProgress,
- SearchRequest,
+
Instructor,
} from '../types'

diff --git a/src/api/modules/exam.ts b/src/api/modules/exam.ts
index 5a1865f..f7be93a 100644
--- a/src/api/modules/exam.ts
+++ b/src/api/modules/exam.ts
@@ -333,6 +333,420 @@ export class ExamApi {
console.log('✅ 批量添加题目答案成功:', responses)
return responses
}
+
+ // ========== 试卷管理相关接口 ==========
+
+ /**
+ * 获取试卷列表
+ */
+ static async getExamPaperList(params: {
+ page?: number
+ pageSize?: number
+ keyword?: string
+ category?: string
+ status?: string
+ difficulty?: string
+ creator?: string
+ } = {}): Promise<ApiResponseWithResult<{
+ list: any[]
+ total: number
+ page: number
+ pageSize: number
+ }>> {
+ console.log('🚀 获取试卷列表:', params)
+ const response = await ApiRequest.get<{
+ result: {
+ records: any[]
+ total: number
+ current: number
+ size: number
+ }
+ }>('/aiol/aiolPaper/list', { params })
+ console.log('✅ 获取试卷列表成功:', response)
+ return response
+ }
+
+ /**
+ * 获取试卷详情
+ */
+ static async getExamPaperDetail(id: string): Promise<ApiResponse<any>> {
+ console.log('🚀 获取试卷详情:', id)
+ const response = await ApiRequest.get<any>(`/aiol/aiolExam/paperDetail/${id}`)
+ console.log('✅ 获取试卷详情成功:', response)
+ return response
+ }
+
+ /**
+ * 创建试卷
+ */
+ static async createExamPaper(data: {
+ name: string
+ category: string
+ description?: string
+ totalScore: number
+ difficulty: string
+ duration: number
+ questions: any[]
+ }): Promise<ApiResponse<string>> {
+ console.log('🚀 创建试卷:', data)
+ const response = await ApiRequest.post<string>('/aiol/aiolPaper/add', data)
+ console.log('✅ 创建试卷成功:', response)
+ return response
+ }
+
+ /**
+ * 更新试卷
+ */
+ static async updateExamPaper(id: string, data: {
+ name?: string
+ category?: string
+ description?: string
+ totalScore?: number
+ difficulty?: string
+ duration?: number
+ questions?: any[]
+ }): Promise<ApiResponse<string>> {
+ console.log('🚀 更新试卷:', { id, data })
+ const response = await ApiRequest.put<string>(`/aiol/aiolExam/paperUpdate/${id}`, data)
+ console.log('✅ 更新试卷成功:', response)
+ return response
+ }
+
+ /**
+ * 删除试卷
+ */
+ static async deleteExamPaper(id: string): Promise<ApiResponse<string>> {
+ console.log('🚀 删除试卷:', id)
+ const response = await ApiRequest.delete<string>(`/aiol/aiolExam/paperDelete/${id}`)
+ console.log('✅ 删除试卷成功:', response)
+ return response
+ }
+
+ /**
+ * 批量删除试卷
+ */
+ static async batchDeleteExamPapers(ids: string[]): Promise<ApiResponse<string>> {
+ console.log('🚀 批量删除试卷:', ids)
+ const response = await ApiRequest.post<string>('/aiol/aiolExam/paperBatchDelete', { ids })
+ console.log('✅ 批量删除试卷成功:', response)
+ return response
+ }
+
+ /**
+ * 发布试卷
+ */
+ static async publishExamPaper(id: string, data: {
+ startTime: string
+ endTime: string
+ classIds?: string[]
+ }): Promise<ApiResponse<string>> {
+ console.log('🚀 发布试卷:', { id, data })
+ const response = await ApiRequest.post<string>(`/aiol/aiolExam/paperPublish/${id}`, data)
+ console.log('✅ 发布试卷成功:', response)
+ return response
+ }
+
+ /**
+ * 取消发布试卷
+ */
+ static async unpublishExamPaper(id: string): Promise<ApiResponse<string>> {
+ console.log('🚀 取消发布试卷:', id)
+ const response = await ApiRequest.post<string>(`/aiol/aiolExam/paperUnpublish/${id}`)
+ console.log('✅ 取消发布试卷成功:', response)
+ return response
+ }
+
+ /**
+ * 结束试卷
+ */
+ static async endExamPaper(id: string): Promise<ApiResponse<string>> {
+ console.log('🚀 结束试卷:', id)
+ const response = await ApiRequest.post<string>(`/aiol/aiolExam/paperEnd/${id}`)
+ console.log('✅ 结束试卷成功:', response)
+ return response
+ }
+
+ /**
+ * 导入试卷
+ */
+ static async importExamPaper(file: File): Promise<ApiResponse<string>> {
+ console.log('🚀 导入试卷:', file.name)
+ const formData = new FormData()
+ formData.append('file', file)
+ const response = await ApiRequest.post<string>('/aiol/aiolExam/paperImport', formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data'
+ }
+ })
+ console.log('✅ 导入试卷成功:', response)
+ return response
+ }
+
+ /**
+ * 导出试卷
+ */
+ static async exportE