feat: 课程章节部分接口对接(编辑,查询,删除章节);阅卷中心接口对接: 获取阅卷列表, 获取班级列表;试卷管理接口对接: 获取试卷列表
This commit is contained in:
commit
e645a190dd
@ -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) {
|
||||||
|
@ -23,7 +23,7 @@ import type {
|
|||||||
CourseComment,
|
CourseComment,
|
||||||
Quiz,
|
Quiz,
|
||||||
LearningProgress,
|
LearningProgress,
|
||||||
SearchRequest,
|
|
||||||
Instructor,
|
Instructor,
|
||||||
} from '../types'
|
} from '../types'
|
||||||
|
|
||||||
|
@ -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
|
@ -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);
|
||||||
|
|
||||||
// 更新本地数据
|
// 更新本地数据
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
}))
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 预览试卷
|
// 预览试卷
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
@ -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,
|
||||||
|
@ -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
194
tatus --porcelain
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
[1mdiff --git a/src/api/examples/usage.ts b/src/api/examples/usage.ts[m
|
||||||
|
[1mindex 0363f0d..64dbce6 100644[m
|
||||||
|
[1m--- a/src/api/examples/usage.ts[m
|
||||||
|
[1m+++ b/src/api/examples/usage.ts[m
|
||||||
|
[36m@@ -93,13 +93,8 @@[m [mexport const searchCoursesExample = async () => {[m
|
||||||
|
try {[m
|
||||||
|
const response = await CourseApi.searchCourses({[m
|
||||||
|
keyword: 'Vue.js',[m
|
||||||
|
[31m- category: '前端开发',[m
|
||||||
|
[31m- level: 'intermediate',[m
|
||||||
|
[31m- price: 'paid',[m
|
||||||
|
[31m- rating: 4,[m
|
||||||
|
[31m- sortBy: 'newest',[m
|
||||||
|
[31m- page: 1,[m
|
||||||
|
[31m- pageSize: 10[m
|
||||||
|
[32m+[m[32m limit: '20',[m
|
||||||
|
[32m+[m[32m page: 1[m
|
||||||
|
})[m
|
||||||
|
[m
|
||||||
|
if (response.code === 200) {[m
|
||||||
|
[1mdiff --git a/src/api/modules/course.ts b/src/api/modules/course.ts[m
|
||||||
|
[1mindex 9d3a3e1..e8cdde2 100644[m
|
||||||
|
[1m--- a/src/api/modules/course.ts[m
|
||||||
|
[1m+++ b/src/api/modules/course.ts[m
|
||||||
|
[36m@@ -23,7 +23,7 @@[m [mimport type {[m
|
||||||
|
CourseComment,[m
|
||||||
|
Quiz,[m
|
||||||
|
LearningProgress,[m
|
||||||
|
[31m- SearchRequest,[m
|
||||||
|
[32m+[m
|
||||||
|
Instructor,[m
|
||||||
|
} from '../types'[m
|
||||||
|
[m
|
||||||
|
[1mdiff --git a/src/api/modules/exam.ts b/src/api/modules/exam.ts[m
|
||||||
|
[1mindex 5a1865f..f7be93a 100644[m
|
||||||
|
[1m--- a/src/api/modules/exam.ts[m
|
||||||
|
[1m+++ b/src/api/modules/exam.ts[m
|
||||||
|
[36m@@ -333,6 +333,420 @@[m [mexport class ExamApi {[m
|
||||||
|
console.log('✅ 批量添加题目答案成功:', responses)[m
|
||||||
|
return responses[m
|
||||||
|
}[m
|
||||||
|
[32m+[m
|
||||||
|
[32m+[m[32m // ========== 试卷管理相关接口 ==========[m
|
||||||
|
[32m+[m
|
||||||
|
[32m+[m[32m /**[m
|
||||||
|
[32m+[m[32m * 获取试卷列表[m
|
||||||
|
[32m+[m[32m */[m
|
||||||
|
[32m+[m[32m static async getExamPaperList(params: {[m
|
||||||
|
[32m+[m[32m page?: number[m
|
||||||
|
[32m+[m[32m pageSize?: number[m
|
||||||
|
[32m+[m[32m keyword?: string[m
|
||||||
|
[32m+[m[32m category?: string[m
|
||||||
|
[32m+[m[32m status?: string[m
|
||||||
|
[32m+[m[32m difficulty?: string[m
|
||||||
|
[32m+[m[32m creator?: string[m
|
||||||
|
[32m+[m[32m } = {}): Promise<ApiResponseWithResult<{[m
|
||||||
|
[32m+[m[32m list: any[][m
|
||||||
|
[32m+[m[32m total: number[m
|
||||||
|
[32m+[m[32m page: number[m
|
||||||
|
[32m+[m[32m pageSize: number[m
|
||||||
|
[32m+[m[32m }>> {[m
|
||||||
|
[32m+[m[32m console.log('🚀 获取试卷列表:', params)[m
|
||||||
|
[32m+[m[32m const response = await ApiRequest.get<{[m
|
||||||
|
[32m+[m[32m result: {[m
|
||||||
|
[32m+[m[32m records: any[][m
|
||||||
|
[32m+[m[32m total: number[m
|
||||||
|
[32m+[m[32m current: number[m
|
||||||
|
[32m+[m[32m size: number[m
|
||||||
|
[32m+[m[32m }[m
|
||||||
|
[32m+[m[32m }>('/aiol/aiolPaper/list', { params })[m
|
||||||
|
[32m+[m[32m console.log('✅ 获取试卷列表成功:', response)[m
|
||||||
|
[32m+[m[32m return response[m
|
||||||
|
[32m+[m[32m }[m
|
||||||
|
[32m+[m
|
||||||
|
[32m+[m[32m /**[m
|
||||||
|
[32m+[m[32m * 获取试卷详情[m
|
||||||
|
[32m+[m[32m */[m
|
||||||
|
[32m+[m[32m static async getExamPaperDetail(id: string): Promise<ApiResponse<any>> {[m
|
||||||
|
[32m+[m[32m console.log('🚀 获取试卷详情:', id)[m
|
||||||
|
[32m+[m[32m const response = await ApiRequest.get<any>(`/aiol/aiolExam/paperDetail/${id}`)[m
|
||||||
|
[32m+[m[32m console.log('✅ 获取试卷详情成功:', response)[m
|
||||||
|
[32m+[m[32m return response[m
|
||||||
|
[32m+[m[32m }[m
|
||||||
|
[32m+[m
|
||||||
|
[32m+[m[32m /**[m
|
||||||
|
[32m+[m[32m * 创建试卷[m
|
||||||
|
[32m+[m[32m */[m
|
||||||
|
[32m+[m[32m static async createExamPaper(data: {[m
|
||||||
|
[32m+[m[32m name: string[m
|
||||||
|
[32m+[m[32m category: string[m
|
||||||
|
[32m+[m[32m description?: string[m
|
||||||
|
[32m+[m[32m totalScore: number[m
|
||||||
|
[32m+[m[32m difficulty: string[m
|
||||||
|
[32m+[m[32m duration: number[m
|
||||||
|
[32m+[m[32m questions: any[][m
|
||||||
|
[32m+[m[32m }): Promise<ApiResponse<string>> {[m
|
||||||
|
[32m+[m[32m console.log('🚀 创建试卷:', data)[m
|
||||||
|
[32m+[m[32m const response = await ApiRequest.post<string>('/aiol/aiolPaper/add', data)[m
|
||||||
|
[32m+[m[32m console.log('✅ 创建试卷成功:', response)[m
|
||||||
|
[32m+[m[32m return response[m
|
||||||
|
[32m+[m[32m }[m
|
||||||
|
[32m+[m
|
||||||
|
[32m+[m[32m /**[m
|
||||||
|
[32m+[m[32m * 更新试卷[m
|
||||||
|
[32m+[m[32m */[m
|
||||||
|
[32m+[m[32m static async updateExamPaper(id: string, data: {[m
|
||||||
|
[32m+[m[32m name?: string[m
|
||||||
|
[32m+[m[32m category?: string[m
|
||||||
|
[32m+[m[32m description?: string[m
|
||||||
|
[32m+[m[32m totalScore?: number[m
|
||||||
|
[32m+[m[32m difficulty?: string[m
|
||||||
|
[32m+[m[32m duration?: number[m
|
||||||
|
[32m+[m[32m questions?: any[][m
|
||||||
|
[32m+[m[32m }): Promise<ApiResponse<string>> {[m
|
||||||
|
[32m+[m[32m console.log('🚀 更新试卷:', { id, data })[m
|
||||||
|
[32m+[m[32m const response = await ApiRequest.put<string>(`/aiol/aiolExam/paperUpdate/${id}`, data)[m
|
||||||
|
[32m+[m[32m console.log('✅ 更新试卷成功:', response)[m
|
||||||
|
[32m+[m[32m return response[m
|
||||||
|
[32m+[m[32m }[m
|
||||||
|
[32m+[m
|
||||||
|
[32m+[m[32m /**[m
|
||||||
|
[32m+[m[32m * 删除试卷[m
|
||||||
|
[32m+[m[32m */[m
|
||||||
|
[32m+[m[32m static async deleteExamPaper(id: string): Promise<ApiResponse<string>> {[m
|
||||||
|
[32m+[m[32m console.log('🚀 删除试卷:', id)[m
|
||||||
|
[32m+[m[32m const response = await ApiRequest.delete<string>(`/aiol/aiolExam/paperDelete/${id}`)[m
|
||||||
|
[32m+[m[32m console.log('✅ 删除试卷成功:', response)[m
|
||||||
|
[32m+[m[32m return response[m
|
||||||
|
[32m+[m[32m }[m
|
||||||
|
[32m+[m
|
||||||
|
[32m+[m[32m /**[m
|
||||||
|
[32m+[m[32m * 批量删除试卷[m
|
||||||
|
[32m+[m[32m */[m
|
||||||
|
[32m+[m[32m static async batchDeleteExamPapers(ids: string[]): Promise<ApiResponse<string>> {[m
|
||||||
|
[32m+[m[32m console.log('🚀 批量删除试卷:', ids)[m
|
||||||
|
[32m+[m[32m const response = await ApiRequest.post<string>('/aiol/aiolExam/paperBatchDelete', { ids })[m
|
||||||
|
[32m+[m[32m console.log('✅ 批量删除试卷成功:', response)[m
|
||||||
|
[32m+[m[32m return response[m
|
||||||
|
[32m+[m[32m }[m
|
||||||
|
[32m+[m
|
||||||
|
[32m+[m[32m /**[m
|
||||||
|
[32m+[m[32m * 发布试卷[m
|
||||||
|
[32m+[m[32m */[m
|
||||||
|
[32m+[m[32m static async publishExamPaper(id: string, data: {[m
|
||||||
|
[32m+[m[32m startTime: string[m
|
||||||
|
[32m+[m[32m endTime: string[m
|
||||||
|
[32m+[m[32m classIds?: string[][m
|
||||||
|
[32m+[m[32m }): Promise<ApiResponse<string>> {[m
|
||||||
|
[32m+[m[32m console.log('🚀 发布试卷:', { id, data })[m
|
||||||
|
[32m+[m[32m const response = await ApiRequest.post<string>(`/aiol/aiolExam/paperPublish/${id}`, data)[m
|
||||||
|
[32m+[m[32m console.log('✅ 发布试卷成功:', response)[m
|
||||||
|
[32m+[m[32m return response[m
|
||||||
|
[32m+[m[32m }[m
|
||||||
|
[32m+[m
|
||||||
|
[32m+[m[32m /**[m
|
||||||
|
[32m+[m[32m * 取消发布试卷[m
|
||||||
|
[32m+[m[32m */[m
|
||||||
|
[32m+[m[32m static async unpublishExamPaper(id: string): Promise<ApiResponse<string>> {[m
|
||||||
|
[32m+[m[32m console.log('🚀 取消发布试卷:', id)[m
|
||||||
|
[32m+[m[32m const response = await ApiRequest.post<string>(`/aiol/aiolExam/paperUnpublish/${id}`)[m
|
||||||
|
[32m+[m[32m console.log('✅ 取消发布试卷成功:', response)[m
|
||||||
|
[32m+[m[32m return response[m
|
||||||
|
[32m+[m[32m }[m
|
||||||
|
[32m+[m
|
||||||
|
[32m+[m[32m /**[m
|
||||||
|
[32m+[m[32m * 结束试卷[m
|
||||||
|
[32m+[m[32m */[m
|
||||||
|
[32m+[m[32m static async endExamPaper(id: string): Promise<ApiResponse<string>> {[m
|
||||||
|
[32m+[m[32m console.log('🚀 结束试卷:', id)[m
|
||||||
|
[32m+[m[32m const response = await ApiRequest.post<string>(`/aiol/aiolExam/paperEnd/${id}`)[m
|
||||||
|
[32m+[m[32m console.log('✅ 结束试卷成功:', response)[m
|
||||||
|
[32m+[m[32m return response[m
|
||||||
|
[32m+[m[32m }[m
|
||||||
|
[32m+[m
|
||||||
|
[32m+[m[32m /**[m
|
||||||
|
[32m+[m[32m * 导入试卷[m
|
||||||
|
[32m+[m[32m */[m
|
||||||
|
[32m+[m[32m static async importExamPaper(file: File): Promise<ApiResponse<string>> {[m
|
||||||
|
[32m+[m[32m console.log('🚀 导入试卷:', file.name)[m
|
||||||
|
[32m+[m[32m const formData = new FormData()[m
|
||||||
|
[32m+[m[32m formData.append('file', file)[m
|
||||||
|
[32m+[m[32m const response = await ApiRequest.post<string>('/aiol/aiolExam/paperImport', formData, {[m
|
||||||
|
[32m+[m[32m headers: {[m
|
||||||
|
[32m+[m[32m 'Content-Type': 'multipart/form-data'[m
|
||||||
|
[32m+[m[32m }[m
|
||||||
|
[32m+[m[32m })[m
|
||||||
|
[32m+[m[32m console.log('✅ 导入试卷成功:', response)[m
|
||||||
|
[32m+[m[32m return response[m
|
||||||
|
[32m+[m[32m }[m
|
||||||
|
[32m+[m
|
||||||
|
[32m+[m[32m /**[m
|
||||||
|
[32m+[m[32m * 导出试卷[m
|
||||||
|
[32m+[m[32m */[m
|
||||||
|
[32m+[m[32m static async exportE
|
Loading…
x
Reference in New Issue
Block a user