v2.6更新
This commit is contained in:
@@ -277,6 +277,7 @@ classmanager/
|
||||
| v2.3 | 2026.5.28 | 升级系统全面重构:修复前后端通信错误处理、SQL DELIMITER解析修复、XSS防护、升级验证+自动重试+失败回滚机制、补全v1.0-v1.6增量升级脚本 |
|
||||
| v2.5 | 2026.5.28 | 历史记录页优化(文字宽度/换行/合并按钮样式)、新增状态筛选项、合并记录批量撤销/反撤销、操作菜单底部遮挡修复、删除科目报错修复、学期自动关联+当前周数计算 |
|
||||
| v2.5.1 | 2026.5.28 | 筛选学生时自动取消合并记录、合并记录选项样式修复(竖排显示)、历史记录筛选改为折叠式、科目管理调用修复、全局escapeHtml XSS转义修复 |
|
||||
| v2.6 | 2026.5.28 | 历史记录筛选面板重构(学生筛选移入面板、合并按钮始终可见)、新增科目下拉筛选、新增原因关键词搜索、科目删除后默认仅显示启用科目、筛选面板展开修复、版本号同步至2.6 |
|
||||
|
||||
## 许可证
|
||||
|
||||
|
||||
@@ -152,6 +152,7 @@ class ConductModel:
|
||||
params.extend([limit, offset])
|
||||
return await execute_query(sql, tuple(params))
|
||||
|
||||
@staticmethod
|
||||
@staticmethod
|
||||
async def get_all_records(
|
||||
limit: int = 100,
|
||||
@@ -162,7 +163,8 @@ class ConductModel:
|
||||
include_revoked: bool = True,
|
||||
related_type: str = None,
|
||||
reason_prefix: str = None,
|
||||
is_revoked: int = None
|
||||
is_revoked: int = None,
|
||||
reason_search: str = None
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""获取所有记录(班主任/班长专用)"""
|
||||
# 空字符串转为None
|
||||
@@ -174,6 +176,8 @@ class ConductModel:
|
||||
related_type = None
|
||||
if reason_prefix == "":
|
||||
reason_prefix = None
|
||||
if reason_search == "":
|
||||
reason_search = None
|
||||
sql = """
|
||||
SELECT cr.*, s.name as student_name, s.student_no, u.real_name as recorder_name,
|
||||
ru.real_name as revoker_name
|
||||
@@ -207,6 +211,10 @@ class ConductModel:
|
||||
sql += " AND cr.reason LIKE %s"
|
||||
params.append(f"{reason_prefix}%")
|
||||
|
||||
if reason_search:
|
||||
sql += " AND cr.reason LIKE %s"
|
||||
params.append(f"%{reason_search}%")
|
||||
|
||||
if is_revoked is not None:
|
||||
sql += " AND cr.is_revoked = %s"
|
||||
params.append(1 if is_revoked else 0)
|
||||
@@ -215,7 +223,7 @@ class ConductModel:
|
||||
params.extend([limit, offset])
|
||||
|
||||
return await execute_query(sql, tuple(params))
|
||||
|
||||
|
||||
@staticmethod
|
||||
async def get_grouped_records(
|
||||
student_id: int = None,
|
||||
@@ -225,7 +233,8 @@ class ConductModel:
|
||||
reason_prefix: str = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
is_revoked: int = None
|
||||
is_revoked: int = None,
|
||||
reason_search: str = None
|
||||
) -> Dict[str, Any]:
|
||||
"""获取分组后的操行分记录(同批次合并)"""
|
||||
if start_date == "":
|
||||
@@ -236,6 +245,8 @@ class ConductModel:
|
||||
related_type = None
|
||||
if reason_prefix == "":
|
||||
reason_prefix = None
|
||||
if reason_search == "":
|
||||
reason_search = None
|
||||
|
||||
conditions = ["1=1"]
|
||||
params = []
|
||||
@@ -261,6 +272,9 @@ class ConductModel:
|
||||
if reason_prefix:
|
||||
conditions.append("cr.reason LIKE %s")
|
||||
params.append(f"{reason_prefix}%")
|
||||
if reason_search:
|
||||
conditions.append("cr.reason LIKE %s")
|
||||
params.append(f"%{reason_search}%")
|
||||
|
||||
where_clause = " AND ".join(conditions)
|
||||
|
||||
|
||||
@@ -320,7 +320,8 @@ async def get_conduct_history(
|
||||
grouped: bool = Query(False),
|
||||
related_type: Optional[str] = None,
|
||||
reason_prefix: Optional[str] = None,
|
||||
is_revoked: Optional[int] = None
|
||||
is_revoked: Optional[int] = None,
|
||||
reason_search: Optional[str] = None
|
||||
):
|
||||
"""获取操行分历史记录"""
|
||||
try:
|
||||
@@ -337,7 +338,8 @@ async def get_conduct_history(
|
||||
grouped=grouped,
|
||||
related_type=related_type,
|
||||
reason_prefix=reason_prefix,
|
||||
is_revoked=is_revoked
|
||||
is_revoked=is_revoked,
|
||||
reason_search=reason_search
|
||||
)
|
||||
return success_response(data=result)
|
||||
except Exception as e:
|
||||
|
||||
@@ -18,6 +18,7 @@ import re
|
||||
logger = setup_logger()
|
||||
router = APIRouter()
|
||||
|
||||
# 版本列表(按顺序)
|
||||
# 版本列表(按顺序)
|
||||
ALL_VERSIONS = {
|
||||
'1.0': 'v1.0.sql',
|
||||
@@ -34,12 +35,11 @@ ALL_VERSIONS = {
|
||||
'2.1': 'v2.1.sql',
|
||||
'2.2': 'v2.2.sql',
|
||||
'2.3': 'v2.3.sql',
|
||||
'2.3': 'v2.3.sql',
|
||||
'2.4': 'v2.4.sql',
|
||||
'2.5': 'v2.5.sql',
|
||||
'2.5.1': 'v2.5.1.sql',
|
||||
'2.6': 'v2.6.sql',
|
||||
}
|
||||
|
||||
# 版本特征标记(按优先级从高到低)
|
||||
VERSION_MARKERS = [
|
||||
('2.0', 'students', 'dormitory_number'),
|
||||
|
||||
@@ -230,7 +230,8 @@ class ConductService:
|
||||
grouped: bool = False,
|
||||
related_type: Optional[str] = None,
|
||||
reason_prefix: Optional[str] = None,
|
||||
is_revoked: Optional[int] = None
|
||||
is_revoked: Optional[int] = None,
|
||||
reason_search: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""获取历史记录"""
|
||||
# 空字符串转为None
|
||||
@@ -242,6 +243,8 @@ class ConductService:
|
||||
related_type = None
|
||||
if reason_prefix == "":
|
||||
reason_prefix = None
|
||||
if reason_search == "":
|
||||
reason_search = None
|
||||
if related_type and related_type not in ('manual', 'homework', 'attendance'):
|
||||
return {"records": [], "page": page, "page_size": page_size, "total": 0, "total_pages": 0}
|
||||
|
||||
@@ -259,7 +262,8 @@ class ConductService:
|
||||
reason_prefix=reason_prefix,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
is_revoked=is_revoked
|
||||
is_revoked=is_revoked,
|
||||
reason_search=reason_search
|
||||
)
|
||||
|
||||
records = await ConductModel.get_all_records(
|
||||
@@ -270,7 +274,8 @@ class ConductService:
|
||||
student_id=student_id,
|
||||
related_type=related_type,
|
||||
reason_prefix=reason_prefix,
|
||||
is_revoked=is_revoked
|
||||
is_revoked=is_revoked,
|
||||
reason_search=reason_search
|
||||
)
|
||||
|
||||
# 获取总数
|
||||
@@ -292,6 +297,9 @@ class ConductService:
|
||||
if reason_prefix:
|
||||
count_conditions.append("cr.reason LIKE %s")
|
||||
count_params.append(f"{reason_prefix}%")
|
||||
if reason_search:
|
||||
count_conditions.append("cr.reason LIKE %s")
|
||||
count_params.append(f"%{reason_search}%")
|
||||
if is_revoked is not None:
|
||||
count_conditions.append("cr.is_revoked = %s")
|
||||
count_params.append(1 if is_revoked else 0)
|
||||
|
||||
@@ -27,14 +27,11 @@ include __DIR__ . '/../includes/header.php';
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<div class="filter-bar" id="historyFilterBar">
|
||||
<div class="filter-group">
|
||||
<label>学生</label>
|
||||
<select id="historyStudentId" onchange="onStudentFilterChange()">
|
||||
<option value="">全部</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="loadHistory(1)">查询</button>
|
||||
<button class="btn btn-ghost" id="filterToggleBtn" onclick="toggleFilterPanel()">展开筛选 ▼</button>
|
||||
<label class="history-grouped-label">
|
||||
<input type="checkbox" id="historyGrouped" checked onchange="loadHistory(1)"> 批次合并
|
||||
</label>
|
||||
<?php if ($role === '班主任'): ?>
|
||||
<button class="btn btn-secondary" onclick="exportHistoryRecords()">导出</button>
|
||||
<?php endif; ?>
|
||||
@@ -42,6 +39,22 @@ include __DIR__ . '/../includes/header.php';
|
||||
<!-- 高级筛选面板(默认折叠) -->
|
||||
<div id="advancedFilters" style="display:none; padding: 0 16px 16px; border-top: 1px solid var(--color-border-light);">
|
||||
<div style="display:flex; flex-wrap:wrap; gap:16px; padding-top:16px;">
|
||||
<div class="filter-group">
|
||||
<label>学生</label>
|
||||
<select id="historyStudentId" onchange="onStudentFilterChange()">
|
||||
<option value="">全部</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label>科目</label>
|
||||
<select id="historySubjectFilter" onchange="onSubjectFilterChange()">
|
||||
<option value="">全部科目</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label>搜索原因</label>
|
||||
<input type="text" id="historyReasonSearch" placeholder="输入关键词..." style="min-width:150px;">
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label>开始日期</label>
|
||||
<input type="date" id="historyStartDate">
|
||||
@@ -74,11 +87,6 @@ include __DIR__ . '/../includes/header.php';
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div style="margin-top: 12px;">
|
||||
<label class="history-grouped-label">
|
||||
<input type="checkbox" id="historyGrouped" checked onchange="loadHistory(1)"> 批次合并
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-wrapper">
|
||||
|
||||
@@ -33,6 +33,20 @@ async function loadStudentsForSelect() {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载科目下拉列表
|
||||
async function loadSubjectsForFilter() {
|
||||
var subjectSelect = document.getElementById('historySubjectFilter');
|
||||
if (!subjectSelect) return;
|
||||
var res = await apiGet('/api/subject/list', { is_active: true });
|
||||
if (res && res.success && res.data && res.data.subjects) {
|
||||
let html = '<option value="">全部科目</option>';
|
||||
res.data.subjects.forEach(s => {
|
||||
html += '<option value="' + escapeHtml(s.subject_name) + '">' + escapeHtml(s.subject_name) + '</option>';
|
||||
});
|
||||
subjectSelect.innerHTML = html;
|
||||
}
|
||||
}
|
||||
|
||||
// 筛选学生时自动取消合并记录
|
||||
function onStudentFilterChange() {
|
||||
var studentId = document.getElementById('historyStudentId').value;
|
||||
@@ -42,10 +56,19 @@ function onStudentFilterChange() {
|
||||
}
|
||||
}
|
||||
|
||||
// 科目筛选变化时,取消扣分类型筛选(互斥)
|
||||
function onSubjectFilterChange() {
|
||||
var subjectVal = document.getElementById('historySubjectFilter').value;
|
||||
if (subjectVal) {
|
||||
document.getElementById('historyReasonFilter').value = '';
|
||||
}
|
||||
}
|
||||
|
||||
// 折叠/展开筛选面板
|
||||
function toggleFilterPanel() {
|
||||
var panel = document.getElementById('advancedFilters');
|
||||
var btn = document.getElementById('filterToggleBtn');
|
||||
if (!panel || !btn) return;
|
||||
if (panel.style.display === 'none') {
|
||||
panel.style.display = 'block';
|
||||
btn.textContent = '收起筛选 ▲';
|
||||
@@ -62,6 +85,8 @@ async function loadHistory(page) {
|
||||
var endDate = document.getElementById('historyEndDate').value;
|
||||
var studentId = document.getElementById('historyStudentId').value;
|
||||
var reasonFilter = document.getElementById('historyReasonFilter').value;
|
||||
var subjectFilter = document.getElementById('historySubjectFilter').value;
|
||||
var reasonSearch = document.getElementById('historyReasonSearch').value.trim();
|
||||
var isGrouped = document.getElementById('historyGrouped').checked;
|
||||
var statusFilter = document.getElementById('historyStatusFilter') ? document.getElementById('historyStatusFilter').value : '';
|
||||
|
||||
@@ -74,7 +99,15 @@ async function loadHistory(page) {
|
||||
end_date: endDate
|
||||
};
|
||||
if (studentId) params.student_id = studentId;
|
||||
if (reasonFilter) params.reason_prefix = reasonFilter;
|
||||
|
||||
// 科目筛选优先于扣分类型筛选
|
||||
if (subjectFilter) {
|
||||
params.reason_prefix = '[' + subjectFilter + ']';
|
||||
} else if (reasonFilter) {
|
||||
params.reason_prefix = reasonFilter;
|
||||
}
|
||||
|
||||
if (reasonSearch) params.reason_search = reasonSearch;
|
||||
if (isGrouped) params.grouped = true;
|
||||
if (statusFilter !== '') params.is_revoked = parseInt(statusFilter);
|
||||
|
||||
@@ -186,11 +219,18 @@ async function exportHistoryRecords() {
|
||||
|
||||
try {
|
||||
var reasonFilter = document.getElementById('historyReasonFilter').value;
|
||||
var subjectFilter = document.getElementById('historySubjectFilter').value;
|
||||
var reasonSearch = document.getElementById('historyReasonSearch').value.trim();
|
||||
var params = { page: 1, page_size: 1000 };
|
||||
if (startDate) params.start_date = startDate;
|
||||
if (endDate) params.end_date = endDate;
|
||||
if (studentId) params.student_id = studentId;
|
||||
if (reasonFilter) params.reason_prefix = reasonFilter;
|
||||
if (subjectFilter) {
|
||||
params.reason_prefix = '[' + subjectFilter + ']';
|
||||
} else if (reasonFilter) {
|
||||
params.reason_prefix = reasonFilter;
|
||||
}
|
||||
if (reasonSearch) params.reason_search = reasonSearch;
|
||||
|
||||
var res = await apiGet('/api/admin/conduct/history', params);
|
||||
if (res && res.success && res.data.records) {
|
||||
@@ -270,7 +310,8 @@ async function batchRevokeGrouped(reason, pointsChange, recorderName, createdAt)
|
||||
}
|
||||
}
|
||||
|
||||
loadStudentsForSelect().then(function() {
|
||||
// 初始化:并行加载学生和科目列表,然后加载历史记录
|
||||
Promise.all([loadStudentsForSelect(), loadSubjectsForFilter()]).then(function() {
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
var preStudentId = urlParams.get('student_id');
|
||||
if (preStudentId) {
|
||||
@@ -285,6 +326,7 @@ window.loadStudentsForSelect = loadStudentsForSelect;
|
||||
window.exportHistoryRecords = exportHistoryRecords;
|
||||
window.batchRevokeGrouped = batchRevokeGrouped;
|
||||
window.onStudentFilterChange = onStudentFilterChange;
|
||||
window.onSubjectFilterChange = onSubjectFilterChange;
|
||||
window.toggleFilterPanel = toggleFilterPanel;
|
||||
|
||||
})();
|
||||
|
||||
@@ -126,7 +126,7 @@ function toggleSubjectPanel() {
|
||||
}
|
||||
|
||||
async function loadSubjectList() {
|
||||
const res = await apiGet('/api/subject/list');
|
||||
const res = await apiGet('/api/subject/list', { is_active: true });
|
||||
if (res && res.success && res.data) {
|
||||
let html = '';
|
||||
const subjects = res.data.subjects || [];
|
||||
|
||||
@@ -230,10 +230,10 @@ INSERT IGNORE INTO `subjects` (`subject_name`, `subject_code`, `sort_order`) VAL
|
||||
('数学', 'MATH', 2),
|
||||
('英语', 'ENG', 3);
|
||||
|
||||
-- 初始化系统版本号
|
||||
-- 初始化系统版本号
|
||||
INSERT INTO `system_settings` (`setting_key`, `setting_value`)
|
||||
VALUES ('db_version', '2.5.1')
|
||||
ON DUPLICATE KEY UPDATE `setting_value` = '2.5.1';
|
||||
|
||||
VALUES ('db_version', '2.6')
|
||||
ON DUPLICATE KEY UPDATE `setting_value` = '2.6';
|
||||
-- 控制台输出初始化结果(含版本号)
|
||||
SELECT CONCAT('数据库初始化完成!版本: v', (SELECT setting_value FROM system_settings WHERE setting_key = 'db_version')) AS message;
|
||||
|
||||
12
sql/upgrades/v2.6.sql
Normal file
12
sql/upgrades/v2.6.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
-- ===========================================
|
||||
-- 班级操行分管理系统 - v2.5.1 → v2.6 升级脚本
|
||||
-- 字符集: utf8mb4
|
||||
--
|
||||
-- 说明: v2.6 为功能增强版本,无数据库 schema 变更。
|
||||
-- 主要变更:
|
||||
-- 1. 历史记录页筛选面板重构(学生筛选移入面板、合并按钮移出面板)
|
||||
-- 2. 新增科目下拉筛选(通过 reason 前缀匹配)
|
||||
-- 3. 新增原因关键词搜索(reason_search 模糊匹配)
|
||||
-- 4. 科目管理删除后仅显示启用科目
|
||||
-- 5. 筛选面板展开/收起功能修复
|
||||
-- ===========================================
|
||||
@@ -29,6 +29,7 @@ $UPGRADE_VERSIONS = [
|
||||
'2.4' => __DIR__ . '/sql/upgrades/v2.4.sql',
|
||||
'2.5' => __DIR__ . '/sql/upgrades/v2.5.sql',
|
||||
'2.5.1' => __DIR__ . '/sql/upgrades/v2.5.1.sql',
|
||||
'2.6' => __DIR__ . '/sql/upgrades/v2.6.sql',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user