import { Request, Response } from 'express'; import { QuestionModel, CreateQuestionData, ExcelQuestionData } from '../models'; import * as XLSX from 'xlsx'; export class QuestionController { // 获取题目列表 static async getQuestions(req: Request, res: Response) { try { const { type, category, keyword, startDate, endDate, page = 1, limit = 10 } = req.query; const result = await QuestionModel.findAll({ type: type as string, category: category as string, keyword: keyword as string, startDate: startDate as string, endDate: endDate as string, limit: parseInt(limit as string), offset: (parseInt(page as string) - 1) * parseInt(limit as string) }); res.json({ success: true, data: result.questions, pagination: { page: parseInt(page as string), limit: parseInt(limit as string), total: result.total, pages: Math.ceil(result.total / parseInt(limit as string)) } }); } catch (error: any) { console.error('获取题目列表失败:', error); res.status(500).json({ success: false, message: error.message || '获取题目列表失败' }); } } // 获取单个题目 static async getQuestion(req: Request, res: Response) { try { const { id } = req.params; const question = await QuestionModel.findById(id); if (!question) { return res.status(404).json({ success: false, message: '题目不存在' }); } res.json({ success: true, data: question }); } catch (error: any) { console.error('获取题目失败:', error); res.status(500).json({ success: false, message: error.message || '获取题目失败' }); } } // 创建题目 static async createQuestion(req: Request, res: Response) { try { const { content, type, category, options, answer, score } = req.body; const questionData: CreateQuestionData = { content, type, category, options, answer, score }; // 验证题目数据 const errors = QuestionModel.validateQuestionData(questionData); if (errors.length > 0) { return res.status(400).json({ success: false, message: '数据验证失败', errors }); } const question = await QuestionModel.create(questionData); res.json({ success: true, data: question }); } catch (error: any) { console.error('创建题目失败:', error); res.status(500).json({ success: false, message: error.message || '创建题目失败' }); } } // 更新题目 static async updateQuestion(req: Request, res: Response) { try { const { id } = req.params; const { content, type, category, options, answer, score } = req.body; const updateData: Partial = {}; if (content !== undefined) updateData.content = content; if (type !== undefined) updateData.type = type; if (category !== undefined) updateData.category = category; if (options !== undefined) updateData.options = options; if (answer !== undefined) updateData.answer = answer; if (score !== undefined) updateData.score = score; const question = await QuestionModel.update(id, updateData); res.json({ success: true, data: question }); } catch (error: any) { console.error('更新题目失败:', error); res.status(500).json({ success: false, message: error.message || '更新题目失败' }); } } // 删除题目 static async deleteQuestion(req: Request, res: Response) { try { const { id } = req.params; const success = await QuestionModel.delete(id); if (!success) { return res.status(404).json({ success: false, message: '题目不存在' }); } res.json({ success: true, message: '删除成功' }); } catch (error: any) { console.error('删除题目失败:', error); res.status(500).json({ success: false, message: error.message || '删除题目失败' }); } } // Excel导入题目 static async importQuestions(req: Request, res: Response) { try { if (!req.file) { return res.status(400).json({ success: false, message: '请上传Excel文件' }); } // 读取Excel文件 const workbook = XLSX.read(req.file.buffer, { type: 'buffer' }); const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; // 转换为JSON数据 const rawData = XLSX.utils.sheet_to_json(worksheet); // 转换数据格式 const questionsData: ExcelQuestionData[] = rawData.map((row: any) => ({ content: row['题目内容'] || row['content'], type: QuestionController.mapQuestionType(row['题型'] || row['type']), category: row['题目类别'] || row['category'] || '通用', answer: row['标准答案'] || row['answer'], score: parseInt(row['分值'] || row['score']) || 0, options: QuestionController.parseOptions(row['选项'] || row['options']) })); // 验证数据 const validation = QuestionModel.validateExcelData(questionsData); if (!validation.valid) { return res.status(400).json({ success: false, message: 'Excel数据格式错误', errors: validation.errors }); } // 批量创建题目 const result = await QuestionModel.createMany(questionsData as any[]); res.json({ success: true, data: { imported: result.success, total: questionsData.length, errors: result.errors } }); } catch (error: any) { console.error('Excel导入失败:', error); res.status(500).json({ success: false, message: error.message || 'Excel导入失败' }); } } // 映射题型 private static mapQuestionType(type: string): string { const typeMap: { [key: string]: string } = { '单选': 'single', '多选': 'multiple', '判断': 'judgment', '文字描述': 'text', 'single': 'single', 'multiple': 'multiple', 'judgment': 'judgment', 'text': 'text' }; return typeMap[type] || 'single'; } // 解析选项 private static parseOptions(optionsStr: string): string[] | undefined { if (!optionsStr) return undefined; // 支持多种分隔符:|、;、\n const separators = ['|', ';', '\n']; for (const separator of separators) { if (optionsStr.includes(separator)) { return optionsStr.split(separator).map(opt => opt.trim()).filter(opt => opt); } } // 如果没有找到分隔符,返回单个选项 return [optionsStr.trim()]; } // Excel导出题目 static async exportQuestions(req: Request, res: Response) { try { const { type, category } = req.query; // 获取所有题目数据(使用大的limit值获取所有题目) const result = await QuestionModel.findAll({ type: type as string, category: category as string, limit: 10000, // 使用大的limit值获取所有题目 offset: 0 }); const questions = result.questions; // 转换为Excel数据格式 const excelData = questions.map((question: any) => ({ '题目ID': question.id, '题目内容': question.content, '题型': this.getQuestionTypeLabel(question.type), '题目类别': question.category || '通用', '选项': question.options ? question.options.join('|') : '', '标准答案': question.answer, '分值': question.score, '创建时间': new Date(question.createdAt).toLocaleString() })); // 创建Excel工作簿和工作表 const workbook = XLSX.utils.book_new(); const worksheet = XLSX.utils.json_to_sheet(excelData); // 设置列宽 const columnWidths = [ { wch: 10 }, // 题目ID { wch: 60 }, // 题目内容 { wch: 10 }, // 题型 { wch: 15 }, // 题目类别 { wch: 80 }, // 选项 { wch: 20 }, // 标准答案 { wch: 8 }, // 分值 { wch: 20 } // 创建时间 ]; worksheet['!cols'] = columnWidths; // 将工作表添加到工作簿 XLSX.utils.book_append_sheet(workbook, worksheet, '题目列表'); // 设置响应头 res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); res.setHeader('Content-Disposition', `attachment; filename=questions_${new Date().getTime()}.xlsx`); // 生成Excel文件并发送 const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'buffer' }); res.send(excelBuffer); } catch (error: any) { console.error('Excel导出失败:', error); res.status(500).json({ success: false, message: error.message || 'Excel导出失败' }); } } // 获取题型中文标签 private static getQuestionTypeLabel(type: string): string { const typeMap: { [key: string]: string } = { 'single': '单选题', 'multiple': '多选题', 'judgment': '判断题', 'text': '文字题' }; return typeMap[type] || type; } }