修正了修改或删除题目后全局刷新的问题,添加了联合搜索题目
This commit is contained in:
@@ -33,17 +33,19 @@ public class AdminController {
|
||||
return "admin";
|
||||
}
|
||||
|
||||
// API: 获取题目列表(支持全部、按类型、按分值)
|
||||
// API: 获取题目列表(支持全部、按类型、按分值、按标题模糊查询、联合查询)
|
||||
@GetMapping("/admin/api/questions")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Map<String, Object>> getQuestions(
|
||||
@RequestParam(required = false) Integer type,
|
||||
@RequestParam(required = false) Integer score) {
|
||||
@RequestParam(required = false) Integer score,
|
||||
@RequestParam(required = false) String title) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
List<question> questions;
|
||||
|
||||
if (type != null) {
|
||||
questions = adminService.getQuestionsByType(type);
|
||||
if (type != null || title != null) {
|
||||
// 联合查询:按类型和/或标题
|
||||
questions = adminService.getQuestionsByTypeAndTitle(type, title);
|
||||
} else if (score != null) {
|
||||
questions = adminService.getQuestionsByScore(score);
|
||||
} else {
|
||||
|
||||
@@ -23,6 +23,14 @@ public interface adminService extends IService<question> {
|
||||
*/
|
||||
List<question> getQuestionsByType(Integer type);
|
||||
|
||||
/**
|
||||
* 根据类型和/或标题模糊查询题目(联合查询)
|
||||
* @param type 题目类型,可为null
|
||||
* @param title 标题关键词,可为null,支持模糊匹配
|
||||
* @return 符合条件的题目列表
|
||||
*/
|
||||
List<question> getQuestionsByTypeAndTitle(Integer type, String title);
|
||||
|
||||
/**
|
||||
* 根据分数查询所有题目
|
||||
*/
|
||||
|
||||
@@ -47,6 +47,20 @@ public class adminServiceImpl extends ServiceImpl<questionMapper, question> impl
|
||||
return list(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<question> getQuestionsByTypeAndTitle(Integer type, String title) {
|
||||
LambdaQueryWrapper<question> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
if (type != null) {
|
||||
wrapper.eq(question::getType, type);
|
||||
}
|
||||
if (title != null && !title.trim().isEmpty()) {
|
||||
wrapper.like(question::getTitle, title.trim());
|
||||
}
|
||||
|
||||
return list(wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<question> getQuestionsByScore(Integer score) {
|
||||
LambdaQueryWrapper<question> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
@@ -2,8 +2,8 @@ spring:
|
||||
application:
|
||||
name: exam
|
||||
datasource:
|
||||
# url: jdbc:mysql://localhost:3306/exam
|
||||
url: jdbc:mysql://192.168.1.56:3306/exam?useSSL=true&requireSSL=true&serverTimezone=UTC
|
||||
url: jdbc:mysql://localhost:3306/exam
|
||||
# url: jdbc:mysql://192.168.1.56:3306/exam?useSSL=true&requireSSL=true&serverTimezone=UTC
|
||||
username: root
|
||||
password: 521707
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
@@ -283,26 +283,26 @@
|
||||
<div>
|
||||
<div class="query-section">
|
||||
<div class="form-group">
|
||||
<label>按类型查询</label>
|
||||
<select id="queryType" required>
|
||||
<label>题目类型</label>
|
||||
<select id="queryType">
|
||||
<option value="">全部类型</option>
|
||||
<option value="1">选择题</option>
|
||||
<option value="2">简答题</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="button" class="btn btn-success" onclick="queryByType()">查询</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="query-section">
|
||||
<div class="form-group">
|
||||
<label>按分值查询</label>
|
||||
<input type="number" id="queryScore" placeholder="请输入分值" required min="0">
|
||||
<div class="form-group" style="flex: 1;">
|
||||
<label>标题关键词</label>
|
||||
<input type="text" id="queryTitle" placeholder="请输入标题关键词(模糊查询)">
|
||||
</div>
|
||||
<button type="button" class="btn btn-success" onclick="queryByScore()">查询</button>
|
||||
<button type="button" class="btn btn-success" onclick="queryQuestions()" style="margin-bottom: 0; height: fit-content; align-self: flex-end;">查询</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 16px; display: flex; gap: 12px;">
|
||||
<div style="margin-top: 16px; display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<button type="button" class="btn btn-primary" onclick="loadAllQuestions()">显示全部</button>
|
||||
<a th:href="@{/admin/mkexam}" class="btn btn-success">创建试卷</a>
|
||||
<a th:href="@{/admin/listexam}" class="btn btn-success">查看试卷</a>
|
||||
@@ -316,6 +316,7 @@
|
||||
题目列表
|
||||
<span id="queryTypeTag" class="tag tag-choice" style="margin-left: 10px; display: none;"></span>
|
||||
<span id="queryScoreTag" class="tag tag-essay" style="margin-left: 10px; display: none;"></span>
|
||||
<span id="queryTitleTag" class="tag" style="margin-left: 10px; display: none; background: #e8f5e9; color: #2e7d32;"></span>
|
||||
<span id="questionCount" class="tag" style="margin-left: 10px; background: #e3f2fd; color: #1976d2; display: none;">共 0 题</span>
|
||||
</h2>
|
||||
|
||||
@@ -399,31 +400,49 @@
|
||||
function loadAllQuestions() {
|
||||
document.getElementById('queryTypeTag').style.display = 'none';
|
||||
document.getElementById('queryScoreTag').style.display = 'none';
|
||||
document.getElementById('queryTitleTag').style.display = 'none';
|
||||
document.getElementById('queryType').value = '';
|
||||
document.getElementById('queryTitle').value = '';
|
||||
fetchQuestions('/admin/api/questions');
|
||||
}
|
||||
|
||||
// 按类型查询
|
||||
function queryByType() {
|
||||
// 联合查询:按类型和/或标题
|
||||
function queryQuestions() {
|
||||
const type = document.getElementById('queryType').value;
|
||||
const typeTag = document.getElementById('queryTypeTag');
|
||||
typeTag.textContent = type === '1' ? '选择题' : '简答题';
|
||||
typeTag.style.display = 'inline';
|
||||
document.getElementById('queryScoreTag').style.display = 'none';
|
||||
fetchQuestions('/admin/api/questions?type=' + type);
|
||||
}
|
||||
const title = document.getElementById('queryTitle').value.trim();
|
||||
|
||||
// 按分值查询
|
||||
function queryByScore() {
|
||||
const score = document.getElementById('queryScore').value;
|
||||
if (!score && score !== '0') {
|
||||
showAlert('请输入分值', 'error');
|
||||
// 如果类型和标题都为空,查询全部
|
||||
if (!type && !title) {
|
||||
loadAllQuestions();
|
||||
return;
|
||||
}
|
||||
const scoreTag = document.getElementById('queryScoreTag');
|
||||
scoreTag.textContent = score + '分';
|
||||
scoreTag.style.display = 'inline';
|
||||
document.getElementById('queryTypeTag').style.display = 'none';
|
||||
fetchQuestions('/admin/api/questions?score=' + score);
|
||||
|
||||
const typeTag = document.getElementById('queryTypeTag');
|
||||
const titleTag = document.getElementById('queryTitleTag');
|
||||
|
||||
if (type) {
|
||||
typeTag.textContent = type === '1' ? '选择题' : '简答题';
|
||||
typeTag.style.display = 'inline';
|
||||
} else {
|
||||
typeTag.style.display = 'none';
|
||||
}
|
||||
|
||||
if (title) {
|
||||
titleTag.textContent = '关键词: ' + title;
|
||||
titleTag.style.display = 'inline';
|
||||
} else {
|
||||
titleTag.style.display = 'none';
|
||||
}
|
||||
|
||||
document.getElementById('queryScoreTag').style.display = 'none';
|
||||
|
||||
let url = '/admin/api/questions?';
|
||||
const params = [];
|
||||
if (type) params.push('type=' + encodeURIComponent(type));
|
||||
if (title) params.push('title=' + encodeURIComponent(title));
|
||||
url += params.join('&');
|
||||
|
||||
fetchQuestions(url);
|
||||
}
|
||||
|
||||
// 获取题目列表
|
||||
@@ -514,7 +533,7 @@
|
||||
'<span style="color: #999;">-</span>';
|
||||
|
||||
html += `
|
||||
<tr>
|
||||
<tr id="question-row-${q.id}" data-id="${q.id}" data-type="${q.type}">
|
||||
<td>${q.id}</td>
|
||||
<td style="max-width: 300px; word-wrap: break-word;">${escapeHtml(q.title)}</td>
|
||||
<td>${typeTag}</td>
|
||||
@@ -524,7 +543,7 @@
|
||||
<td>
|
||||
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
|
||||
<button class="btn btn-primary" style="padding: 6px 12px; font-size: 12px;" onclick="editQuestion(${q.id}, ${q.type})">修改</button>
|
||||
<button class="btn btn-danger" style="padding: 6px 12px; font-size: 12px;" onclick="deleteQuestion('${escapeHtml(q.title)}')">删除</button>
|
||||
<button class="btn btn-danger" style="padding: 6px 12px; font-size: 12px;" onclick="deleteQuestion(${q.id}, '${escapeHtml(q.title)}')">删除</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -536,7 +555,7 @@
|
||||
}
|
||||
|
||||
// 删除题目
|
||||
function deleteQuestion(title) {
|
||||
function deleteQuestion(id, title) {
|
||||
if (!confirm('确定要删除这道题吗?')) return;
|
||||
|
||||
const formData = new FormData();
|
||||
@@ -550,7 +569,13 @@
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showAlert('删除成功!', 'success');
|
||||
loadAllQuestions();
|
||||
// 局部删除:从DOM中移除该行,不刷新整个列表
|
||||
const row = document.getElementById('question-row-' + id);
|
||||
if (row) {
|
||||
row.remove();
|
||||
// 更新计数
|
||||
updateQuestionCount();
|
||||
}
|
||||
} else {
|
||||
showAlert(data.message || '删除失败', 'error');
|
||||
}
|
||||
@@ -561,6 +586,15 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 更新题目计数
|
||||
function updateQuestionCount() {
|
||||
const countTag = document.getElementById('questionCount');
|
||||
if (countTag) {
|
||||
const rows = document.querySelectorAll('#questionsContainer tbody tr');
|
||||
countTag.textContent = `共 ${rows.length} 题`;
|
||||
}
|
||||
}
|
||||
|
||||
// 转义HTML
|
||||
function escapeHtml(text) {
|
||||
if (!text) return '';
|
||||
@@ -683,8 +717,9 @@
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showAlert('修改成功!', 'success');
|
||||
// 局部更新:只刷新修改的那一行(必须在 closeEditModal 之前调用)
|
||||
updateQuestionRow(currentEditId, currentEditType, formData);
|
||||
closeEditModal();
|
||||
loadAllQuestions();
|
||||
} else {
|
||||
showAlert(data.message || '修改失败', 'error');
|
||||
}
|
||||
@@ -695,13 +730,58 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 点击弹窗外部关闭
|
||||
window.onclick = function(event) {
|
||||
const modal = document.getElementById('editModal');
|
||||
if (event.target == modal) {
|
||||
closeEditModal();
|
||||
// 局部更新题目行
|
||||
function updateQuestionRow(id, type, formData) {
|
||||
const row = document.getElementById('question-row-' + id);
|
||||
if (!row) return;
|
||||
|
||||
const title = formData.get('title');
|
||||
const score = formData.get('score');
|
||||
const rAnswer = formData.get('rAnswer');
|
||||
|
||||
const typeTag = type == 1 ?
|
||||
'<span class="tag tag-choice">选择题</span>' :
|
||||
'<span class="tag tag-essay">简答题</span>';
|
||||
|
||||
let optionsHtml = '';
|
||||
if (type == 1) {
|
||||
const answerA = formData.get('answerA');
|
||||
const answerB = formData.get('answerB');
|
||||
const answerC = formData.get('answerC');
|
||||
const answerD = formData.get('answerD');
|
||||
const parts = [];
|
||||
if (answerA) parts.push(`A: ${escapeHtml(answerA)}`);
|
||||
if (answerB) parts.push(`B: ${escapeHtml(answerB)}`);
|
||||
if (answerC) parts.push(`C: ${escapeHtml(answerC)}`);
|
||||
if (answerD) parts.push(`D: ${escapeHtml(answerD)}`);
|
||||
optionsHtml = parts.join('<br>');
|
||||
} else {
|
||||
const answer = formData.get('answer');
|
||||
optionsHtml = answer ? escapeHtml(answer) : '无答案';
|
||||
}
|
||||
|
||||
const rAnswerHtml = rAnswer ?
|
||||
`<span class="tag tag-answer">${escapeHtml(rAnswer)}</span>` :
|
||||
'<span style="color: #999;">-</span>';
|
||||
|
||||
// 更新行内容
|
||||
row.innerHTML = `
|
||||
<td>${id}</td>
|
||||
<td style="max-width: 300px; word-wrap: break-word;">${escapeHtml(title)}</td>
|
||||
<td>${typeTag}</td>
|
||||
<td>${optionsHtml}</td>
|
||||
<td>${rAnswerHtml}</td>
|
||||
<td><span class="score-badge">${score}</span></td>
|
||||
<td>
|
||||
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
|
||||
<button class="btn btn-primary" style="padding: 6px 12px; font-size: 12px;" onclick="editQuestion(${id}, ${type})">修改</button>
|
||||
<button class="btn btn-danger" style="padding: 6px 12px; font-size: 12px;" onclick="deleteQuestion(${id}, '${escapeHtml(title)}')">删除</button>
|
||||
</div>
|
||||
</td>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
|
||||
Reference in New Issue
Block a user