v2.0.1更新

This commit is contained in:
2026-05-26 08:39:12 +08:00
parent cb0c367eb7
commit c575d711ee
34 changed files with 750 additions and 204 deletions

View File

@@ -744,4 +744,89 @@ tr:hover {
.search-bar input {
flex: 1;
}
}
/* ========== 操作列下拉菜单 ========== */
.action-dropdown {
position: relative;
display: inline-flex;
align-items: center;
gap: 4px;
}
.action-dropdown-toggle {
background: #f7fafc;
color: #4a5568;
border: 1px solid #e2e8f0;
padding: 4px 10px;
font-size: 12px;
cursor: pointer;
border-radius: 6px;
transition: all 0.2s;
white-space: nowrap;
}
.action-dropdown-toggle:hover {
background: #edf2f7;
border-color: #cbd5e0;
}
.action-dropdown-toggle.open {
background: #edf2f7;
border-color: #a0aec0;
}
.action-dropdown-menu {
display: none;
position: absolute;
top: 100%;
right: 0;
margin-top: 4px;
background: white;
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
border: 1px solid #e2e8f0;
min-width: 120px;
z-index: 200;
overflow: hidden;
padding: 4px 0;
}
.action-dropdown-menu.show {
display: block;
}
.action-dropdown-menu a {
display: block;
padding: 8px 14px;
color: #4a5568;
font-size: 13px;
cursor: pointer;
text-decoration: none;
transition: background 0.15s;
white-space: nowrap;
}
.action-dropdown-menu a:hover {
background: #f7fafc;
color: #2d3748;
}
.action-dropdown-menu a.danger {
color: #e53e3e;
border-top: 1px solid #edf2f7;
margin-top: 4px;
padding-top: 10px;
}
.action-dropdown-menu a.danger:hover {
background: #fff5f5;
color: #c53030;
}
@media (max-width: 768px) {
.action-dropdown-menu {
right: auto;
left: 0;
}
}

View File

