fix: classes.html JavaScript syntax error - remove template literals in onclick attributes

- Replace template literals with string concatenation to avoid HTML attribute parsing issues
- Add escape function for proper HTML attribute values
This commit is contained in:
hmo
2026-04-27 03:34:35 +08:00
parent e50a9207b4
commit e6d3726d47
+28 -67
View File
@@ -236,8 +236,6 @@ function toggleMineFilter() {
// 加载班级列表 // 加载班级列表
function loadClasses() { function loadClasses() {
saveClassFilterState();
const activeFilter = document.getElementById('activeFilter').value; const activeFilter = document.getElementById('activeFilter').value;
const mineFilter = document.getElementById('mineFilterBtn').classList.contains('active'); const mineFilter = document.getElementById('mineFilterBtn').classList.contains('active');
let url = '/api/classes?'; let url = '/api/classes?';
@@ -248,69 +246,27 @@ function loadClasses() {
fetch(url).then(r => r.json()).then(classes => { fetch(url).then(r => r.json()).then(classes => {
const tbody = document.querySelector('#classesTable tbody'); const tbody = document.querySelector('#classesTable tbody');
const isAdmin = currentUserRole === 'admin'; const isAdmin = currentUserRole === 'admin';
tbody.innerHTML = classes.map(c => ` tbody.innerHTML = classes.map(c => {
<tr> const esc = (s) => s == null ? '' : String(s).replace(/'/g, "\\'").replace(/"/g, '&quot;');
<td>${c.id}</td> const level = c.level || '启蒙';
<td>${c.name}</td> const desc = c.description || '';
<td>${c.level || '启蒙'}</td> const teacherId = c.teacher_id || 'null';
<td>${c.description || '-'}</td> const active = c.active ? 'true' : 'false';
<td>${c.active ? '<span class="badge bg-success">进行中</span>' : '<span class="badge bg-secondary">已结束</span>'}</td> return '<tr>' +
<td><a href="#" onclick="viewClassStudents(${c.id})"> ${c.student_count}</a></td> '<td>' + c.id + '</td>' +
<td>${c.created_at}</td> '<td>' + esc(c.name) + '</td>' +
<td> '<td>' + level + '</td>' +
<button type="button" class="btn btn-sm btn-success me-1" onclick="openAssignGoalModal(${c.id}, '${c.name}')">分配目标</button> '<td>' + esc(desc) + '</td>' +
${isAdmin ? `<button type="button" class="btn btn-sm btn-primary me-1" onclick="editClass(${c.id}, '${c.name}', ${c.teacher_id || 'null'}, '${c.description || ''}', ${c.active}, '${c.level || '启蒙'}')">编辑</button> '<td>' + (c.active ? '<span class="badge bg-success">进行中</span>' : '<span class="badge bg-secondary">已结束</span>') + '</td>' +
<button type="button" class="btn btn-sm btn-danger" onclick="deleteClass(${c.id})">删除</button>` : ''} '<td><a href="#" onclick="viewClassStudents(' + c.id + ')">' + c.student_count + '</a></td>' +
</td> '<td>' + c.created_at + '</td>' +
</tr> '<td>' +
`).join(''); '<button type="button" class="btn btn-sm btn-success me-1" onclick="openAssignGoalModal(' + c.id + ', \'' + esc(c.name) + '\')">分配目标</button>' +
}); (isAdmin ? '<button type="button" class="btn btn-sm btn-primary me-1" onclick="editClass(' + c.id + ', \'' + esc(c.name) + '\', ' + teacherId + ', \'' + esc(desc) + '\', ' + active + ', \'' + level + '\')">编辑</button>' : '') +
} '<button type="button" class="btn btn-sm btn-danger" onclick="deleteClass(' + c.id + ')">删除</button>' +
loadClasses(); '</td>' +
}; '</tr>';
}).join('');
// 我的班级筛选
function toggleMineFilter() {
const btn = document.getElementById('mineFilterBtn');
btn.classList.toggle('active');
if (btn.classList.contains('active')) {
btn.classList.remove('btn-outline-secondary');
btn.classList.add('btn-primary');
} else {
btn.classList.remove('btn-primary');
btn.classList.add('btn-outline-secondary');
}
loadClasses();
}
// 加载班级列表
function loadClasses() {
const activeFilter = document.getElementById('activeFilter').value;
const mineFilter = document.getElementById('mineFilterBtn').classList.contains('active');
let url = '/api/classes?';
if (activeFilter) url += 'active=' + activeFilter + '&';
if (mineFilter) url += 'mine=true&';
url = url.endsWith('&') ? url.slice(0, -1) : url;
url = url.endsWith('?') ? '/api/classes' : url;
fetch(url).then(r => r.json()).then(classes => {
const tbody = document.querySelector('#classesTable tbody');
const isAdmin = currentUserRole === 'admin';
tbody.innerHTML = classes.map(c => `
<tr>
<td>${c.id}</td>
<td>${c.name}</td>
<td>${c.level || '启蒙'}</td>
<td>${c.description || '-'}</td>
<td>${c.active ? '<span class="badge bg-success">进行中</span>' : '<span class="badge bg-secondary">已结束</span>'}</td>
<td><a href="#" onclick="viewClassStudents(${c.id})">${c.student_count}</a></td>
<td>${c.created_at}</td>
<td>
<button type="button" class="btn btn-sm btn-success me-1" onclick="openAssignGoalModal(${c.id}, '${c.name}')">分配目标</button>
${isAdmin ? `<button type="button" class="btn btn-sm btn-primary me-1" onclick="editClass(${c.id}, '${c.name}', ${c.teacher_id || 'null'}, '${c.description || ''}', ${c.active}, '${c.level || '启蒙'}')">编辑</button>
<button type="button" class="btn btn-sm btn-danger" onclick="deleteClass(${c.id})">删除</button>` : ''}
</td>
</tr>
`).join('');
}); });
} }
@@ -518,7 +474,8 @@ document.getElementById('confirm-assign-goal').addEventListener('click', async (
// 弹出确认框 // 弹出确认框
if (!confirm('将给班级所有学员分配此目标,确定吗?')) return; if (!confirm('将给班级所有学员分配此目标,确定吗?')) return;
const res = await fetch(`/api/classes/${currentClassId}/goals`, { const url = '/api/classes/' + currentClassId + '/goals';
const res = await fetch(url, {
method: 'POST', method: 'POST',
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ body: JSON.stringify({
@@ -533,7 +490,11 @@ document.getElementById('confirm-assign-goal').addEventListener('click', async (
if (res.ok) { if (res.ok) {
const data = await res.json(); const data = await res.json();
assignGoalModal.hide(); assignGoalModal.hide();
alert(data.message + (data.skipped_count ? `${data.skipped_count}个学员已分配此目标,跳过)` : '')); let msg = data.message;
if (data.skipped_count) {
msg += ' (' + data.skipped_count + '个学员已分配此目标跳过)';
}
alert(msg);
} else { } else {
const err = await res.json(); const err = await res.json();
alert(err.error || '分配失败'); alert(err.error || '分配失败');