更新:models/routes/services/templates/docs

This commit is contained in:
hmo
2026-04-26 18:02:36 +08:00
parent f7a82ac48a
commit 6abdd49c04
31 changed files with 1480 additions and 676 deletions
+77 -4
View File
@@ -11,6 +11,9 @@
<option value="true" selected>进行中</option>
<option value="false">已结束</option>
</select>
<button class="btn btn-primary btn-sm active" id="mineFilterBtn" onclick="toggleMineFilter()">
<i class="bi bi-person"></i> 我的
</button>
</div>
<button class="btn btn-primary" id="addClassBtn" style="display:none;">
<i class="bi bi-plus-circle"></i> 新增班级
@@ -24,6 +27,7 @@
<tr>
<th>ID</th>
<th>班级名称</th>
<th>级别</th>
<th>描述</th>
<th>进行中</th>
<th>学员数</th>
@@ -64,10 +68,26 @@
<label class="form-label">班级名称</label>
<input type="text" class="form-control" id="className" required>
</div>
<div class="mb-3">
<label class="form-label">班主任</label>
<select class="form-select" id="classTeacher">
<option value="">未指定</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">描述</label>
<textarea class="form-control" id="classDesc" rows="2"></textarea>
</div>
<div class="mb-3">
<label class="form-label">级别</label>
<select class="form-select" id="classLevel">
<option value="启蒙">启蒙</option>
<option value="启蒙" selected>启蒙</option>
<option value="进阶">进阶</option>
<option value="熟练">熟练</option>
<option value="精通">精通</option>
</select>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="classActive" checked>
<label class="form-check-label" for="classActive">进行中</label>
@@ -165,10 +185,29 @@ window.pageInit = function(data) {
loadClasses();
};
// 我的班级筛选
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 url = activeFilter ? '/api/classes?active=' + activeFilter : '/api/classes';
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';
@@ -176,13 +215,14 @@ function loadClasses() {
<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.description || ''}', ${c.active})">编辑</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>
@@ -194,7 +234,9 @@ function loadClasses() {
document.getElementById('saveClassBtn').onclick = () => {
const id = document.getElementById('classId').value;
const name = document.getElementById('className').value.trim();
const teacherId = document.getElementById('classTeacher').value;
const description = document.getElementById('classDesc').value;
const level = document.getElementById('classLevel').value;
const active = document.getElementById('classActive').checked;
if (!name) {
@@ -206,7 +248,7 @@ document.getElementById('saveClassBtn').onclick = () => {
fetch('/api/classes' + (id ? '/' + id : ''), {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, description, active })
body: JSON.stringify({ name, description, level, active, teacher_id: teacherId ? parseInt(teacherId) : null })
}).then(r => r.json()).then(data => {
if (data.error) {
showToast(data.error);
@@ -221,12 +263,14 @@ document.getElementById('saveClassBtn').onclick = () => {
};
// 编辑班级
function editClass(id, name, desc, active) {
function editClass(id, name, teacherId, desc, active, level) {
document.getElementById('classId').value = id;
document.getElementById('className').value = name;
document.getElementById('classDesc').value = desc;
document.getElementById('classActive').checked = active !== false;
document.getElementById('classLevel').value = level || '启蒙';
document.getElementById('classModalTitle').textContent = '编辑班级';
loadTeacherOptions(teacherId);
new bootstrap.Modal(document.getElementById('classModal')).show();
}
@@ -289,6 +333,18 @@ document.getElementById('confirmAssignBtn').onclick = () => {
});
};
// 加载班主任选项
function loadTeacherOptions(selectedId) {
fetch('/api/teachers').then(r => r.json()).then(users => {
const select = document.getElementById('classTeacher');
select.innerHTML = '<option value="">未指定</option>';
users.forEach(u => {
const selected = u.id === selectedId ? 'selected' : '';
select.innerHTML += `<option value="${u.id}" ${selected}>${u.name}</option>`;
});
});
}
// 新增班级按钮
document.getElementById('addClassBtn').onclick = () => {
document.getElementById('classId').value = '';
@@ -296,6 +352,7 @@ document.getElementById('addClassBtn').onclick = () => {
document.getElementById('classDesc').value = '';
document.getElementById('classActive').checked = true;
document.getElementById('classModalTitle').textContent = '新增班级';
loadTeacherOptions(null);
new bootstrap.Modal(document.getElementById('classModal')).show();
};
@@ -348,6 +405,22 @@ document.getElementById('assign-assessment-date').addEventListener('change', fun
}
});
// 开始日期联动:修改开始日期后,如果使用"XX天后"评估,自动重新计算评估日期
document.getElementById('assign-start-date').addEventListener('change', function() {
const startDateStr = this.value;
const daysStr = document.getElementById('assign-assessment-days').value;
if (startDateStr && daysStr) {
const days = parseInt(daysStr);
const [y, m, d] = startDateStr.split('-').map(Number);
const startDate = new Date(y, m - 1, d);
startDate.setDate(startDate.getDate() + days);
const yy = startDate.getFullYear();
const mm = String(startDate.getMonth() + 1).padStart(2, '0');
const dd = String(startDate.getDate()).padStart(2, '0');
document.getElementById('assign-assessment-date').value = `${yy}-${mm}-${dd}`;
}
});
// 确认分配目标
document.getElementById('confirm-assign-goal').addEventListener('click', async () => {
const goalId = document.getElementById('assign-goal-select').value;