@@ -23,10 +23,15 @@ async function loadAdmins() {
<td>${escapeHtml(admin.real_name)}</td>
<td>${escapeHtml(admin.role_type)}</td>
<td>
<button class="btn btn-sm btn-primary" onclick="showEditAdminModal(${admin.user_id}, '${escapeHtml(admin.username)}', '${escapeHtml(admin.real_name)}', '${escapeHtml(admin.role_type)}')">编辑</button>
<button class="btn btn-sm btn-warning" onclick="resetAdminPassword(${admin.user_id}, '${escapeHtml(admin.real_name)}')">重置密码</button>
<button class="btn btn-sm btn-info" onclick="unlockUser('${escapeHtml(admin.username)}', '${escapeHtml(admin.real_name)}')">解锁</button>
<button class="btn btn-sm btn-danger" onclick="deleteAdmin(${admin.user_id}, '${escapeHtml(admin.real_name)}')">删除</button>
<div class="action-dropdown">
<button class="btn btn-sm action-dropdown-toggle" onclick="toggleActionDropdown(this)">操作 ▼</button>
<div class="action-dropdown-menu">
<a onclick="showEditAdminModal(${admin.user_id}, '${escapeHtml(admin.username)}', '${escapeHtml(admin.real_name)}', '${escapeHtml(admin.role_type)}')">编辑</a>
<a onclick="resetAdminPassword(${admin.user_id}, '${escapeHtml(admin.real_name)}')">重置密码</a>
<a onclick="unlockUser('${escapeHtml(admin.username)}', '${escapeHtml(admin.real_name)}')">解锁</a>
<a class="danger" onclick="deleteAdmin(${admin.user_id}, '${escapeHtml(admin.real_name)}')">删除</a>
</div>
</div>
</td>
</tr>`;
});

View File

@@ -197,12 +197,12 @@ async function logout() {
function escapeHtml(str) {
if (!str) return '';
return str.replace(/[&<>]/g, function(m) {
if (m === '&') return '&amp;';
if (m === '<') return '&lt;';
if (m === '>') return '&gt;';
return m;
});
return String(str)
.replace(/&/g, '\x26amp;')
.replace(/</g, '\x26lt;')
.replace(/>/g, '\x26gt;')
.replace(/"/g, '\x26quot;')
.replace(/'/g, '\x26#x27;');
}
/**
@@ -336,6 +336,36 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
function toggleActionDropdown(el) {
var dropdown = el.closest('.action-dropdown');
if (!dropdown) return;
var menu = dropdown.querySelector('.action-dropdown-menu');
if (!menu) return;
var isOpen = menu.classList.contains('show');
// 先关闭所有
document.querySelectorAll('.action-dropdown-menu.show').forEach(function(m) {
m.classList.remove('show');
var toggle = m.closest('.action-dropdown').querySelector('.action-dropdown-toggle');
if (toggle) toggle.classList.remove('open');
});
if (!isOpen) {
menu.classList.add('show');
el.classList.add('open');
}
}
document.addEventListener('click', function(e) {
if (!e.target.closest('.action-dropdown')) {
document.querySelectorAll('.action-dropdown-menu.show').forEach(function(m) {
m.classList.remove('show');
var toggle = m.closest('.action-dropdown').querySelector('.action-dropdown-toggle');
if (toggle) toggle.classList.remove('open');
});
}
});
// 全局textarea键盘事件Enter提交表单Ctrl+Enter换行
document.addEventListener('keydown', function(e) {
if (e.target.tagName !== 'TEXTAREA') return;
@@ -351,4 +381,28 @@ document.addEventListener('keydown', function(e) {
}
}
// Ctrl+Enter和Shift+Enter保持默认换行行为不拦截
});
});
window.selectDeductionType = function(points, reason) {
var pointsEl = document.getElementById('pointsChange');
var reasonEl = document.getElementById('pointsReason');
if (points === 0 && reason === '') {
// 自定义模式 - 清空分值和原因,聚焦原因输入框
if (pointsEl) pointsEl.value = '';
if (reasonEl) {
reasonEl.value = '';
reasonEl.focus();
}
} else if (points === null || points === undefined) {
// 类别模式 - 仅填充原因,聚焦分值输入框
if (reasonEl) reasonEl.value = reason;
if (pointsEl) {
pointsEl.value = '';
pointsEl.focus();
}
} else {
// 预设模式 - 同时填充分值和原因
if (pointsEl) pointsEl.value = points;
if (reasonEl) reasonEl.value = reason;
}
};

View File

@@ -1,5 +1,5 @@
/**
* 班级操行分管理系统 - 作业管理页JS
* 班级操行分管理系统 - 作业扣分页JS
*
* 开发者: Canglan
* 版权归属: Sea Network Technology Studio
@@ -28,7 +28,6 @@ document.getElementById('pointsChange').setAttribute('max', hwMaxPoints);
// 加载科目列表(学习委员)
async function loadSubjectsForHomework() {
if (hwRole !== '学习委员') return;
const subjectSelect = document.getElementById('hwSubjectSelect');
if (!subjectSelect) return;
const res = await apiGet('/api/subject/list');
@@ -69,16 +68,6 @@ function showSinglePointsModal(studentId, studentName) {
document.getElementById('batchPointsModal').style.display = 'flex';
}
function selectDeductionType(points, reason) {
document.getElementById('pointsChange').value = points;
if (points !== 0) {
document.getElementById('pointsReason').value = reason;
} else {
document.getElementById('pointsReason').value = '';
document.getElementById('pointsReason').focus();
}
}
function handleSubmitPoints() {
const pointsChange = parseInt(document.getElementById('pointsChange').value);
if (isNaN(pointsChange) || pointsChange === 0) {
@@ -91,7 +80,7 @@ function handleSubmitPoints() {
}
// 学习委员附加科目前缀、具体作业和缴交时间
if (hwRole === '学习委员') {
if (hwRole === '学习委员' || hwRole === '班主任') {
const subjectSelect = document.getElementById('hwSubjectSelect');
const subjectName = subjectSelect ? subjectSelect.value : '';
const hwTitle = document.getElementById('hwTitle').value.trim();
@@ -113,7 +102,136 @@ function handleSubmitPoints() {
}
}
submitBatchPoints();
submitBatchPoints({ related_type: 'homework' });
}
// ========== 科目管理功能 ==========
function toggleSubjectPanel() {
const content = document.getElementById('subjectPanelContent');
const toggle = document.getElementById('subjectPanelToggle');
if (content.style.display === 'none') {
content.style.display = 'block';
toggle.textContent = '▼ 收起';
loadSubjectList();
} else {
content.style.display = 'none';
toggle.textContent = '▶ 展开';
}
}
async function loadSubjectList() {
const res = await apiGet('/api/subject/list');
if (res && res.success) {
let html = '';
res.data.subjects.forEach(sub => {
html += `
<div class="subject-item">
<span class="subject-name">${escapeHtml(sub.subject_name)}</span>
<span class="subject-code">${escapeHtml(sub.subject_code || '')}</span>
<span class="subject-status ${sub.is_active ? 'subject-status-active' : 'subject-status-inactive'}">
${sub.is_active ? '启用' : '禁用'}
</span>
<button class="btn btn-sm btn-primary" onclick="showEditSubjectModal(${sub.subject_id}, '${escapeHtml(sub.subject_name)}', '${escapeHtml(sub.subject_code || '')}', ${sub.sort_order || 0})">编辑</button>
<button class="btn btn-sm" onclick="toggleSubjectStatus(${sub.subject_id}, ${!sub.is_active})">
${sub.is_active ? '禁用' : '启用'}
</button>
<button class="btn btn-sm btn-danger" onclick="deleteSubject(${sub.subject_id})">删除</button>
</div>
`;
});
if (res.data.subjects.length === 0) {
html = '<p style="text-align:center;padding:40px;">暂无科目,请点击"添加科目"</p>';
}
document.getElementById('subjectList').innerHTML = html;
}
}
function showAddSubjectModal() {
const form = document.getElementById('addSubjectFormInHw');
if (form) form.reset();
document.getElementById('addSubjectModal').style.display = 'flex';
}
async function submitAddSubject() {
const subjectName = document.getElementById('subjectName').value.trim();
const subjectCode = document.getElementById('subjectCode').value.trim();
if (!subjectName) {
showToast('请填写科目名称', 'warning');
return;
}
const res = await apiPost('/api/subject/create', {
subject_name: subjectName,
subject_code: subjectCode
});
if (res && res.success) {
showToast('科目添加成功');
closeModal('addSubjectModal');
loadSubjectList();
loadSubjectsForHomework();
} else {
showToast(res?.message || '添加失败', 'error');
}
}
async function toggleSubjectStatus(subjectId, enable) {
const res = await apiPut(`/api/subject/update/${subjectId}`, { is_active: enable });
if (res && res.success) {
showToast(enable ? '科目已启用' : '科目已禁用');
loadSubjectList();
loadSubjectsForHomework();
} else {
showToast(res?.message || '操作失败', 'error');
}
}
async function deleteSubject(subjectId) {
if (!confirm('确定要删除该科目吗?如果科目下有作业数据将无法删除。')) return;
const res = await apiDelete('/api/subject/delete/' + subjectId);
if (res && res.success) {
showToast('科目删除成功');
loadSubjectList();
loadSubjectsForHomework();
} else {
showToast(res?.message || '删除失败', 'error');
}
}
function showEditSubjectModal(subjectId, name, code, sortOrder) {
document.getElementById('editSubjectId').value = subjectId;
document.getElementById('editSubjectName').value = name;
document.getElementById('editSubjectCode').value = code;
document.getElementById('editSubjectSortOrder').value = sortOrder;
document.getElementById('editSubjectModal').style.display = 'flex';
}
async function submitEditSubject() {
const subjectId = document.getElementById('editSubjectId').value;
const subjectName = document.getElementById('editSubjectName').value.trim();
const subjectCode = document.getElementById('editSubjectCode').value.trim();
const sortOrder = document.getElementById('editSubjectSortOrder').value;
if (!subjectName) {
showToast('请填写科目名称', 'warning');
return;
}
const data = { subject_name: subjectName };
if (subjectCode) data.subject_code = subjectCode;
if (sortOrder !== '') data.sort_order = parseInt(sortOrder);
const res = await apiPut(`/api/subject/update/${subjectId}`, data);
if (res && res.success) {
showToast('科目更新成功');
closeModal('editSubjectModal');
loadSubjectList();
loadSubjectsForHomework();
} else {
showToast(res?.message || '更新失败', 'error');
}
}
loadStudents();
@@ -121,7 +239,13 @@ loadSubjectsForHomework();
window.loadStudents = loadStudents;
window.showSinglePointsModal = showSinglePointsModal;
window.selectDeductionType = selectDeductionType;
window.handleSubmitPoints = handleSubmitPoints;
window.toggleSubjectPanel = toggleSubjectPanel;
window.showAddSubjectModal = showAddSubjectModal;
window.submitAddSubject = submitAddSubject;
window.toggleSubjectStatus = toggleSubjectStatus;
window.deleteSubject = deleteSubject;
window.showEditSubjectModal = showEditSubjectModal;
window.submitEditSubject = submitEditSubject;
})();

View File

@@ -35,7 +35,7 @@
}
// 提交批量加减分
async function submitBatchPoints() {
async function submitBatchPoints(options = {}) {
const pointsChange = parseInt(document.getElementById('pointsChange').value);
const reason = document.getElementById('pointsReason').value;
@@ -49,11 +49,15 @@
return;
}
const res = await apiPost('/api/admin/conduct/add', {
const data = {
student_ids: selectedStudentIds,
points_change: pointsChange,
reason: reason
});
};
if (options.related_type) {
data.related_type = options.related_type;
}
const res = await apiPost('/api/admin/conduct/add', data);
if (res && res.success) {
showToast(`操作成功: ${res.data.success_count} 人成功`);

View File

@@ -60,28 +60,39 @@ async function loadSemesters() {
const startDate = sem.start_date || '';
const endDate = sem.end_date || '';
if (!sem.is_archived) {
actions += `<button class="btn btn-sm" style="border:1px solid #667eea;color:#667eea;" onclick="showEditSemesterModal(${sem.semester_id}, '${escapeHtml(sem.semester_name)}', '${startDate}', '${endDate}')">编辑</button> `;
if (!sem.is_active) {
actions += `<button class="btn btn-sm btn-primary" onclick="activateSemester(${sem.semester_id})">激活</button> `;
}
actions += `<button class="btn btn-sm" style="border:1px solid #2ecc71;color:#2ecc71;" onclick="showAssociateConfirm(${sem.semester_id}, '${escapeHtml(sem.semester_name)}', '${startDate}', '${endDate}')">关联数据</button> `;
actions += `<button class="btn btn-sm btn-warning" onclick="showArchiveConfirm(${sem.semester_id}, '${escapeHtml(sem.semester_name)}')">归档</button> `;
actions += `<div class="action-dropdown">
<button class="btn btn-sm action-dropdown-toggle" onclick="toggleActionDropdown(this)">操作 ▼</button>
<div class="action-dropdown-menu">
<a onclick="showEditSemesterModal(${sem.semester_id}, '${escapeHtml(sem.semester_name)}', '${startDate}', '${endDate}')">编辑</a>
${!sem.is_active ? `<a onclick="activateSemester(${sem.semester_id})">激活</a>` : ''}
<a onclick="showAssociateConfirm(${sem.semester_id}, '${escapeHtml(sem.semester_name)}', '${startDate}', '${endDate}')">关联数据</a>
<a class="danger" onclick="showArchiveConfirm(${sem.semester_id}, '${escapeHtml(sem.semester_name)}')">归档</a>
</div>
</div>`;
}
if (sem.is_archived) {
actions += `<button class="btn btn-sm btn-secondary" onclick="viewArchiveData(${sem.semester_id}, '${escapeHtml(sem.semester_name)}')">查看归档</button>`;
}
const conductCount = sem.conduct_count || 0;
const attendanceCount = sem.attendance_count || 0;
let recordText = '-';
if (conductCount > 0 || attendanceCount > 0) {
recordText = `${conductCount}条操行分 / ${attendanceCount}条考勤`;
}
html += `<tr>
<td>${escapeHtml(sem.semester_name)}</td>
<td>${formatDate(sem.start_date)}</td>
<td>${formatDate(sem.end_date)}</td>
<td><span class="${statusClass}">${statusText}</span></td>
<td>${recordText}</td>
<td>${formatDateTime(sem.created_at)}</td>
<td>${actions}</td>
</tr>`;
});
if (semesters.length === 0) {
html = '<tr><td colspan="6" style="text-align:center;">暂无学期,请点击上方按钮创建新学期</td></tr>';
html = '<tr><td colspan="7" style="text-align:center;">暂无学期,请点击上方按钮创建新学期</td></tr>';
}
document.getElementById('semesterList').innerHTML = html;
}

View File

@@ -17,17 +17,24 @@ async function loadHomework() {
if (res && res.success) {
let html = '';
res.data.homework.forEach(hw => {
const pointsDisplay = hw.points ? hw.points + '分' : '-';
// 提交状态
let statusDisplay = '-';
if (hw.status) {
statusDisplay = getStatusBadge(hw.status, 'homework');
}
// 扣分显示
const pointsDisplay = hw.points ? `<span style="color: #e53e3e;">${hw.points}分</span>` : '-';
html += `<tr>
<td>${escapeHtml(hw.subject_name)}</td>
<td>${hw.deadline || hw.created_at}</td>
<td>${pointsDisplay}</td>
<td>${escapeHtml(hw.comments || '-')}</td>
<td>${escapeHtml(hw.title)}</td>
<td>${escapeHtml(hw.subject_name)}</td>
<td>${hw.deadline || '-'}</td>
<td>${statusDisplay}</td>
<td>${pointsDisplay}</td>
</tr>`;
});
if (res.data.homework.length === 0) {
html = '<tr><td colspan="5" style="text-align:center;">暂无作业</td></tr>';
html = '<tr><td colspan="5" style="text-align:center; padding: 40px; color: #999;">📝 暂无作业记录</td></tr>';
}
document.getElementById('homeworkList').innerHTML = html;
}

View File

@@ -30,11 +30,16 @@ async function loadStudents(page = 1) {
<td>${student.total_points}</td>
${userRole === '班主任' ? `<td>${student.parent_phone ? student.parent_phone.slice(0,3) + '******' + student.parent_phone.slice(-2) : '-'}</td>` : ''}
<td>
<button class="btn btn-sm btn-primary" onclick="showSinglePointsModal(${student.student_id}, '${escapeHtml(student.name)}')">加减分</button>
${userRole === '班主任' ? `<button class="btn btn-sm btn-secondary" onclick="showEditStudentModal(${student.student_id}, '${escapeHtml(student.student_no)}', '${escapeHtml(student.name)}', '${escapeHtml(student.parent_phone || '')}', '${escapeHtml(student.dormitory_number || '')}')">编辑</button>
<button class="btn btn-sm btn-warning" onclick="showResetStudentPasswordModal(${student.student_id}, '${escapeHtml(student.name)}')">重置密码</button>
<button class="btn btn-sm btn-info" onclick="unlockStudent('${escapeHtml(student.student_no)}', '${escapeHtml(student.name)}')">解锁</button>
<button class="btn btn-sm btn-danger" onclick="deleteStudent(${student.student_id}, '${escapeHtml(student.name)}')">删除</button>` : ''}
<div class="action-dropdown">
<button class="btn btn-sm btn-primary" onclick="showSinglePointsModal(${student.student_id}, '${escapeHtml(student.name)}')">加减分</button>
${userRole === '班主任' ? `<button class="btn btn-sm action-dropdown-toggle" onclick="toggleActionDropdown(this)">更多 ▼</button>
<div class="action-dropdown-menu">
<a onclick="showEditStudentModal(${student.student_id}, '${escapeHtml(student.student_no)}', '${escapeHtml(student.name)}', '${escapeHtml(student.parent_phone || '')}', '${escapeHtml(student.dormitory_number || '')}')">编辑</a>
<a onclick="showResetStudentPasswordModal(${student.student_id}, '${escapeHtml(student.name)}')">重置密码</a>
<a onclick="unlockStudent('${escapeHtml(student.student_no)}', '${escapeHtml(student.name)}')">解锁</a>
<a class="danger" onclick="deleteStudent(${student.student_id}, '${escapeHtml(student.name)}')">删除</a>
</div>` : ''}
</div>
</td>
</tr>`;
});