fix: 添加难度选项 #2

This commit is contained in:
Yakumo Hokori
2026-03-07 20:37:15 +08:00
parent b53743a68b
commit e36c84b195
11 changed files with 332 additions and 40 deletions

View File

@@ -50,19 +50,20 @@ public class AdminController {
return ResponseEntity.ok(result); return ResponseEntity.ok(result);
} }
// API: 获取题目列表(支持全部、按类型、按分值、按标题模糊查询、联合查询) // API: 获取题目列表(支持全部、按类型、按分值、按标题模糊查询、联合查询、按难度查询
@GetMapping("/admin/api/questions") @GetMapping("/admin/api/questions")
@ResponseBody @ResponseBody
public ResponseEntity<Map<String, Object>> getQuestions( public ResponseEntity<Map<String, Object>> getQuestions(
@RequestParam(required = false) Integer type, @RequestParam(required = false) Integer type,
@RequestParam(required = false) Integer score, @RequestParam(required = false) Integer score,
@RequestParam(required = false) Integer level,
@RequestParam(required = false) String title) { @RequestParam(required = false) String title) {
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
List<question> questions; List<question> questions;
if (type != null || title != null) { if (type != null || level != null || title != null) {
// 联合查询:按类型和/或标题 // 联合查询:按类型、难度和/或标题
questions = adminService.getQuestionsByTypeAndTitle(type, title); questions = adminService.getQuestionsByConditions(type, level, title);
} else if (score != null) { } else if (score != null) {
questions = adminService.getQuestionsByScore(score); questions = adminService.getQuestionsByScore(score);
} else { } else {
@@ -83,10 +84,11 @@ public class AdminController {
@RequestParam(required = false) String answer_c, @RequestParam(required = false) String answer_c,
@RequestParam(required = false) String answer_d, @RequestParam(required = false) String answer_d,
@RequestParam String r_answer, @RequestParam String r_answer,
@RequestParam Integer level,
@RequestParam Integer score) { @RequestParam Integer score) {
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
try { try {
adminService.addChoiceQuestion(title, type, answer_a, answer_b, answer_c, answer_d, r_answer, score); adminService.addChoiceQuestion(title, type, answer_a, answer_b, answer_c, answer_d, r_answer, level, score);
result.put("success", true); result.put("success", true);
result.put("message", "添加成功"); result.put("message", "添加成功");
} catch (Exception e) { } catch (Exception e) {
@@ -100,12 +102,12 @@ public class AdminController {
@ResponseBody @ResponseBody
public ResponseEntity<Map<String, Object>> addEssayQuestion(@RequestParam String title, public ResponseEntity<Map<String, Object>> addEssayQuestion(@RequestParam String title,
@RequestParam Integer type, @RequestParam Integer type,
@RequestParam(required = false) String answer,
@RequestParam(required = false) String r_answer, @RequestParam(required = false) String r_answer,
@RequestParam Integer level,
@RequestParam Integer score) { @RequestParam Integer score) {
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
try { try {
adminService.addEssayQuestion(title, type, answer, r_answer, score); adminService.addEssayQuestion(title, type, r_answer, level, score);
result.put("success", true); result.put("success", true);
result.put("message", "添加成功"); result.put("message", "添加成功");
} catch (Exception e) { } catch (Exception e) {
@@ -137,8 +139,9 @@ public class AdminController {
@PostMapping("/admin/mkexam/create") @PostMapping("/admin/mkexam/create")
@ResponseBody @ResponseBody
public examPaper createExam(@RequestParam(required = false) String title) { public examPaper createExam(@RequestParam(required = false) String title,
return makeExamService.createExamPaper(title); @RequestParam(required = false, defaultValue = "1") Integer level) {
return makeExamService.createExamPaper(title, level);
} }
@GetMapping("/admin/mkexam/detail/{uid}") @GetMapping("/admin/mkexam/detail/{uid}")
@@ -191,6 +194,7 @@ public class AdminController {
@RequestParam(required = false) String answerC, @RequestParam(required = false) String answerC,
@RequestParam(required = false) String answerD, @RequestParam(required = false) String answerD,
@RequestParam(required = false) String rAnswer, @RequestParam(required = false) String rAnswer,
@RequestParam Integer level,
@RequestParam Integer score) { @RequestParam Integer score) {
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
try { try {
@@ -205,6 +209,7 @@ public class AdminController {
.id(id) .id(id)
.type(type) .type(type)
.title(title) .title(title)
.level(level)
.score(score) .score(score)
.rAnswer(rAnswer); .rAnswer(rAnswer);

View File

@@ -12,12 +12,12 @@ public interface adminService extends IService<question> {
* 添加选择题type=1 * 添加选择题type=1
*/ */
boolean addChoiceQuestion(String title, Integer type, String answer_a, String answer_b, boolean addChoiceQuestion(String title, Integer type, String answer_a, String answer_b,
String answer_c, String answer_d, String r_answer, Integer score); String answer_c, String answer_d, String r_answer, Integer level, Integer score);
/** /**
* 添加简答题type=2 * 添加简答题type=2
*/ */
boolean addEssayQuestion(String title, Integer type, String answer, String r_answer, Integer score); boolean addEssayQuestion(String title, Integer type, String r_answer, Integer level, Integer score);
/** /**
* 根据类型查询所有题目 * 根据类型查询所有题目
@@ -32,6 +32,15 @@ public interface adminService extends IService<question> {
*/ */
List<question> getQuestionsByTypeAndTitle(Integer type, String title); List<question> getQuestionsByTypeAndTitle(Integer type, String title);
/**
* 根据条件查询题目(类型、难度、标题联合查询)
* @param type 题目类型可为null
* @param level 题目难度可为null
* @param title 标题关键词可为null支持模糊匹配
* @return 符合条件的题目列表
*/
List<question> getQuestionsByConditions(Integer type, Integer level, String title);
/** /**
* 根据分数查询所有题目 * 根据分数查询所有题目
*/ */

View File

@@ -16,7 +16,7 @@ public class adminServiceImpl extends ServiceImpl<questionMapper, question> impl
@Override @Override
public boolean addChoiceQuestion(String title, Integer type, String answer_a, String answer_b, public boolean addChoiceQuestion(String title, Integer type, String answer_a, String answer_b,
String answer_c, String answer_d, String r_answer, Integer score) { String answer_c, String answer_d, String r_answer, Integer level, Integer score) {
question q = question.builder() question q = question.builder()
.title(title) .title(title)
.type(type) .type(type)
@@ -25,17 +25,19 @@ public class adminServiceImpl extends ServiceImpl<questionMapper, question> impl
.answerC(answer_c) .answerC(answer_c)
.answerD(answer_d) .answerD(answer_d)
.rAnswer(r_answer) .rAnswer(r_answer)
.level(level)
.score(score) .score(score)
.build(); .build();
return save(q); return save(q);
} }
@Override @Override
public boolean addEssayQuestion(String title, Integer type, String answer, String r_answer, Integer score) { public boolean addEssayQuestion(String title, Integer type, String r_answer, Integer level, Integer score) {
question q = question.builder() question q = question.builder()
.title(title) .title(title)
.type(type) .type(type)
.rAnswer(r_answer) .rAnswer(r_answer)
.level(level)
.score(score) .score(score)
.build(); .build();
return save(q); return save(q);
@@ -62,6 +64,23 @@ public class adminServiceImpl extends ServiceImpl<questionMapper, question> impl
return list(wrapper); return list(wrapper);
} }
@Override
public List<question> getQuestionsByConditions(Integer type, Integer level, String title) {
LambdaQueryWrapper<question> wrapper = new LambdaQueryWrapper<>();
if (type != null) {
wrapper.eq(question::getType, type);
}
if (level != null) {
wrapper.eq(question::getLevel, level);
}
if (title != null && !title.trim().isEmpty()) {
wrapper.like(question::getTitle, title.trim());
}
return list(wrapper);
}
@Override @Override
public List<question> getQuestionsByScore(Integer score) { public List<question> getQuestionsByScore(Integer score) {
LambdaQueryWrapper<question> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<question> wrapper = new LambdaQueryWrapper<>();
@@ -95,10 +114,12 @@ public class adminServiceImpl extends ServiceImpl<questionMapper, question> impl
String title = (String) qData.get("title"); String title = (String) qData.get("title");
Integer type = ((Number) qData.get("type")).intValue(); Integer type = ((Number) qData.get("type")).intValue();
Integer score = ((Number) qData.get("score")).intValue(); Integer score = ((Number) qData.get("score")).intValue();
Integer level = qData.get("level") != null ? ((Number) qData.get("level")).intValue() : 1;
question.questionBuilder builder = question.builder() question.questionBuilder builder = question.builder()
.title(title) .title(title)
.type(type) .type(type)
.level(level)
.score(score); .score(score);
if (type == 1) { if (type == 1) {

View File

@@ -30,12 +30,20 @@ public class makeExamServiceImpl extends ServiceImpl<examPaperMapper, examPaper>
@Override @Override
public examPaper createExamPaper(String title) { public examPaper createExamPaper(String title) {
return createExamPaper(title, 1);
}
@Override
public examPaper createExamPaper(String title, Integer level) {
// 生成唯一UID // 生成唯一UID
String uid = UidGenerator.generateUniqueUid(this::isUidExists); String uid = UidGenerator.generateUniqueUid(this::isUidExists);
// 获取所有题目 // 根据难度获取题目
List<question> allChoiceQuestions = getQuestionsByType(1); // level=1: 只选level=1的题目
List<question> allEssayQuestions = getQuestionsByType(2); // level=2: 选level=1或2的题目
// level=3: 选level=1、2、3的题目
List<question> allChoiceQuestions = getQuestionsByTypeAndLevel(1, level);
List<question> allEssayQuestions = getQuestionsByTypeAndLevel(2, level);
if (allChoiceQuestions.size() < 5 || allEssayQuestions.size() < 5) { if (allChoiceQuestions.size() < 5 || allEssayQuestions.size() < 5) {
throw new RuntimeException("题库题目不足至少需要5道选择题和5道简答题"); throw new RuntimeException("题库题目不足至少需要5道选择题和5道简答题");
@@ -75,6 +83,7 @@ public class makeExamServiceImpl extends ServiceImpl<examPaperMapper, examPaper>
examPaper paper = examPaper.builder() examPaper paper = examPaper.builder()
.uid(uid) .uid(uid)
.title(title) .title(title)
.level(level)
.select(selectIds) .select(selectIds)
.content(contentIds) .content(contentIds)
.build(); .build();
@@ -103,6 +112,16 @@ public class makeExamServiceImpl extends ServiceImpl<examPaperMapper, examPaper>
return questionMapper.selectList(wrapper); return questionMapper.selectList(wrapper);
} }
private List<question> getQuestionsByTypeAndLevel(Integer type, Integer level) {
LambdaQueryWrapper<question> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(question::getType, type);
// level=1: 只选level<=1的题目
// level=2: 选level<=2的题目
// level=3: 选level<=3的题目
wrapper.le(question::getLevel, level);
return questionMapper.selectList(wrapper);
}
/** /**
* 寻找精确100分的组合 * 寻找精确100分的组合
*/ */
@@ -289,6 +308,7 @@ public class makeExamServiceImpl extends ServiceImpl<examPaperMapper, examPaper>
result.put("uid", paper.getUid()); result.put("uid", paper.getUid());
result.put("title", paper.getTitle()); result.put("title", paper.getTitle());
result.put("level", paper.getLevel());
// 解析选择题ID列表 // 解析选择题ID列表
List<Integer> choiceIds = parseIdList(paper.getSelect()); List<Integer> choiceIds = parseIdList(paper.getSelect());

View File

@@ -11,6 +11,13 @@ public interface makeExamService {
/** /**
* 创建试卷 * 创建试卷
* 自动生成uid按7:3比例选择题和简答题总分100分 * 自动生成uid按7:3比例选择题和简答题总分100分
* @param title 试卷标题
* @param level 试卷难度1=低级只选1级题2=中级选1-2级题3=高级选1-3级题
*/
examPaper createExamPaper(String title, Integer level);
/**
* 创建试卷默认难度1
*/ */
examPaper createExamPaper(String title); examPaper createExamPaper(String title);

View File

@@ -15,6 +15,7 @@ public class examPaper {
private Integer id; private Integer id;
private String uid; private String uid;
private String title; private String title;
private Integer level;
@TableField("`select`") @TableField("`select`")
private String select; private String select;

View File

@@ -15,6 +15,7 @@ public class question {
private Integer id; private Integer id;
private String title; private String title;
private Integer type; private Integer type;
private Integer level;
private String answerA; private String answerA;
private String answerB; private String answerB;
private String answerC; private String answerC;

View File

@@ -146,6 +146,18 @@
background: #fef9c3; background: #fef9c3;
color: #a16207; color: #a16207;
} }
.tag-level-1 {
background: #dcfce7;
color: #15803d;
}
.tag-level-2 {
background: #dbeafe;
color: #1d4ed8;
}
.tag-level-3 {
background: #fce7f3;
color: #be185d;
}
.score-badge { .score-badge {
background: #f59e0b; background: #f59e0b;
color: white; color: white;
@@ -247,9 +259,19 @@
<option value="D">D</option> <option value="D">D</option>
</select> </select>
</div> </div>
<div class="form-group"> <div class="grid-2">
<label>分值</label> <div class="form-group">
<input type="number" name="score" id="choiceScore" placeholder="请输入分值" required min="0"> <label>难度<span class="required">*</span></label>
<select name="level" id="choiceLevel" required>
<option value="1">低级</option>
<option value="2">中级</option>
<option value="3">高级</option>
</select>
</div>
<div class="form-group">
<label>分值</label>
<input type="number" name="score" id="choiceScore" placeholder="请输入分值" required min="0">
</div>
</div> </div>
<button type="submit" class="btn btn-primary">添加选择题</button> <button type="submit" class="btn btn-primary">添加选择题</button>
</form> </form>
@@ -267,9 +289,19 @@
<label>参考答案</label> <label>参考答案</label>
<textarea name="r_answer" id="essayRAnswer" placeholder="选填"></textarea> <textarea name="r_answer" id="essayRAnswer" placeholder="选填"></textarea>
</div> </div>
<div class="form-group"> <div class="grid-2">
<label>分值</label> <div class="form-group">
<input type="number" name="score" id="essayScore" placeholder="请输入分值" required min="0"> <label>难度<span class="required">*</span></label>
<select name="level" id="essayLevel" required>
<option value="1">低级</option>
<option value="2">中级</option>
<option value="3">高级</option>
</select>
</div>
<div class="form-group">
<label>分值</label>
<input type="number" name="score" id="essayScore" placeholder="请输入分值" required min="0">
</div>
</div> </div>
<button type="submit" class="btn btn-primary">添加简答题</button> <button type="submit" class="btn btn-primary">添加简答题</button>
</form> </form>
@@ -290,6 +322,15 @@
<option value="2">简答题</option> <option value="2">简答题</option>
</select> </select>
</div> </div>
<div class="form-group">
<label>难度</label>
<select id="queryLevel">
<option value="">全部难度</option>
<option value="1">低级</option>
<option value="2">中级</option>
<option value="3">高级</option>
</select>
</div>
</div> </div>
</div> </div>
<div> <div>
@@ -403,6 +444,7 @@
document.getElementById('queryScoreTag').style.display = 'none'; document.getElementById('queryScoreTag').style.display = 'none';
document.getElementById('queryTitleTag').style.display = 'none'; document.getElementById('queryTitleTag').style.display = 'none';
document.getElementById('queryType').value = ''; document.getElementById('queryType').value = '';
document.getElementById('queryLevel').value = '';
document.getElementById('queryTitle').value = ''; document.getElementById('queryTitle').value = '';
fetchQuestions('/admin/api/questions'); fetchQuestions('/admin/api/questions');
} }
@@ -410,10 +452,11 @@
// 联合查询:按类型和/或标题 // 联合查询:按类型和/或标题
function queryQuestions() { function queryQuestions() {
const type = document.getElementById('queryType').value; const type = document.getElementById('queryType').value;
const level = document.getElementById('queryLevel').value;
const title = document.getElementById('queryTitle').value.trim(); const title = document.getElementById('queryTitle').value.trim();
// 如果类型和标题都为空,查询全部 // 如果类型、难度和标题都为空,查询全部
if (!type && !title) { if (!type && !level && !title) {
loadAllQuestions(); loadAllQuestions();
return; return;
} }
@@ -440,6 +483,7 @@
let url = '/admin/api/questions?'; let url = '/admin/api/questions?';
const params = []; const params = [];
if (type) params.push('type=' + encodeURIComponent(type)); if (type) params.push('type=' + encodeURIComponent(type));
if (level) params.push('level=' + encodeURIComponent(level));
if (title) params.push('title=' + encodeURIComponent(title)); if (title) params.push('title=' + encodeURIComponent(title));
url += params.join('&'); url += params.join('&');
@@ -502,7 +546,8 @@
<tr> <tr>
<th width="60">ID</th> <th width="60">ID</th>
<th>题目</th> <th>题目</th>
<th width="100">类型</th> <th width="80">类型</th>
<th width="80">难度</th>
<th>正确答案</th> <th>正确答案</th>
<th width="80">分值</th> <th width="80">分值</th>
<th width="150">操作</th> <th width="150">操作</th>
@@ -516,6 +561,10 @@
'<span class="tag tag-choice">选择题</span>' : '<span class="tag tag-choice">选择题</span>' :
'<span class="tag tag-essay">简答题</span>'; '<span class="tag tag-essay">简答题</span>';
const levelMap = {1: '低级', 2: '中级', 3: '高级'};
const levelClassMap = {1: 'tag-level-1', 2: 'tag-level-2', 3: 'tag-level-3'};
const levelTag = `<span class="tag ${levelClassMap[q.level] || 'tag-level-1'}">${levelMap[q.level] || '低级'}</span>`;
let optionsHtml = ''; let optionsHtml = '';
if (q.type == 1) { if (q.type == 1) {
const parts = []; const parts = [];
@@ -537,6 +586,7 @@
<td>${q.id}</td> <td>${q.id}</td>
<td style="max-width: 300px; word-wrap: break-word;">${escapeHtml(q.title)}</td> <td style="max-width: 300px; word-wrap: break-word;">${escapeHtml(q.title)}</td>
<td>${typeTag}</td> <td>${typeTag}</td>
<td>${levelTag}</td>
<td>${optionsHtml}</td> <td>${optionsHtml}</td>
<td>${rAnswerHtml}</td> <td>${rAnswerHtml}</td>
<td><span class="score-badge">${q.score}</span></td> <td><span class="score-badge">${q.score}</span></td>
@@ -689,6 +739,11 @@
resultDiv.style.display = 'block'; resultDiv.style.display = 'block';
return; return;
} }
if (q.level && (q.level < 1 || q.level > 3)) {
resultDiv.innerHTML = '<div class="alert" style="background: #fee; color: #c33;">第 ' + (i + 1) + ' 题难度错误level必须是1(低级)、2(中级)或3(高级)</div>';
resultDiv.style.display = 'block';
return;
}
if (!q.score || q.score < 0) { if (!q.score || q.score < 0) {
resultDiv.innerHTML = '<div class="alert" style="background: #fee; color: #c33;">第 ' + (i + 1) + ' 题分值错误</div>'; resultDiv.innerHTML = '<div class="alert" style="background: #fee; color: #c33;">第 ' + (i + 1) + ' 题分值错误</div>';
resultDiv.style.display = 'block'; resultDiv.style.display = 'block';
@@ -753,6 +808,7 @@
document.getElementById('editType').value = q.type; document.getElementById('editType').value = q.type;
document.getElementById('editTitle').value = q.title || ''; document.getElementById('editTitle').value = q.title || '';
document.getElementById('editScore').value = q.score || ''; document.getElementById('editScore').value = q.score || '';
document.getElementById('editLevel').value = q.level || 1;
if (q.type == 1) { if (q.type == 1) {
// 选择题表单 // 选择题表单
@@ -791,6 +847,7 @@
formData.append('type', currentEditType); formData.append('type', currentEditType);
formData.append('title', document.getElementById('editTitle').value); formData.append('title', document.getElementById('editTitle').value);
formData.append('score', document.getElementById('editScore').value); formData.append('score', document.getElementById('editScore').value);
formData.append('level', document.getElementById('editLevel').value);
if (currentEditType == 1) { if (currentEditType == 1) {
formData.append('answerA', document.getElementById('editAnswerA').value); formData.append('answerA', document.getElementById('editAnswerA').value);
@@ -831,11 +888,16 @@
const title = formData.get('title'); const title = formData.get('title');
const score = formData.get('score'); const score = formData.get('score');
const rAnswer = formData.get('rAnswer'); const rAnswer = formData.get('rAnswer');
const level = formData.get('level') || 1;
const typeTag = type == 1 ? const typeTag = type == 1 ?
'<span class="tag tag-choice">选择题</span>' : '<span class="tag tag-choice">选择题</span>' :
'<span class="tag tag-essay">简答题</span>'; '<span class="tag tag-essay">简答题</span>';
const levelMap = {1: '低级', 2: '中级', 3: '高级'};
const levelClassMap = {1: 'tag-level-1', 2: 'tag-level-2', 3: 'tag-level-3'};
const levelTag = `<span class="tag ${levelClassMap[level] || 'tag-level-1'}">${levelMap[level] || '低级'}</span>`;
let optionsHtml = ''; let optionsHtml = '';
if (type == 1) { if (type == 1) {
const answerA = formData.get('answerA'); const answerA = formData.get('answerA');
@@ -861,6 +923,7 @@
<td>${id}</td> <td>${id}</td>
<td style="max-width: 300px; word-wrap: break-word;">${escapeHtml(title)}</td> <td style="max-width: 300px; word-wrap: break-word;">${escapeHtml(title)}</td>
<td>${typeTag}</td> <td>${typeTag}</td>
<td>${levelTag}</td>
<td>${optionsHtml}</td> <td>${optionsHtml}</td>
<td>${rAnswerHtml}</td> <td>${rAnswerHtml}</td>
<td><span class="score-badge">${score}</span></td> <td><span class="score-badge">${score}</span></td>
@@ -885,7 +948,7 @@
</div> </div>
<div class="alert alert-info" style="margin-bottom: 16px;"> <div class="alert alert-info" style="margin-bottom: 16px;">
<strong>格式说明:</strong>JSON数组格式每道题包含 title(必填), type(1=选择题,2=简答题), score(分值) 等字段 <strong>格式说明:</strong>JSON数组格式每道题包含 title(必填), type(1=选择题,2=简答题), level(1=低级,2=中级,3=高级), score(分值) 等字段
</div> </div>
<div class="form-group"> <div class="form-group">
@@ -894,6 +957,7 @@
{ {
"title": "选择题示例", "title": "选择题示例",
"type": 1, "type": 1,
"level": 1,
"answerA": "选项A", "answerA": "选项A",
"answerB": "选项B", "answerB": "选项B",
"answerC": "选项C", "answerC": "选项C",
@@ -904,6 +968,7 @@
{ {
"title": "简答题示例", "title": "简答题示例",
"type": 2, "type": 2,
"level": 2,
"rAnswer": "参考答案", "rAnswer": "参考答案",
"score": 10 "score": 10
} }
@@ -979,9 +1044,19 @@
</div> </div>
</div> </div>
<div class="form-group"> <div class="grid-2">
<label>分值 <span class="required">*</span></label> <div class="form-group">
<input type="number" id="editScore" required min="0"> <label>难度 <span class="required">*</span></label>
<select id="editLevel" required>
<option value="1">低级</option>
<option value="2">中级</option>
<option value="3">高级</option>
</select>
</div>
<div class="form-group">
<label>分值 <span class="required">*</span></label>
<input type="number" id="editScore" required min="0">
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">

View File

@@ -220,6 +220,26 @@
padding: 8px 12px; padding: 8px 12px;
border-radius: 6px; border-radius: 6px;
} }
.tag-level-1 {
background: #dcfce7;
color: #15803d;
}
.tag-level-2 {
background: #dbeafe;
color: #1d4ed8;
}
.tag-level-1 {
background: #dcfce7;
color: #15803d;
}
.tag-level-2 {
background: #dbeafe;
color: #1d4ed8;
}
.tag-level-3 {
background: #fce7f3;
color: #be185d;
}
.action-btns { .action-btns {
display: flex; display: flex;
gap: 8px; gap: 8px;
@@ -268,6 +288,8 @@
<div class="exam-meta"> <div class="exam-meta">
<p><strong>试卷UID</strong> <span id="modalUid" style="font-family: monospace;"></span></p> <p><strong>试卷UID</strong> <span id="modalUid" style="font-family: monospace;"></span></p>
<p style="margin-top: 8px;"> <p style="margin-top: 8px;">
<strong>难度:</strong> <span id="modalLevel" style="font-weight: bold;">-</span>
<span style="margin: 0 10px;">|</span>
<strong>总分:</strong> <span id="modalTotalScore" style="font-size: 18px; font-weight: bold;">0</span> <strong>总分:</strong> <span id="modalTotalScore" style="font-size: 18px; font-weight: bold;">0</span>
<span style="margin: 0 10px;">|</span> <span style="margin: 0 10px;">|</span>
<strong>选择题:</strong> <span id="modalChoiceCount">0</span> <strong>选择题:</strong> <span id="modalChoiceCount">0</span>
@@ -331,6 +353,7 @@
<th width="60">ID</th> <th width="60">ID</th>
<th width="280">UID</th> <th width="280">UID</th>
<th>试卷标题</th> <th>试卷标题</th>
<th width="100">难度</th>
<th width="220">操作</th> <th width="220">操作</th>
</tr> </tr>
</thead> </thead>
@@ -339,11 +362,17 @@
examPapers.forEach(paper => { examPapers.forEach(paper => {
const title = paper.title || '未命名试卷'; const title = paper.title || '未命名试卷';
const level = paper.level || 1;
const levelMap = {1: '低级', 2: '中级', 3: '高级'};
const levelClassMap = {1: 'tag-level-1', 2: 'tag-level-2', 3: 'tag-level-3'};
const levelTag = `<span class="${levelClassMap[level] || 'tag-level-1'}">${levelMap[level] || '低级'}</span>`;
html += ` html += `
<tr> <tr>
<td>${paper.id}</td> <td>${paper.id}</td>
<td style="font-family: monospace; color: #666; font-size: 13px;">${paper.uid}</td> <td style="font-family: monospace; color: #666; font-size: 13px;">${paper.uid}</td>
<td>${escapeHtml(title)}</td> <td>${escapeHtml(title)}</td>
<td>${levelTag}</td>
<td> <td>
<div class="action-btns"> <div class="action-btns">
<button class="btn btn-primary btn-sm" onclick="viewExamDetail('${paper.uid}')">查看内容</button> <button class="btn btn-primary btn-sm" onclick="viewExamDetail('${paper.uid}')">查看内容</button>
@@ -409,8 +438,12 @@
fetch('/admin/mkexam/detail/' + uid) fetch('/admin/mkexam/detail/' + uid)
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
const levelMap = {1: '低级', 2: '中级', 3: '高级'};
const level = data.level || 1;
document.getElementById('modalTitle').textContent = data.title || '未命名试卷'; document.getElementById('modalTitle').textContent = data.title || '未命名试卷';
document.getElementById('modalUid').textContent = data.uid; document.getElementById('modalUid').textContent = data.uid;
document.getElementById('modalLevel').textContent = levelMap[level] || '低级';
document.getElementById('modalTotalScore').textContent = data.totalScore; document.getElementById('modalTotalScore').textContent = data.totalScore;
document.getElementById('modalChoiceCount').textContent = data.choiceCount; document.getElementById('modalChoiceCount').textContent = data.choiceCount;
document.getElementById('modalEssayCount').textContent = data.essayCount; document.getElementById('modalEssayCount').textContent = data.essayCount;

View File

@@ -327,6 +327,14 @@
<label for="examTitle">试卷标题</label> <label for="examTitle">试卷标题</label>
<input type="text" id="examTitle" placeholder="请输入试卷标题(可选)" maxlength="100"> <input type="text" id="examTitle" placeholder="请输入试卷标题(可选)" maxlength="100">
</div> </div>
<div class="form-group">
<label for="examLevel">试卷难度</label>
<select id="examLevel" style="width: 100%; padding: 12px 16px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 14px;">
<option value="1">低级只选1级难度的题目</option>
<option value="2">中级选1-2级难度的题目</option>
<option value="3">高级选1-3级难度的题目</option>
</select>
</div>
<div class="form-actions"> <div class="form-actions">
<button class="btn btn-primary create-btn" onclick="createExam()">创建试卷</button> <button class="btn btn-primary create-btn" onclick="createExam()">创建试卷</button>
</div> </div>
@@ -354,6 +362,9 @@
<button class="copy-btn" onclick="copyUid()">复制</button> <button class="copy-btn" onclick="copyUid()">复制</button>
</div> </div>
<h3 style="margin-top: 16px;">试卷难度</h3>
<div class="exam-title" id="examLevelDisplay" style="background: #e8f5e9; border-left-color: #4caf50;">-</div>
<div class="stats"> <div class="stats">
<div class="stat-item"> <div class="stat-item">
<div class="stat-label">选择题数量</div> <div class="stat-label">选择题数量</div>
@@ -391,8 +402,9 @@
<script> <script>
function createExam() { function createExam() {
// 获取试卷标题 // 获取试卷标题和难度
const title = document.getElementById('examTitle').value.trim(); const title = document.getElementById('examTitle').value.trim();
const level = document.getElementById('examLevel').value;
// 显示加载,隐藏创建按钮 // 显示加载,隐藏创建按钮
document.getElementById('createSection').style.display = 'none'; document.getElementById('createSection').style.display = 'none';
@@ -405,6 +417,7 @@
if (title) { if (title) {
formData.append('title', title); formData.append('title', title);
} }
formData.append('level', level);
fetch('/admin/mkexam/create', { fetch('/admin/mkexam/create', {
method: 'POST', method: 'POST',
@@ -440,6 +453,11 @@
// 显示UID // 显示UID
document.getElementById('uidDisplay').textContent = data.uid; document.getElementById('uidDisplay').textContent = data.uid;
// 显示难度
const levelMap = {1: '低级', 2: '中级', 3: '高级'};
const level = data.level || 1;
document.getElementById('examLevelDisplay').textContent = levelMap[level] || '低级';
// 更新统计 // 更新统计
const choiceQuestions = data.choiceQuestions || []; const choiceQuestions = data.choiceQuestions || [];
const essayQuestions = data.essayQuestions || []; const essayQuestions = data.essayQuestions || [];

View File

@@ -2,6 +2,7 @@
{ {
"title": "Java中以下哪个关键字用于定义接口", "title": "Java中以下哪个关键字用于定义接口",
"type": 1, "type": 1,
"level": 1,
"answerA": "class", "answerA": "class",
"answerB": "interface", "answerB": "interface",
"answerC": "extends", "answerC": "extends",
@@ -9,9 +10,32 @@
"rAnswer": "B", "rAnswer": "B",
"score": 5 "score": 5
}, },
{
"title": "以下哪个不是Java的基本数据类型",
"type": 1,
"level": 1,
"answerA": "int",
"answerB": "String",
"answerC": "boolean",
"answerD": "double",
"rAnswer": "B",
"score": 5
},
{
"title": "Java中main方法的正确写法是",
"type": 1,
"level": 1,
"answerA": "public void main(String[] args)",
"answerB": "public static void main(String[] args)",
"answerC": "public static int main(String[] args)",
"answerD": "static void main(String args)",
"rAnswer": "B",
"score": 5
},
{ {
"title": "Spring框架的核心特性是什么", "title": "Spring框架的核心特性是什么",
"type": 1, "type": 1,
"level": 2,
"answerA": "AOP面向切面编程", "answerA": "AOP面向切面编程",
"answerB": "IOC控制反转", "answerB": "IOC控制反转",
"answerC": "MVC设计模式", "answerC": "MVC设计模式",
@@ -19,26 +43,104 @@
"rAnswer": "D", "rAnswer": "D",
"score": 5 "score": 5
}, },
{
"title": "MySQL中以下哪个关键字用于删除表",
"type": 1,
"level": 2,
"answerA": "DELETE",
"answerB": "DROP",
"answerC": "REMOVE",
"answerD": "CLEAR",
"rAnswer": "B",
"score": 5
},
{
"title": "以下哪个设计模式属于创建型模式?",
"type": 1,
"level": 2,
"answerA": "观察者模式",
"answerB": "单例模式",
"answerC": "策略模式",
"answerD": "装饰器模式",
"rAnswer": "B",
"score": 5
},
{
"title": "Redis支持的数据结构包括",
"type": 1,
"level": 3,
"answerA": "String",
"answerB": "List",
"answerC": "Set",
"answerD": "以上都是",
"rAnswer": "D",
"score": 5
},
{
"title": "Java虚拟机JVM的主要组成部分包括",
"type": 1,
"level": 3,
"answerA": "类加载器",
"answerB": "运行时数据区",
"answerC": "执行引擎",
"answerD": "以上都是",
"rAnswer": "D",
"score": 5
},
{ {
"title": "简述Java中Exception和Error的区别。", "title": "简述Java中Exception和Error的区别。",
"type": 2, "type": 2,
"rAnswer": "Exception是程序可以处理的异常通常由程序逻辑错误引起Error是严重错误如内存溢出程序无法处理。", "level": 1,
"rAnswer": "Exception是程序可以处理的异常通常由程序逻辑错误引起如NullPointerExceptionError是严重错误如OutOfMemoryError程序通常无法处理。",
"score": 10
},
{
"title": "什么是面向对象编程的三大特性?",
"type": 2,
"level": 1,
"rAnswer": "封装:将数据和方法包装在一起;继承:子类继承父类的特性;多态:同一操作作用于不同对象产生不同行为。",
"score": 10
},
{
"title": "简述JDK、JRE和JVM的区别。",
"type": 2,
"level": 2,
"rAnswer": "JDK是Java开发工具包包含JRE和开发工具JRE是Java运行时环境包含JVM和核心类库JVM是Java虚拟机负责字节码的执行。",
"score": 10 "score": 10
}, },
{ {
"title": "MySQL中如何优化慢查询", "title": "MySQL中如何优化慢查询",
"type": 2, "type": 2,
"rAnswer": "1. 添加索引2. 优化SQL语句3. 使用EXPLAIN分析查询计划4. 避免全表扫描5. 适当分表分库。", "level": 2,
"rAnswer": "1. 添加合适的索引2. 优化SQL语句避免全表扫描3. 使用EXPLAIN分析查询计划4. 优化表结构5. 适当进行分表分库。",
"score": 15 "score": 15
}, },
{ {
"title": "以下哪个不是Java的基本数据类型", "title": "解释Spring中的IOC和AOP概念。",
"type": 1, "type": 2,
"answerA": "int", "level": 2,
"answerB": "String", "rAnswer": "IOC控制反转是将对象的创建和管理交给Spring容器降低耦合度AOP面向切面编程是将横切关注点如日志、事务从业务逻辑中分离提高代码复用性。",
"answerC": "boolean", "score": 15
"answerD": "double", },
"rAnswer": "B", {
"score": 5 "title": "什么是线程安全问题?如何解决?",
"type": 2,
"level": 3,
"rAnswer": "线程安全是指多个线程同时访问共享资源时可能出现数据不一致的问题。解决方法包括1. synchronized关键字2. ReentrantLock锁3. 原子类4. ThreadLocal5. 使用线程安全的集合类。",
"score": 15
},
{
"title": "简述分布式事务的解决方案。",
"type": 2,
"level": 3,
"rAnswer": "1. 两阶段提交2PC准备阶段和提交阶段2. 三阶段提交3PC增加了预提交阶段3. TCCTry-Confirm-Cancel4. 本地消息表5. 最大努力通知6. Saga模式。",
"score": 15
},
{
"title": "什么是JVM的垃圾回收机制",
"type": 2,
"level": 3,
"rAnswer": "垃圾回收是JVM自动管理内存的机制主要回收堆内存中不再使用的对象。常用算法包括标记-清除、标记-整理、复制算法、分代收集算法。垃圾收集器有Serial、Parallel、CMS、G1等。",
"score": 15
} }
] ]