v2.5更新
This commit is contained in:
@@ -54,6 +54,16 @@ include __DIR__ . '/../includes/header.php';
|
||||
<option value="志愿">志愿</option>
|
||||
</select>
|
||||
</div>
|
||||
<?php if ($role === '班主任' || $role === '班长'): ?>
|
||||
<div class="filter-group">
|
||||
<label>状态</label>
|
||||
<select id="historyStatusFilter">
|
||||
<option value="">全部</option>
|
||||
<option value="0">正常</option>
|
||||
<option value="1">已撤销</option>
|
||||
</select>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<button class="btn btn-primary" onclick="loadHistory(1)">查询</button>
|
||||
<div class="filter-group" style="min-width:auto;">
|
||||
<label> </label>
|
||||
|
||||
@@ -288,6 +288,7 @@ body {
|
||||
/* ========== 表格 ========== */
|
||||
.table-wrapper {
|
||||
overflow-x: auto;
|
||||
overflow-y: visible;
|
||||
}
|
||||
|
||||
table {
|
||||
@@ -853,9 +854,9 @@ tr:hover {
|
||||
.action-dropdown-menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
bottom: 100%;
|
||||
right: 0;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
||||
@@ -926,3 +927,50 @@ tr:hover {
|
||||
.tag-danger { background: #ffebee; color: #c62828; }
|
||||
.tag-warning { background: #fff3e0; color: #e65100; }
|
||||
.tag-info { background: #e3f2fd; color: #1565c0; }
|
||||
|
||||
/* ========== 历史记录页优化 ========== */
|
||||
/* 时间列:确保分两行显示(日期+时间) */
|
||||
.history-time {
|
||||
white-space: pre-line;
|
||||
min-width: 80px;
|
||||
line-height: 1.5;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* 原因列:每行最少7个字,自动换行 */
|
||||
.history-reason {
|
||||
min-width: 7em;
|
||||
max-width: 200px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 学生名列:允许换行 */
|
||||
.history-students {
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
min-width: 60px;
|
||||
max-width: 120px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 合并记录按钮样式 */
|
||||
.btn-outline-danger {
|
||||
background: transparent;
|
||||
color: var(--color-danger);
|
||||
border: 1px solid var(--color-danger);
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn-outline-danger:hover {
|
||||
background: var(--color-danger-light);
|
||||
color: var(--color-danger-dark);
|
||||
border-color: var(--color-danger-dark);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ async function loadHistory(page = 1) {
|
||||
const studentId = document.getElementById('historyStudentId').value;
|
||||
const reasonFilter = document.getElementById('historyReasonFilter').value;
|
||||
const isGrouped = document.getElementById('historyGrouped').checked;
|
||||
const statusFilter = document.getElementById('historyStatusFilter')?.value;
|
||||
|
||||
const params = {
|
||||
page, page_size: 20,
|
||||
@@ -42,6 +43,7 @@ async function loadHistory(page = 1) {
|
||||
if (studentId) params.student_id = studentId;
|
||||
if (reasonFilter) params.reason_prefix = reasonFilter;
|
||||
if (isGrouped) params.grouped = true;
|
||||
if (statusFilter !== undefined && statusFilter !== '') params.is_revoked = parseInt(statusFilter);
|
||||
|
||||
const res = await apiGet('/api/admin/conduct/history', params);
|
||||
|
||||
@@ -50,6 +52,9 @@ async function loadHistory(page = 1) {
|
||||
let headHtml = '';
|
||||
if (isGrouped) {
|
||||
headHtml = '<th>时间</th><th>原因</th><th>分值</th><th' + nowrapStyle + '>操作人</th><th>涉及学生</th>';
|
||||
if (role === '班主任' || role === '班长') {
|
||||
headHtml += '<th>操作</th>';
|
||||
}
|
||||
} else {
|
||||
headHtml = '<th>时间</th><th>学生</th><th>分数变动</th><th>原因</th><th' + nowrapStyle + '>操作人</th>';
|
||||
if (role === '班主任' || role === '班长' || role === '考勤委员') {
|
||||
@@ -63,46 +68,56 @@ async function loadHistory(page = 1) {
|
||||
res.data.records.forEach(record => {
|
||||
const pointsClass = record.points_change > 0 ? 'plus' : 'minus';
|
||||
const names = record.student_names || '';
|
||||
html += `<tr>
|
||||
<td>${formatDateTime(record.created_at)}</td>
|
||||
<td class="preserve-newlines">${escapeHtml(record.reason)}</td>
|
||||
const allRevoked = record.all_revoked;
|
||||
const revokedStyle = allRevoked ? ' style="opacity:0.5; text-decoration:line-through;"' : '';
|
||||
html += `<tr${revokedStyle}>
|
||||
<td class="history-time">${formatDateTime(record.created_at)}</td>
|
||||
<td class="preserve-newlines history-reason">${escapeHtml(record.reason)}</td>
|
||||
<td class="${pointsClass}">${record.points_change > 0 ? '+' : ''}${record.points_change}×${record.student_count}</td>
|
||||
<td>${escapeHtml(record.recorder_name || '')}</td>
|
||||
<td style="white-space: nowrap;">${escapeHtml(names)}</td>
|
||||
</tr>`;
|
||||
<td class="history-students">${escapeHtml(names)}</td>`;
|
||||
if (role === '班主任' || role === '班长') {
|
||||
if (allRevoked) {
|
||||
html += `<td><span class="text-muted">已撤销</span></td>`;
|
||||
} else {
|
||||
html += `<td><button class="btn btn-sm btn-outline-danger" onclick="batchRevokeGrouped('${escapeHtml(record.reason)}', ${record.points_change}, '${escapeHtml(record.recorder_name || '')}', '${formatDateTime(record.created_at)}')">批量撤销</button></td>`;
|
||||
}
|
||||
}
|
||||
html += `</tr>`;
|
||||
});
|
||||
if (res.data.records.length === 0) {
|
||||
html = '<tr><td colspan="5" style="text-align:center;">暂无记录</td></tr>';
|
||||
const colSpan = (role === '班主任' || role === '班长') ? 6 : 5;
|
||||
html = '<tr><td colspan="' + colSpan + '" style="text-align:center;">暂无记录</td></tr>';
|
||||
}
|
||||
} else {
|
||||
res.data.records.forEach(record => {
|
||||
const pointsClass = record.points_change > 0 ? 'plus' : 'minus';
|
||||
const revokedStyle = record.is_revoked == 1 ? ' style="opacity:0.5; text-decoration:line-through;"' : '';
|
||||
html += `<tr${revokedStyle}>
|
||||
<td>${formatDateTime(record.created_at)}</td>
|
||||
<td>${escapeHtml(record.student_name)}</td>
|
||||
<td class="history-time">${formatDateTime(record.created_at)}</td>
|
||||
<td class="history-students">${escapeHtml(record.student_name)}</td>
|
||||
<td class="${pointsClass}">${record.points_change > 0 ? '+' : ''}${record.points_change}</td>
|
||||
<td class="preserve-newlines">${escapeHtml(record.reason)}</td>
|
||||
<td class="preserve-newlines history-reason">${escapeHtml(record.reason)}</td>
|
||||
<td>${escapeHtml(record.recorder_name)}</td>`;
|
||||
if (role === '班主任') {
|
||||
if (record.is_revoked == 1) {
|
||||
const revokerInfo = record.revoker_name ? `由 ${escapeHtml(record.revoker_name)} 撤销` : '已撤销';
|
||||
html += `<td><span class="text-muted" style="margin-right:4px;">${revokerInfo}</span><button class="btn btn-sm btn-secondary" onclick="restoreRecord(${record.record_id})">反撤销</button></td>`;
|
||||
html += `<td><span class="text-muted" style="margin-right:4px;">${revokerInfo}</span><button class="btn btn-sm btn-outline" onclick="restoreRecord(${record.record_id})">反撤销</button></td>`;
|
||||
} else {
|
||||
html += `<td><button class="btn btn-sm btn-danger" onclick="revokeRecord(${record.record_id})">撤销</button></td>`;
|
||||
html += `<td><button class="btn btn-sm btn-outline" onclick="revokeRecord(${record.record_id})">撤销</button></td>`;
|
||||
}
|
||||
} else if (role === '班长') {
|
||||
if (record.is_revoked == 1) {
|
||||
const revokerInfo = record.revoker_name ? `由 ${escapeHtml(record.revoker_name)} 撤销` : '已撤销';
|
||||
html += `<td><span class="text-muted">${revokerInfo}</span></td>`;
|
||||
} else {
|
||||
html += `<td><button class="btn btn-sm btn-danger" onclick="revokeRecord(${record.record_id})">撤销</button></td>`;
|
||||
html += `<td><button class="btn btn-sm btn-outline" onclick="revokeRecord(${record.record_id})">撤销</button></td>`;
|
||||
}
|
||||
} else if (role === '考勤委员') {
|
||||
if (record.is_revoked == 1) {
|
||||
html += `<td><span class="text-muted">已撤销</span></td>`;
|
||||
} else if (record.recorder_id == currentUserId) {
|
||||
html += `<td><button class="btn btn-sm btn-danger" onclick="revokeRecord(${record.record_id})">撤销</button></td>`;
|
||||
html += `<td><button class="btn btn-sm btn-outline" onclick="revokeRecord(${record.record_id})">撤销</button></td>`;
|
||||
} else {
|
||||
html += `<td><span class="text-muted">-</span></td>`;
|
||||
}
|
||||
@@ -177,6 +192,53 @@ async function exportHistoryRecords() {
|
||||
}
|
||||
}
|
||||
|
||||
// 批量撤销合并记录(按条件查找并撤销)
|
||||
async function batchRevokeGrouped(reason, pointsChange, recorderName, createdAt) {
|
||||
if (!confirm(`确定要撤销所有"${reason}"(${pointsChange > 0 ? '+' : ''}${pointsChange}分)的记录吗?`)) return;
|
||||
|
||||
showToast('正在批量撤销...', 'info');
|
||||
|
||||
try {
|
||||
// 先查询匹配的记录
|
||||
const params = {
|
||||
page: 1, page_size: 1000,
|
||||
start_date: document.getElementById('historyStartDate').value,
|
||||
end_date: document.getElementById('historyEndDate').value,
|
||||
reason_prefix: reason.substring(0, 4),
|
||||
grouped: false
|
||||
};
|
||||
|
||||
const res = await apiGet('/api/admin/conduct/history', params);
|
||||
if (!res || !res.success || !res.data.records) {
|
||||
showToast('查询记录失败', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// 精确匹配
|
||||
const matchedIds = [];
|
||||
res.data.records.forEach(r => {
|
||||
if (r.reason === reason && r.points_change === pointsChange && r.is_revoked == 0) {
|
||||
matchedIds.push(r.record_id);
|
||||
}
|
||||
});
|
||||
|
||||
if (matchedIds.length === 0) {
|
||||
showToast('没有找到可撤销的记录', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const revokeRes = await apiPost('/api/admin/conduct/batch-revoke', { record_ids: matchedIds });
|
||||
if (revokeRes && revokeRes.success) {
|
||||
showToast(`批量撤销完成: ${revokeRes.data.success_count}条成功`);
|
||||
loadHistory(currentHistoryPage);
|
||||
} else {
|
||||
showToast(revokeRes?.message || '批量撤销失败', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
showToast('批量撤销失败: ' + err.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
loadStudentsForSelect().then(() => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const preStudentId = urlParams.get('student_id');
|
||||
@@ -191,5 +253,6 @@ loadStudentsForSelect().then(() => {
|
||||
window.loadHistory = loadHistory;
|
||||
window.loadStudentsForSelect = loadStudentsForSelect;
|
||||
window.exportHistoryRecords = exportHistoryRecords;
|
||||
window.batchRevokeGrouped = batchRevokeGrouped;
|
||||
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user