fix: 添加批量导入题目通能 #1
This commit is contained in:
@@ -33,6 +33,23 @@ public class AdminController {
|
||||
return "admin";
|
||||
}
|
||||
|
||||
// API: 批量导入题目
|
||||
@PostMapping("/admin/api/questions/batch-import")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Map<String, Object>> batchImportQuestions(@RequestBody List<Map<String, Object>> questions) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
int importedCount = adminService.batchImportQuestions(questions);
|
||||
result.put("success", true);
|
||||
result.put("importedCount", importedCount);
|
||||
result.put("message", "成功导入 " + importedCount + " 道题目");
|
||||
} catch (Exception e) {
|
||||
result.put("success", false);
|
||||
result.put("message", e.getMessage());
|
||||
}
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
// API: 获取题目列表(支持全部、按类型、按分值、按标题模糊查询、联合查询)
|
||||
@GetMapping("/admin/api/questions")
|
||||
@ResponseBody
|
||||
@@ -173,7 +190,6 @@ public class AdminController {
|
||||
@RequestParam(required = false) String answerB,
|
||||
@RequestParam(required = false) String answerC,
|
||||
@RequestParam(required = false) String answerD,
|
||||
@RequestParam(required = false) String answer,
|
||||
@RequestParam(required = false) String rAnswer,
|
||||
@RequestParam Integer score) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
@@ -198,9 +214,6 @@ public class AdminController {
|
||||
.answerB(answerB)
|
||||
.answerC(answerC)
|
||||
.answerD(answerD);
|
||||
} else {
|
||||
// 简答题
|
||||
builder.answer(answer);
|
||||
}
|
||||
|
||||
adminService.updateQuestion(builder.build());
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.baobaot.exam.dao.question;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface adminService extends IService<question> {
|
||||
|
||||
@@ -50,4 +51,11 @@ public interface adminService extends IService<question> {
|
||||
* 更新题目
|
||||
*/
|
||||
boolean updateQuestion(question q);
|
||||
|
||||
/**
|
||||
* 批量导入题目
|
||||
* @param questions 题目数据列表
|
||||
* @return 成功导入的题目数量
|
||||
*/
|
||||
int batchImportQuestions(List<Map<String, Object>> questions);
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ import com.baobaot.exam.mapper.questionMapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class adminServiceImpl extends ServiceImpl<questionMapper, question> implements adminService {
|
||||
@@ -33,7 +35,6 @@ public class adminServiceImpl extends ServiceImpl<questionMapper, question> impl
|
||||
question q = question.builder()
|
||||
.title(title)
|
||||
.type(type)
|
||||
.answer(answer)
|
||||
.rAnswer(r_answer)
|
||||
.score(score)
|
||||
.build();
|
||||
@@ -84,4 +85,39 @@ public class adminServiceImpl extends ServiceImpl<questionMapper, question> impl
|
||||
public boolean updateQuestion(question q) {
|
||||
return updateById(q);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int batchImportQuestions(List<Map<String, Object>> questions) {
|
||||
int importedCount = 0;
|
||||
|
||||
for (Map<String, Object> qData : questions) {
|
||||
String title = (String) qData.get("title");
|
||||
Integer type = ((Number) qData.get("type")).intValue();
|
||||
Integer score = ((Number) qData.get("score")).intValue();
|
||||
|
||||
question.questionBuilder builder = question.builder()
|
||||
.title(title)
|
||||
.type(type)
|
||||
.score(score);
|
||||
|
||||
if (type == 1) {
|
||||
// 选择题
|
||||
builder.answerA((String) qData.get("answerA"))
|
||||
.answerB((String) qData.get("answerB"))
|
||||
.answerC((String) qData.get("answerC"))
|
||||
.answerD((String) qData.get("answerD"))
|
||||
.rAnswer((String) qData.get("rAnswer"));
|
||||
} else {
|
||||
// 简答题
|
||||
builder.rAnswer((String) qData.get("rAnswer"));
|
||||
}
|
||||
|
||||
if (save(builder.build())) {
|
||||
importedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return importedCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ public class question {
|
||||
private Integer id;
|
||||
private String title;
|
||||
private Integer type;
|
||||
private String answer;
|
||||
private String answerA;
|
||||
private String answerB;
|
||||
private String answerC;
|
||||
|
||||
@@ -304,6 +304,7 @@
|
||||
</div>
|
||||
<div style="margin-top: 16px; display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<button type="button" class="btn btn-primary" onclick="loadAllQuestions()">显示全部</button>
|
||||
<button type="button" class="btn btn-success" onclick="openBatchImportModal()">批量导入</button>
|
||||
<a th:href="@{/admin/mkexam}" class="btn btn-success">创建试卷</a>
|
||||
<a th:href="@{/admin/listexam}" class="btn btn-success">查看试卷</a>
|
||||
<a th:href="@{/admin/correct}" class="btn btn-success" style="background: #f59e0b;">批改试卷</a>
|
||||
@@ -502,7 +503,6 @@
|
||||
<th width="60">ID</th>
|
||||
<th>题目</th>
|
||||
<th width="100">类型</th>
|
||||
<th>选项/学生答案</th>
|
||||
<th>正确答案</th>
|
||||
<th width="80">分值</th>
|
||||
<th width="150">操作</th>
|
||||
@@ -525,7 +525,7 @@
|
||||
if (q.answerD) parts.push(`D: ${escapeHtml(q.answerD)}`);
|
||||
optionsHtml = parts.join('<br>');
|
||||
} else {
|
||||
optionsHtml = q.answer ? escapeHtml(q.answer) : '无答案';
|
||||
optionsHtml = '<span style="color: #999;">-</span>';
|
||||
}
|
||||
|
||||
const rAnswerHtml = q.rAnswer ?
|
||||
@@ -630,6 +630,101 @@
|
||||
let currentEditId = null;
|
||||
let currentEditType = null;
|
||||
|
||||
// 打开批量导入弹窗
|
||||
function openBatchImportModal() {
|
||||
document.getElementById('batchImportModal').style.display = 'block';
|
||||
document.getElementById('batchImportJson').value = '';
|
||||
document.getElementById('batchImportResult').style.display = 'none';
|
||||
}
|
||||
|
||||
// 关闭批量导入弹窗
|
||||
function closeBatchImportModal() {
|
||||
document.getElementById('batchImportModal').style.display = 'none';
|
||||
document.getElementById('batchImportJson').value = '';
|
||||
document.getElementById('batchImportResult').style.display = 'none';
|
||||
}
|
||||
|
||||
// 提交批量导入
|
||||
function submitBatchImport() {
|
||||
const jsonText = document.getElementById('batchImportJson').value.trim();
|
||||
const resultDiv = document.getElementById('batchImportResult');
|
||||
|
||||
if (!jsonText) {
|
||||
resultDiv.innerHTML = '<div class="alert" style="background: #fee; color: #c33;">请输入JSON数据</div>';
|
||||
resultDiv.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
let questions;
|
||||
try {
|
||||
questions = JSON.parse(jsonText);
|
||||
} catch (e) {
|
||||
resultDiv.innerHTML = '<div class="alert" style="background: #fee; color: #c33;">JSON格式错误: ' + e.message + '</div>';
|
||||
resultDiv.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(questions)) {
|
||||
resultDiv.innerHTML = '<div class="alert" style="background: #fee; color: #c33;">数据必须是JSON数组格式</div>';
|
||||
resultDiv.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
if (questions.length === 0) {
|
||||
resultDiv.innerHTML = '<div class="alert" style="background: #fee; color: #c33;">题目数组不能为空</div>';
|
||||
resultDiv.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证每道题的必填字段
|
||||
for (let i = 0; i < questions.length; i++) {
|
||||
const q = questions[i];
|
||||
if (!q.title || q.title.trim() === '') {
|
||||
resultDiv.innerHTML = '<div class="alert" style="background: #fee; color: #c33;">第 ' + (i + 1) + ' 题缺少题目内容(title)</div>';
|
||||
resultDiv.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
if (!q.type || (q.type !== 1 && q.type !== 2)) {
|
||||
resultDiv.innerHTML = '<div class="alert" style="background: #fee; color: #c33;">第 ' + (i + 1) + ' 题类型错误,type必须是1(选择题)或2(简答题)</div>';
|
||||
resultDiv.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
if (!q.score || q.score < 0) {
|
||||
resultDiv.innerHTML = '<div class="alert" style="background: #fee; color: #c33;">第 ' + (i + 1) + ' 题分值错误</div>';
|
||||
resultDiv.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
fetch('/admin/api/questions/batch-import', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(questions)
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
resultDiv.innerHTML = '<div class="alert" style="background: #efe; color: #2e7d32;">导入成功!共导入 ' + data.importedCount + ' 道题目</div>';
|
||||
resultDiv.style.display = 'block';
|
||||
loadAllQuestions();
|
||||
setTimeout(() => {
|
||||
closeBatchImportModal();
|
||||
}, 1500);
|
||||
} else {
|
||||
resultDiv.innerHTML = '<div class="alert" style="background: #fee; color: #c33;">导入失败: ' + (data.message || '未知错误') + '</div>';
|
||||
resultDiv.style.display = 'block';
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
resultDiv.innerHTML = '<div class="alert" style="background: #fee; color: #c33;">导入失败,请检查网络</div>';
|
||||
resultDiv.style.display = 'block';
|
||||
});
|
||||
}
|
||||
|
||||
// 打开编辑弹窗
|
||||
function editQuestion(id, type) {
|
||||
currentEditId = id;
|
||||
@@ -676,7 +771,6 @@
|
||||
document.getElementById('editEssayForm').style.display = 'block';
|
||||
document.getElementById('editTypeLabel').textContent = '简答题';
|
||||
|
||||
document.getElementById('editAnswer').value = q.answer || '';
|
||||
document.getElementById('editRAnswerEssay').value = q.rAnswer || '';
|
||||
}
|
||||
}
|
||||
@@ -705,7 +799,6 @@
|
||||
formData.append('answerD', document.getElementById('editAnswerD').value);
|
||||
formData.append('rAnswer', document.getElementById('editRAnswer').value);
|
||||
} else {
|
||||
formData.append('answer', document.getElementById('editAnswer').value);
|
||||
formData.append('rAnswer', document.getElementById('editRAnswerEssay').value);
|
||||
}
|
||||
|
||||
@@ -756,8 +849,7 @@
|
||||
if (answerD) parts.push(`D: ${escapeHtml(answerD)}`);
|
||||
optionsHtml = parts.join('<br>');
|
||||
} else {
|
||||
const answer = formData.get('answer');
|
||||
optionsHtml = answer ? escapeHtml(answer) : '无答案';
|
||||
optionsHtml = '<span style="color: #999;">-</span>';
|
||||
}
|
||||
|
||||
const rAnswerHtml = rAnswer ?
|
||||
@@ -784,6 +876,49 @@
|
||||
|
||||
</script>
|
||||
|
||||
<!-- 批量导入弹窗 -->
|
||||
<div id="batchImportModal" class="modal">
|
||||
<div class="modal-content" style="max-width: 700px; max-height: 90vh; overflow-y: auto;">
|
||||
<div class="modal-header">
|
||||
<h2>批量导入题目</h2>
|
||||
<span class="close-btn" onclick="closeBatchImportModal()">×</span>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info" style="margin-bottom: 16px;">
|
||||
<strong>格式说明:</strong>JSON数组格式,每道题包含 title(必填), type(1=选择题,2=简答题), score(分值) 等字段
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>JSON数据 <span class="required">*</span></label>
|
||||
<textarea id="batchImportJson" rows="15" placeholder='[
|
||||
{
|
||||
"title": "选择题示例",
|
||||
"type": 1,
|
||||
"answerA": "选项A",
|
||||
"answerB": "选项B",
|
||||
"answerC": "选项C",
|
||||
"answerD": "选项D",
|
||||
"rAnswer": "A",
|
||||
"score": 5
|
||||
},
|
||||
{
|
||||
"title": "简答题示例",
|
||||
"type": 2,
|
||||
"rAnswer": "参考答案",
|
||||
"score": 10
|
||||
}
|
||||
]' style="font-family: monospace; font-size: 13px;"></textarea>
|
||||
</div>
|
||||
|
||||
<div id="batchImportResult" style="display: none; margin-bottom: 16px;"></div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeBatchImportModal()">取消</button>
|
||||
<button type="button" class="btn btn-primary" onclick="submitBatchImport()">确认导入</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<div id="editModal" class="modal">
|
||||
<div class="modal-content" style="max-width: 600px; max-height: 90vh; overflow-y: auto;">
|
||||
@@ -834,10 +969,10 @@
|
||||
|
||||
<!-- 简答题表单 -->
|
||||
<div id="editEssayForm" style="display: none;">
|
||||
<div class="form-group">
|
||||
<!-- <div class="form-group">
|
||||
<label>学生答案参考</label>
|
||||
<textarea id="editAnswer" rows="2"></textarea>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="form-group">
|
||||
<label>参考答案</label>
|
||||
<textarea id="editRAnswerEssay" rows="2"></textarea>
|
||||
|
||||
44
test_import.json
Normal file
44
test_import.json
Normal file
@@ -0,0 +1,44 @@
|
||||
[
|
||||
{
|
||||
"title": "Java中,以下哪个关键字用于定义接口?",
|
||||
"type": 1,
|
||||
"answerA": "class",
|
||||
"answerB": "interface",
|
||||
"answerC": "extends",
|
||||
"answerD": "implements",
|
||||
"rAnswer": "B",
|
||||
"score": 5
|
||||
},
|
||||
{
|
||||
"title": "Spring框架的核心特性是什么?",
|
||||
"type": 1,
|
||||
"answerA": "AOP面向切面编程",
|
||||
"answerB": "IOC控制反转",
|
||||
"answerC": "MVC设计模式",
|
||||
"answerD": "以上都是",
|
||||
"rAnswer": "D",
|
||||
"score": 5
|
||||
},
|
||||
{
|
||||
"title": "简述Java中Exception和Error的区别。",
|
||||
"type": 2,
|
||||
"rAnswer": "Exception是程序可以处理的异常,通常由程序逻辑错误引起;Error是严重错误,如内存溢出,程序无法处理。",
|
||||
"score": 10
|
||||
},
|
||||
{
|
||||
"title": "MySQL中,如何优化慢查询?",
|
||||
"type": 2,
|
||||
"rAnswer": "1. 添加索引;2. 优化SQL语句;3. 使用EXPLAIN分析查询计划;4. 避免全表扫描;5. 适当分表分库。",
|
||||
"score": 15
|
||||
},
|
||||
{
|
||||
"title": "以下哪个不是Java的基本数据类型?",
|
||||
"type": 1,
|
||||
"answerA": "int",
|
||||
"answerB": "String",
|
||||
"answerC": "boolean",
|
||||
"answerD": "double",
|
||||
"rAnswer": "B",
|
||||
"score": 5
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user