From 6b685501dd4933627789d814c53118e732a8215e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=BC=A0?= <2091066548@qq.com> Date: Fri, 5 Sep 2025 19:46:11 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9Ahls=E6=B5=81=E8=AF=AF=E5=88=A0?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E8=A7=86=E9=A2=91=E6=8A=A5=E9=94=99=EF=BC=8C?= =?UTF-8?q?=E4=BD=9C=E4=B8=9A=E8=B5=84=E6=96=99=E6=8E=A5=E5=8F=A3=E5=AF=B9?= =?UTF-8?q?=E6=8E=A5=EF=BC=8C=E9=80=BB=E8=BE=91=E5=A4=84=E7=90=86=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=EF=BC=8C=E8=AF=BE=E7=A8=8B=E5=88=97=E8=A1=A8=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E6=8C=89=E7=85=A7=20=E5=90=8E=E7=AB=AF=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E5=88=86=E4=B8=BA=E9=9C=80=E4=BC=A0token=20=E5=92=8C?= =?UTF-8?q?=E4=B8=8D=E9=9C=80=E8=A6=81=E6=A0=B9=E6=8D=AE=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/modules/exam.ts | 16 +- src/views/teacher/ExamPages/AddQuestion.vue | 335 +++++++++++++++--- .../teacher/ExamPages/QuestionManagement.vue | 161 +++++++-- 3 files changed, 433 insertions(+), 79 deletions(-) diff --git a/src/api/modules/exam.ts b/src/api/modules/exam.ts index df35dd4..90b4760 100644 --- a/src/api/modules/exam.ts +++ b/src/api/modules/exam.ts @@ -117,7 +117,7 @@ export class ExamApi { */ static async getQuestionDetail(questionId: string): Promise> { console.log('🚀 查询题目详情:', { questionId }) - const response = await ApiRequest.get(`/biz/repo/repoList/${questionId}`) + const response = await ApiRequest.get(`/aiol/aiolRepo/repoList/${questionId}`) console.log('✅ 查询题目详情成功:', response) return response } @@ -127,7 +127,7 @@ export class ExamApi { */ static async createQuestion(data: CreateQuestionRequest): Promise> { console.log('🚀 添加题目:', data) - const response = await ApiRequest.post('/gen/question/question/add', data) + const response = await ApiRequest.post('/aiol/aiolQuestion/add', data) console.log('✅ 添加题目成功:', response) return response } @@ -137,7 +137,7 @@ export class ExamApi { */ static async updateQuestion(data: UpdateQuestionRequest): Promise> { console.log('🚀 编辑题目:', data) - const response = await ApiRequest.put('/gen/question/question/edit', data) + const response = await ApiRequest.put('/aiol/aiolQuestion/edit', data) console.log('✅ 编辑题目成功:', response) return response } @@ -145,15 +145,15 @@ export class ExamApi { /** * 删除题目 */ - static async deleteQuestion(id: string): Promise> { - console.log('🚀 删除题目:', { id }) - const response = await ApiRequest.delete('/gen/question/question/delete', { - params: { id } - }) + static async deleteQuestion(questionId: string): Promise> { + console.log('🚀 删除题目:', { questionId }) + const response = await ApiRequest.delete(`/aiol/aiolQuestion/delete?id=${questionId}`) console.log('✅ 删除题目成功:', response) return response } + + // ========== 题目选项管理 ========== /** diff --git a/src/views/teacher/ExamPages/AddQuestion.vue b/src/views/teacher/ExamPages/AddQuestion.vue index 5aa5a8f..a7115fd 100644 --- a/src/views/teacher/ExamPages/AddQuestion.vue +++ b/src/views/teacher/ExamPages/AddQuestion.vue @@ -263,6 +263,19 @@ const message = useMessage(); const questionId = route.params.questionId as string | undefined; const isEditMode = ref(!!questionId); +// 题目类型映射 +const getQuestionTypeKey = (type: number): string => { + const typeMap: { [key: number]: string } = { + 0: 'single_choice', // 单选题 + 1: 'multiple_choice', // 多选题 + 2: 'true_false', // 判断题 + 3: 'fill_blank', // 填空题 + 4: 'short_answer', // 简答题 + 5: 'composite' // 复合题 + }; + return typeMap[type] || 'single_choice'; +}; + // 表单引用 const formRef = ref(); @@ -288,16 +301,16 @@ const categoryOptions = ref([ // 难度选项 const difficultyOptions = ref([ - { label: '简单', value: 'easy' }, - { label: '中等', value: 'medium' }, - { label: '困难', value: 'hard' } + { label: '简单', value: 0 }, + { label: '中等', value: 1 }, + { label: '困难', value: 2 } ]); // 试题表单数据 const questionForm = reactive({ type: 'single_choice', // 默认单选题 category: '', - difficulty: '', + difficulty: 0, // 默认简单 score: 10, title: '', options: [ @@ -323,12 +336,13 @@ const formRules = { trigger: 'change' }, category: { - required: true, + required: false, message: '请选择分类', trigger: 'change' }, difficulty: { required: true, + type: 'number', message: '请选择难度', trigger: 'change' }, @@ -397,14 +411,20 @@ const getQuestionTypeNumber = (type: string): number => { return typeMap[type] || 0; }; -// 难度映射函数:将字符串难度转换为数字 -const getDifficultyNumber = (difficulty: string): number => { +// 难度映射函数:将数字难度转换为数字(保持原值) +const getDifficultyNumber = (difficulty: number | string): number => { + // 如果已经是数字,直接返回 + if (typeof difficulty === 'number') { + return difficulty; + } + + // 如果是字符串,进行转换 const difficultyMap: Record = { - 'easy': 1, // 简单 - 'medium': 2, // 中等 - 'hard': 3 // 困难 + 'easy': 0, // 简单 + 'medium': 1, // 中等 + 'hard': 2 // 困难 }; - return difficultyMap[difficulty] || 1; + return difficultyMap[difficulty] || 0; }; // 保存试题 @@ -478,8 +498,9 @@ const saveQuestion = async () => { // 创建新题目的完整流程 const createNewQuestion = async (bankId: string) => { try { - // 第一步:添加题干,获取questionId + // 只调用一次API创建题目 const questionData = { + parentId: null, // 父题目ID,普通题目为null type: getQuestionTypeNumber(questionForm.type), content: questionForm.title, analysis: questionForm.explanation || '', @@ -487,37 +508,33 @@ const createNewQuestion = async (bankId: string) => { score: questionForm.score }; - console.log('🚀 第一步:创建题目题干:', questionData); - const questionResponse = await ExamApi.createQuestion(questionData); - - if (!questionResponse.data) { - throw new Error('创建题目失败,未返回题目ID'); + console.log('🚀 创建题目,数据:', questionData); + const response = await ExamApi.createQuestion(questionData); + console.log('📊 创建题目API响应:', response); + + // 处理API响应 + let success = false; + let questionId = null; + + if (response.data) { + const apiResponse = response.data as any; + + // 检查是否是包装格式 {success, code, result} + if (typeof apiResponse === 'object' && ('success' in apiResponse || 'code' in apiResponse)) { + success = apiResponse.success === true || apiResponse.code === 200 || apiResponse.code === 0; + questionId = apiResponse.result || apiResponse.data; + } else { + // 直接是题目ID + success = true; + questionId = apiResponse; + } } - const createdQuestionId = questionResponse.data; - console.log('✅ 题目创建成功,questionId:', createdQuestionId); - - // 第二步:根据题型添加选项和答案 - if (questionForm.type === 'single_choice') { - await handleSingleChoiceQuestion(createdQuestionId); + if (!success) { + throw new Error('创建题目失败'); } - // TODO: 处理其他题型 - // else if (questionForm.type === 'multiple_choice') { - // await handleMultipleChoiceQuestion(createdQuestionId); - // } else if (questionForm.type === 'true_false') { - // await handleTrueFalseQuestion(createdQuestionId); - // } - // 其他题型暂不处理 - // 最后一步:将题目绑定到题库 - const questionRepoData = { - repoId: bankId, - questionId: createdQuestionId - }; - - console.log('🚀 最后一步:绑定题目到题库:', questionRepoData); - await ExamApi.createQuestionRepo(questionRepoData); - console.log('✅ 题目绑定到题库成功'); + console.log('✅ 题目创建成功,题目ID:', questionId); message.success('题目保存成功'); @@ -772,36 +789,252 @@ const validateCompositeQuestion = (): boolean => { // 组件挂载 onMounted(async () => { + console.log('AddQuestion 组件挂载完成'); + console.log('编辑模式:', isEditMode.value); + console.log('题目ID:', questionId); + // 如果是编辑模式,加载题目数据 if (isEditMode.value && questionId) { - await loadQuestionData(questionId); + // 优先从路由参数中获取数据 + if (route.query.questionData) { + try { + const questionData = JSON.parse(route.query.questionData as string); + console.log('📊 从路由参数获取题目数据:', questionData); + renderQuestionData(questionData); + } catch (error) { + console.error('❌ 解析路由参数中的题目数据失败:', error); + // 如果解析失败,尝试从API加载 + await loadQuestionData(questionId); + } + } else { + // 如果没有路由参数,从API加载 + await loadQuestionData(questionId); + } } }); +// 渲染题目数据 +const renderQuestionData = (questionData: any) => { + console.log('🎨 开始渲染题目数据:', questionData); + + if (!questionData) return; + + const { question, answer = [], children = [] } = questionData; + + if (question) { + // 设置基本信息 + questionForm.type = getQuestionTypeKey(question.type); + questionForm.title = question.content || ''; + questionForm.explanation = question.analysis || ''; + questionForm.score = question.score || 10; + questionForm.difficulty = question.difficulty || 0; + + console.log('📝 题目基本信息:', { + type: questionForm.type, + title: questionForm.title, + explanation: questionForm.explanation, + score: questionForm.score + }); + + // 根据题目类型处理选项和答案 + if (question.type === 0) { // 单选题 + renderSingleChoiceData(answer); + } else if (question.type === 1) { // 多选题 + renderMultipleChoiceData(answer); + } else if (question.type === 2) { // 判断题 + renderTrueFalseData(answer); + } else if (question.type === 3) { // 填空题 + renderFillBlankData(answer); + } else if (question.type === 4) { // 简答题 + renderShortAnswerData(answer); + } else if (question.type === 5) { // 复合题 + renderCompositeData(children); + } + } +}; + +// 渲染单选题数据 +const renderSingleChoiceData = (answers: any[]) => { + console.log('🔘 渲染单选题数据:', answers); + + if (answers && answers.length > 0) { + // 按orderNo排序 + const sortedAnswers = answers.sort((a, b) => a.orderNo - b.orderNo); + + // 设置选项 + questionForm.options = sortedAnswers.map(answer => ({ + content: answer.content || '' + })); + + // 设置正确答案(orderNo从1开始,数组索引从0开始) + const correctAnswer = sortedAnswers.find(answer => answer.izCorrent === 1); + if (correctAnswer) { + questionForm.correctAnswer = correctAnswer.orderNo - 1; + } + + console.log('✅ 单选题渲染完成:', { + options: questionForm.options, + correctAnswer: questionForm.correctAnswer + }); + } +}; + +// 渲染多选题数据 +const renderMultipleChoiceData = (answers: any[]) => { + console.log('☑️ 渲染多选题数据:', answers); + + if (answers && answers.length > 0) { + // 按orderNo排序 + const sortedAnswers = answers.sort((a, b) => a.orderNo - b.orderNo); + + // 设置选项 + questionForm.options = sortedAnswers.map(answer => ({ + content: answer.content || '' + })); + + // 设置正确答案(可能有多个) + questionForm.correctAnswers = sortedAnswers + .filter(answer => answer.izCorrent === 1) + .map(answer => answer.orderNo - 1); + + console.log('✅ 多选题渲染完成:', { + options: questionForm.options, + correctAnswers: questionForm.correctAnswers + }); + } +}; + +// 渲染判断题数据 +const renderTrueFalseData = (answers: any[]) => { + console.log('✔️ 渲染判断题数据:', answers); + + if (answers && answers.length > 0) { + const correctAnswer = answers.find(answer => answer.izCorrent === 1); + if (correctAnswer) { + // 假设判断题的正确答案content为"正确"或"错误" + questionForm.trueFalseAnswer = correctAnswer.content === '正确' || correctAnswer.content === 'true'; + } + + console.log('✅ 判断题渲染完成:', { + trueFalseAnswer: questionForm.trueFalseAnswer + }); + } +}; + +// 渲染填空题数据 +const renderFillBlankData = (answers: any[]) => { + console.log('📝 渲染填空题数据:', answers); + + if (answers && answers.length > 0) { + questionForm.fillBlankAnswers = answers.map(answer => ({ + content: answer.content || '', + score: 1, + caseSensitive: false + })); + + console.log('✅ 填空题渲染完成:', { + fillBlankAnswers: questionForm.fillBlankAnswers + }); + } +}; + +// 渲染简答题数据 +const renderShortAnswerData = (answers: any[]) => { + console.log('📄 渲染简答题数据:', answers); + + if (answers && answers.length > 0) { + questionForm.shortAnswer = answers[0]?.content || ''; + + console.log('✅ 简答题渲染完成:', { + shortAnswer: questionForm.shortAnswer + }); + } +}; + +// 渲染复合题数据 +const renderCompositeData = (children: any[]) => { + console.log('🔗 渲染复合题数据:', children); + + if (children && children.length > 0) { + questionForm.compositeData = { + subQuestions: children.map((child: any) => ({ + id: child.question?.id, + type: getQuestionTypeKey(child.question?.type || 0), + title: child.question?.content || '', + explanation: child.question?.analysis || '', + score: child.question?.score || 1, + options: child.answer || [], + correctAnswer: null, + correctAnswers: [] + })) + }; + + console.log('✅ 复合题渲染完成:', { + subQuestions: questionForm.compositeData.subQuestions.length + }); + } +}; + // 加载题目数据(编辑模式使用) const loadQuestionData = async (id: string) => { try { - // TODO: 实现编辑模式的数据加载 - // const response = await ExamApi.getQuestionDetail(id); - // if (response.data) { - // // 根据返回的数据回显表单 - // } - console.log('加载题目数据,题目ID:', id); - message.info('编辑模式数据加载暂未实现'); + console.log('🚀 开始加载题目数据,题目ID:', id); + + // 调用题目详情接口 + const response = await ExamApi.getQuestionDetail(id); + console.log('📊 题目详情API响应:', response); + + // 处理API响应 + let questionData = null; + let success = false; + + if (response.data) { + const apiResponse = response.data as any; + + // 检查是否是包装格式 {success, code, result} + if (typeof apiResponse === 'object' && 'result' in apiResponse) { + success = apiResponse.success === true || apiResponse.code === 200 || apiResponse.code === 0; + questionData = apiResponse.result; + } else { + // 直接是题目数据 + success = true; + questionData = apiResponse; + } + } + + if (success && questionData) { + console.log('✅ 获取题目详情成功,开始渲染数据'); + renderQuestionData(questionData); + } else { + console.error('❌ 获取题目详情失败'); + message.error('获取题目详情失败'); + } } catch (error) { - console.error('加载题目数据错误:', error); + console.error('❌ 加载题目数据异常:', error); message.error('加载题目数据失败,请检查网络连接'); } }; -const getDifficultyLabel = (difficulty: string): string => { - const difficultyMap: { [key: string]: string } = { +const getDifficultyLabel = (difficulty: number | string): string => { + const difficultyMap: { [key: number]: string } = { + 0: '简单', + 1: '中等', + 2: '困难' + }; + + // 如果是数字,直接映射 + if (typeof difficulty === 'number') { + return difficultyMap[difficulty] || '未知'; + } + + // 如果是字符串,先转换为数字再映射 + const stringMap: { [key: string]: string } = { 'easy': '简单', 'medium': '中等', 'hard': '困难' }; - return difficultyMap[difficulty] || difficulty; + return stringMap[difficulty] || difficulty; }; const getCategoryLabel = (category: string): string => { diff --git a/src/views/teacher/ExamPages/QuestionManagement.vue b/src/views/teacher/ExamPages/QuestionManagement.vue index feb915f..98a460e 100644 --- a/src/views/teacher/ExamPages/QuestionManagement.vue +++ b/src/views/teacher/ExamPages/QuestionManagement.vue @@ -302,6 +302,11 @@ const createColumns = ({ width: 300, ellipsis: { tooltip: true + }, + render(row: Question) { + // 如果是子题目,添加缩进显示 + const prefix = row.parentId ? '  └ ' : ''; + return prefix + row.title; } }, { @@ -311,11 +316,12 @@ const createColumns = ({ align: 'center' as const, render(row: Question) { const typeMap: { [key: string]: { text: string; type: any } } = { - 'single_choice': { text: '单选题', type: 'info' }, - 'multiple_choice': { text: '多选题', type: 'warning' }, - 'true_false': { text: '判断题', type: 'success' }, - 'fill_blank': { text: '填空题', type: 'error' }, - 'short_answer': { text: '简答题', type: 'default' } + '单选题': { text: '单选题', type: 'info' }, + '多选题': { text: '多选题', type: 'warning' }, + '判断题': { text: '判断题', type: 'success' }, + '填空题': { text: '填空题', type: 'error' }, + '简答题': { text: '简答题', type: 'default' }, + '复合题': { text: '复合题', type: 'primary' } }; const typeInfo = typeMap[row.type] || { text: row.type, type: 'default' }; return h(NTag, { type: typeInfo.type, size: 'small' }, { default: () => typeInfo.text }); @@ -338,9 +344,9 @@ const createColumns = ({ align: 'center' as const, render(row: Question) { const difficultyMap: { [key: string]: { text: string; type: any } } = { - 'easy': { text: '易', type: 'success' }, - 'medium': { text: '中', type: 'warning' }, - 'hard': { text: '难', type: 'error' } + '简单': { text: '简单', type: 'success' }, + '中等': { text: '中等', type: 'warning' }, + '困难': { text: '困难', type: 'error' } }; const diffInfo = difficultyMap[row.difficulty] || { text: row.difficulty, type: 'default' }; return h(NTag, { type: diffInfo.type, size: 'small' }, { default: () => diffInfo.text }); @@ -450,6 +456,29 @@ const searchQuestions = () => { loadQuestions(); }; +// 题目类型转换函数 +const getQuestionTypeText = (type: number): string => { + const typeMap: { [key: number]: string } = { + 0: '单选题', + 1: '多选题', + 2: '判断题', + 3: '填空题', + 4: '简答题', + 5: '复合题' + }; + return typeMap[type] || '未知类型'; +}; + +// 难度转换函数 +const getDifficultyText = (difficulty: number): string => { + const difficultyMap: { [key: number]: string } = { + 0: '简单', + 1: '中等', + 2: '困难' + }; + return difficultyMap[difficulty] || '未知'; +}; + // 加载题目列表 const loadQuestions = async () => { loading.value = true; @@ -463,18 +492,20 @@ const loadQuestions = async () => { let allData: Question[] = []; // 处理API响应数据 - if (response.code === 200 && response.data) { + if (response.data && (response.data.code === 200 || response.data.code === 0) && response.data.result) { // 将API返回的数据转换为前端格式 - allData = response.data.map((item: any, index: number) => ({ + allData = response.data.result.map((item: any, index: number) => ({ id: item.id || `question_${index}`, sequence: index + 1, - title: item.title || item.content || '题目内容', - type: item.type || '单选题', + title: item.content || '题目内容', + type: getQuestionTypeText(item.type), category: item.category || '未分类', - difficulty: item.difficulty || '简单', + difficulty: getDifficultyText(item.difficulty), score: item.score || 10, - creator: item.createBy || item.creator || '未知', - createTime: item.createTime || new Date().toISOString() + creator: item.createBy || '未知', + createTime: item.createTime || new Date().toISOString(), + parentId: item.parentId, + analysis: item.analysis })); console.log('✅ 题目数据转换成功:', allData.length, '条题目'); } else { @@ -560,13 +591,103 @@ const deleteSelected = () => { console.log('批量删除:', selectedRowKeys.value); }; -const editQuestion = (id: string) => { - console.log('编辑题目:', id); - router.push(`/teacher/exam-management/add-question/${currentBankId.value}/${id}`); +const editQuestion = async (id: string) => { + console.log('🔍 编辑题目,题目ID:', id); + + try { + // 先调用题目详情接口获取完整信息 + console.log('🚀 调用题目详情接口...'); + const response = await ExamApi.getQuestionDetail(id); + console.log('📊 题目详情API响应:', response); + + // 处理API响应,支持不同的响应格式 + let questionData = null; + let success = false; + + if (response.data) { + // 使用类型断言处理API响应 + const apiResponse = response.data as any; + + // 检查是否是包装格式 {success, code, result} + if (typeof apiResponse === 'object' && 'result' in apiResponse) { + success = apiResponse.success === true || apiResponse.code === 200 || apiResponse.code === 0; + questionData = apiResponse.result; + } else { + // 直接是题目数据 + success = true; + questionData = apiResponse; + } + } + + if (success && questionData) { + console.log('✅ 获取题目详情成功:', questionData); + + // 跳转到编辑页面,并传递题目详情数据 + router.push({ + path: `/teacher/exam-management/add-question/${currentBankId.value}/${id}`, + query: { + questionData: JSON.stringify(questionData), + mode: 'edit' + } + }); + } else { + console.error('❌ 获取题目详情失败:', response); + message.error('获取题目详情失败'); + } + } catch (error) { + console.error('❌ 获取题目详情异常:', error); + message.error('获取题目详情失败,请稍后重试'); + } }; -const deleteQuestion = (id: string) => { - console.log('删除题目:', id); +const deleteQuestion = async (id: string) => { + console.log('🗑️ 删除题目,题目ID:', id); + + try { + // 确认删除 + const confirmed = await new Promise((resolve) => { + const dialog = window.confirm('确定要删除这道题目吗?删除后将无法恢复!'); + resolve(dialog); + }); + + if (!confirmed) { + console.log('用户取消删除'); + return; + } + + console.log('🚀 调用删除题目API...'); + const response = await ExamApi.deleteQuestion(id); + console.log('📊 删除题目API响应:', response); + + // 处理API响应 + let success = false; + + if (response.data) { + const apiResponse = response.data as any; + + // 检查是否是包装格式 {success, code, result} + if (typeof apiResponse === 'object' && ('success' in apiResponse || 'code' in apiResponse)) { + success = apiResponse.success === true || apiResponse.code === 200 || apiResponse.code === 0; + } else { + // 直接成功 + success = true; + } + } + + if (success) { + console.log('✅ 删除题目成功'); + message.success('题目删除成功'); + + // 重新加载题目列表 + await loadQuestions(); + } else { + console.error('❌ 删除题目失败'); + message.error('删除题目失败'); + } + } catch (error) { + console.error('❌ 删除题目异常:', error); + message.error('删除题目失败,请稍后重试'); + } }; // 组件挂载时加载数据