diff --git a/README.md b/README.md
index 470281b..d82b9a5 100644
--- a/README.md
+++ b/README.md
@@ -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 |
## 许可证
diff --git a/VERSION b/VERSION
index 73462a5..5154b3f 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.5.1
+2.6
diff --git a/backend/models/conduct.py b/backend/models/conduct.py
index 1e0c685..2cefb0c 100644
--- a/backend/models/conduct.py
+++ b/backend/models/conduct.py
@@ -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)
diff --git a/backend/routes/admin.py b/backend/routes/admin.py
index bda3b11..8e4e7b0 100644
--- a/backend/routes/admin.py
+++ b/backend/routes/admin.py
@@ -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:
diff --git a/backend/routes/upgrade.py b/backend/routes/upgrade.py
index d12c8b1..bb76e34 100644
--- a/backend/routes/upgrade.py
+++ b/backend/routes/upgrade.py
@@ -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'),
diff --git a/backend/services/conduct_service.py b/backend/services/conduct_service.py
index 25a2ee3..0ecb8cd 100644
--- a/backend/services/conduct_service.py
+++ b/backend/services/conduct_service.py
@@ -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)
diff --git a/frontend/admin/history.php b/frontend/admin/history.php
index fe52d53..a1c0a58 100644
--- a/frontend/admin/history.php
+++ b/frontend/admin/history.php
@@ -27,14 +27,11 @@ include __DIR__ . '/../includes/header.php';
-
-
-
-
+
@@ -42,6 +39,22 @@ include __DIR__ . '/../includes/header.php';
+
+
+
+
+
+
+
+
+
+
+
+
@@ -74,11 +87,6 @@ include __DIR__ . '/../includes/header.php';
-
-
-
diff --git a/frontend/assets/js/history.js b/frontend/assets/js/history.js
index 905546c..7e90542 100644
--- a/frontend/assets/js/history.js
+++ b/frontend/assets/js/history.js
@@ -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 = '';
+ res.data.subjects.forEach(s => {
+ html += '';
+ });
+ 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;
})();
diff --git a/frontend/assets/js/homework-manage.js b/frontend/assets/js/homework-manage.js
index 4b27eab..e4256ad 100644
--- a/frontend/assets/js/homework-manage.js
+++ b/frontend/assets/js/homework-manage.js
@@ -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 || [];
diff --git a/sql/init.sql b/sql/init.sql
index 2a7db7d..542ef77 100644
--- a/sql/init.sql
+++ b/sql/init.sql
@@ -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;
diff --git a/sql/upgrades/v2.6.sql b/sql/upgrades/v2.6.sql
new file mode 100644
index 0000000..94617dd
--- /dev/null
+++ b/sql/upgrades/v2.6.sql
@@ -0,0 +1,12 @@
+-- ===========================================
+-- 班级操行分管理系统 - v2.5.1 → v2.6 升级脚本
+-- 字符集: utf8mb4
+--
+-- 说明: v2.6 为功能增强版本,无数据库 schema 变更。
+-- 主要变更:
+-- 1. 历史记录页筛选面板重构(学生筛选移入面板、合并按钮移出面板)
+-- 2. 新增科目下拉筛选(通过 reason 前缀匹配)
+-- 3. 新增原因关键词搜索(reason_search 模糊匹配)
+-- 4. 科目管理删除后仅显示启用科目
+-- 5. 筛选面板展开/收起功能修复
+-- ===========================================
diff --git a/upgrade.php b/upgrade.php
index 7d73908..ccb55b8 100644
--- a/upgrade.php
+++ b/upgrade.php
@@ -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',
];
/**