v2.4更新
This commit is contained in:
@@ -160,7 +160,8 @@ class ConductModel:
|
|||||||
end_date: str = None,
|
end_date: str = None,
|
||||||
student_id: int = None,
|
student_id: int = None,
|
||||||
include_revoked: bool = True,
|
include_revoked: bool = True,
|
||||||
related_type: str = None
|
related_type: str = None,
|
||||||
|
reason_prefix: str = None
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
"""获取所有记录(班主任/班长专用)"""
|
"""获取所有记录(班主任/班长专用)"""
|
||||||
# 空字符串转为None
|
# 空字符串转为None
|
||||||
@@ -170,6 +171,8 @@ class ConductModel:
|
|||||||
end_date = None
|
end_date = None
|
||||||
if related_type == "":
|
if related_type == "":
|
||||||
related_type = None
|
related_type = None
|
||||||
|
if reason_prefix == "":
|
||||||
|
reason_prefix = None
|
||||||
sql = """
|
sql = """
|
||||||
SELECT cr.*, s.name as student_name, s.student_no, u.real_name as recorder_name,
|
SELECT cr.*, s.name as student_name, s.student_no, u.real_name as recorder_name,
|
||||||
ru.real_name as revoker_name
|
ru.real_name as revoker_name
|
||||||
@@ -199,6 +202,10 @@ class ConductModel:
|
|||||||
sql += " AND cr.related_type = %s"
|
sql += " AND cr.related_type = %s"
|
||||||
params.append(related_type)
|
params.append(related_type)
|
||||||
|
|
||||||
|
if reason_prefix:
|
||||||
|
sql += " AND cr.reason LIKE %s"
|
||||||
|
params.append(f"{reason_prefix}%")
|
||||||
|
|
||||||
sql += " ORDER BY cr.created_at DESC LIMIT %s OFFSET %s"
|
sql += " ORDER BY cr.created_at DESC LIMIT %s OFFSET %s"
|
||||||
params.extend([limit, offset])
|
params.extend([limit, offset])
|
||||||
|
|
||||||
@@ -210,6 +217,7 @@ class ConductModel:
|
|||||||
start_date: str = None,
|
start_date: str = None,
|
||||||
end_date: str = None,
|
end_date: str = None,
|
||||||
related_type: str = None,
|
related_type: str = None,
|
||||||
|
reason_prefix: str = None,
|
||||||
page: int = 1,
|
page: int = 1,
|
||||||
page_size: int = 20
|
page_size: int = 20
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
@@ -220,6 +228,8 @@ class ConductModel:
|
|||||||
end_date = None
|
end_date = None
|
||||||
if related_type == "":
|
if related_type == "":
|
||||||
related_type = None
|
related_type = None
|
||||||
|
if reason_prefix == "":
|
||||||
|
reason_prefix = None
|
||||||
|
|
||||||
conditions = ["cr.is_revoked = 0"]
|
conditions = ["cr.is_revoked = 0"]
|
||||||
params = []
|
params = []
|
||||||
@@ -236,6 +246,9 @@ class ConductModel:
|
|||||||
if related_type:
|
if related_type:
|
||||||
conditions.append("cr.related_type = %s")
|
conditions.append("cr.related_type = %s")
|
||||||
params.append(related_type)
|
params.append(related_type)
|
||||||
|
if reason_prefix:
|
||||||
|
conditions.append("cr.reason LIKE %s")
|
||||||
|
params.append(f"{reason_prefix}%")
|
||||||
|
|
||||||
where_clause = " AND ".join(conditions)
|
where_clause = " AND ".join(conditions)
|
||||||
|
|
||||||
|
|||||||
@@ -71,10 +71,8 @@ class SubjectModel:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def has_related_data(subject_id: int) -> bool:
|
async def has_related_data(subject_id: int) -> bool:
|
||||||
"""检查科目是否有关联数据(assignments表)"""
|
"""检查科目是否有关联数据"""
|
||||||
sql = "SELECT COUNT(*) as cnt FROM assignments WHERE subject_id = %s"
|
return False
|
||||||
result = await execute_one(sql, (subject_id,))
|
|
||||||
return result['cnt'] > 0
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def delete(subject_id: int) -> bool:
|
async def delete(subject_id: int) -> bool:
|
||||||
|
|||||||
@@ -318,7 +318,8 @@ async def get_conduct_history(
|
|||||||
start_date: Optional[str] = None,
|
start_date: Optional[str] = None,
|
||||||
end_date: Optional[str] = None,
|
end_date: Optional[str] = None,
|
||||||
grouped: bool = Query(False),
|
grouped: bool = Query(False),
|
||||||
related_type: Optional[str] = None
|
related_type: Optional[str] = None,
|
||||||
|
reason_prefix: Optional[str] = None
|
||||||
):
|
):
|
||||||
"""获取操行分历史记录"""
|
"""获取操行分历史记录"""
|
||||||
try:
|
try:
|
||||||
@@ -333,7 +334,8 @@ async def get_conduct_history(
|
|||||||
start_date=start_date,
|
start_date=start_date,
|
||||||
end_date=end_date,
|
end_date=end_date,
|
||||||
grouped=grouped,
|
grouped=grouped,
|
||||||
related_type=related_type
|
related_type=related_type,
|
||||||
|
reason_prefix=reason_prefix
|
||||||
)
|
)
|
||||||
return success_response(data=result)
|
return success_response(data=result)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ ALL_VERSIONS = {
|
|||||||
'2.1': 'v2.1.sql',
|
'2.1': 'v2.1.sql',
|
||||||
'2.2': 'v2.2.sql',
|
'2.2': 'v2.2.sql',
|
||||||
'2.3': 'v2.3.sql',
|
'2.3': 'v2.3.sql',
|
||||||
|
'2.4': 'v2.4.sql',
|
||||||
}
|
}
|
||||||
|
|
||||||
# 版本特征标记(按优先级从高到低)
|
# 版本特征标记(按优先级从高到低)
|
||||||
|
|||||||
@@ -212,7 +212,8 @@ class ConductService:
|
|||||||
start_date: Optional[str] = None,
|
start_date: Optional[str] = None,
|
||||||
end_date: Optional[str] = None,
|
end_date: Optional[str] = None,
|
||||||
grouped: bool = False,
|
grouped: bool = False,
|
||||||
related_type: Optional[str] = None
|
related_type: Optional[str] = None,
|
||||||
|
reason_prefix: Optional[str] = None
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""获取历史记录"""
|
"""获取历史记录"""
|
||||||
# 空字符串转为None
|
# 空字符串转为None
|
||||||
@@ -222,6 +223,8 @@ class ConductService:
|
|||||||
end_date = None
|
end_date = None
|
||||||
if related_type == "":
|
if related_type == "":
|
||||||
related_type = None
|
related_type = None
|
||||||
|
if reason_prefix == "":
|
||||||
|
reason_prefix = None
|
||||||
if related_type and related_type not in ('manual', 'homework', 'attendance'):
|
if related_type and related_type not in ('manual', 'homework', 'attendance'):
|
||||||
return {"records": [], "page": page, "page_size": page_size, "total": 0, "total_pages": 0}
|
return {"records": [], "page": page, "page_size": page_size, "total": 0, "total_pages": 0}
|
||||||
|
|
||||||
@@ -236,6 +239,7 @@ class ConductService:
|
|||||||
start_date=start_date,
|
start_date=start_date,
|
||||||
end_date=end_date,
|
end_date=end_date,
|
||||||
related_type=related_type,
|
related_type=related_type,
|
||||||
|
reason_prefix=reason_prefix,
|
||||||
page=page,
|
page=page,
|
||||||
page_size=page_size
|
page_size=page_size
|
||||||
)
|
)
|
||||||
@@ -246,7 +250,8 @@ class ConductService:
|
|||||||
start_date=start_date,
|
start_date=start_date,
|
||||||
end_date=end_date,
|
end_date=end_date,
|
||||||
student_id=student_id,
|
student_id=student_id,
|
||||||
related_type=related_type
|
related_type=related_type,
|
||||||
|
reason_prefix=reason_prefix
|
||||||
)
|
)
|
||||||
|
|
||||||
# 获取总数
|
# 获取总数
|
||||||
@@ -265,6 +270,9 @@ class ConductService:
|
|||||||
if related_type:
|
if related_type:
|
||||||
count_conditions.append("cr.related_type = %s")
|
count_conditions.append("cr.related_type = %s")
|
||||||
count_params.append(related_type)
|
count_params.append(related_type)
|
||||||
|
if reason_prefix:
|
||||||
|
count_conditions.append("cr.reason LIKE %s")
|
||||||
|
count_params.append(f"{reason_prefix}%")
|
||||||
count_where = " AND ".join(count_conditions)
|
count_where = " AND ".join(count_conditions)
|
||||||
count_sql = f"""
|
count_sql = f"""
|
||||||
SELECT COUNT(*) as total FROM conduct_records cr
|
SELECT COUNT(*) as total FROM conduct_records cr
|
||||||
|
|||||||
@@ -78,10 +78,14 @@ include __DIR__ . '/../includes/header.php';
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>扣分类型</label>
|
<label>扣分类型</label>
|
||||||
<div class="deduction-types">
|
<div class="deduction-types" style="display: flex; flex-wrap: wrap; gap: 6px;">
|
||||||
<button type="button" class="btn btn-sm" onclick="selectDeductionType(null, '卫生')">卫生</button>
|
<button type="button" class="btn btn-sm" onclick="selectDeductionType(null, '卫生')">卫生</button>
|
||||||
<button type="button" class="btn btn-sm" onclick="selectDeductionType(null, '课堂')">课堂</button>
|
<button type="button" class="btn btn-sm" onclick="selectDeductionType(null, '课堂')">课堂</button>
|
||||||
<button type="button" class="btn btn-sm" onclick="selectDeductionType(null, '纪律')">纪律</button>
|
<button type="button" class="btn btn-sm" onclick="selectDeductionType(null, '纪律')">纪律</button>
|
||||||
|
<button type="button" class="btn btn-sm" onclick="selectDeductionType(null, '作业')">作业</button>
|
||||||
|
<button type="button" class="btn btn-sm" onclick="selectDeductionType(null, '考勤')">考勤</button>
|
||||||
|
<button type="button" class="btn btn-sm" onclick="selectDeductionType(null, '劳动')">劳动</button>
|
||||||
|
<button type="button" class="btn btn-sm" onclick="selectDeductionType(null, '志愿')">志愿</button>
|
||||||
<button type="button" class="btn btn-sm" onclick="selectDeductionType(0, '')">自定义</button>
|
<button type="button" class="btn btn-sm" onclick="selectDeductionType(0, '')">自定义</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,11 +43,15 @@ include __DIR__ . '/../includes/header.php';
|
|||||||
</div>
|
</div>
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
<label>扣分类型</label>
|
<label>扣分类型</label>
|
||||||
<select id="historyRelatedType">
|
<select id="historyReasonFilter">
|
||||||
<option value="">全部类型</option>
|
<option value="">全部类型</option>
|
||||||
<option value="manual">手动加减分</option>
|
<option value="卫生">卫生</option>
|
||||||
<option value="homework">作业扣分</option>
|
<option value="课堂">课堂</option>
|
||||||
<option value="attendance">考勤扣分</option>
|
<option value="纪律">纪律</option>
|
||||||
|
<option value="作业">作业</option>
|
||||||
|
<option value="考勤">考勤</option>
|
||||||
|
<option value="劳动">劳动</option>
|
||||||
|
<option value="志愿">志愿</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary" onclick="loadHistory(1)">查询</button>
|
<button class="btn btn-primary" onclick="loadHistory(1)">查询</button>
|
||||||
@@ -70,7 +74,7 @@ include __DIR__ . '/../includes/header.php';
|
|||||||
<th>学生</th>
|
<th>学生</th>
|
||||||
<th>分数变动</th>
|
<th>分数变动</th>
|
||||||
<th>原因</th>
|
<th>原因</th>
|
||||||
<th>操作人</th>
|
<th style="white-space: nowrap; min-width: 80px;">操作人</th>
|
||||||
<?php if ($role === '班主任' || $role === '班长' || $role === '考勤委员'): ?>
|
<?php if ($role === '班主任' || $role === '班长' || $role === '考勤委员'): ?>
|
||||||
<th>操作</th>
|
<th>操作</th>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ include __DIR__ . '/../includes/header.php';
|
|||||||
<!-- 科目管理折叠面板 -->
|
<!-- 科目管理折叠面板 -->
|
||||||
<div class="card collapsible-card" style="margin-bottom: 20px;">
|
<div class="card collapsible-card" style="margin-bottom: 20px;">
|
||||||
<div class="collapsible-header" id="subjectPanelHeader">
|
<div class="collapsible-header" id="subjectPanelHeader">
|
||||||
<h3 style="margin: 0; font-size: 16px;">📚 科目管理</h3>
|
<h3 style="margin: 0; font-size: 16px;">科目管理</h3>
|
||||||
<span id="subjectPanelToggle" class="toggle-icon">▶ 展开</span>
|
<span id="subjectPanelToggle" class="toggle-icon">▶ 展开</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="subjectPanelContent" class="collapsible-content">
|
<div id="subjectPanelContent" class="collapsible-content">
|
||||||
@@ -89,9 +89,12 @@ include __DIR__ . '/../includes/header.php';
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>扣分类型</label>
|
<label>扣分类型</label>
|
||||||
<div class="deduction-types">
|
<div class="deduction-types" style="display: flex; flex-wrap: wrap; gap: 6px;">
|
||||||
<button type="button" class="btn btn-sm" onclick="selectDeductionType(-window.DEDUCTION_HOMEWORK_NOT_SUBMIT, '未交作业')">未交作业(-<span class="hw-not-submit"></span>分)</button>
|
<button type="button" class="btn btn-sm" onclick="selectDeductionType(-window.DEDUCTION_HOMEWORK_NOT_SUBMIT, '未交作业')">未交作业(-<span class="hw-not-submit"></span>分)</button>
|
||||||
<button type="button" class="btn btn-sm" onclick="selectDeductionType(-window.DEDUCTION_HOMEWORK_LATE, '迟交作业')">迟交作业(-<span class="hw-late"></span>分)</button>
|
<button type="button" class="btn btn-sm" onclick="selectDeductionType(-window.DEDUCTION_HOMEWORK_LATE, '迟交作业')">迟交作业(-<span class="hw-late"></span>分)</button>
|
||||||
|
<button type="button" class="btn btn-sm" onclick="selectDeductionType(null, '作业未完成')">作业未完成</button>
|
||||||
|
<button type="button" class="btn btn-sm" onclick="selectDeductionType(null, '作业抄袭')">作业抄袭</button>
|
||||||
|
<button type="button" class="btn btn-sm" onclick="selectDeductionType(null, '作业态度')">作业态度</button>
|
||||||
<button type="button" class="btn btn-sm" onclick="selectDeductionType(0, '')">自定义</button>
|
<button type="button" class="btn btn-sm" onclick="selectDeductionType(0, '')">自定义</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ async function loadHistory(page = 1) {
|
|||||||
const startDate = document.getElementById('historyStartDate').value;
|
const startDate = document.getElementById('historyStartDate').value;
|
||||||
const endDate = document.getElementById('historyEndDate').value;
|
const endDate = document.getElementById('historyEndDate').value;
|
||||||
const studentId = document.getElementById('historyStudentId').value;
|
const studentId = document.getElementById('historyStudentId').value;
|
||||||
const relatedType = document.getElementById('historyRelatedType').value;
|
const reasonFilter = document.getElementById('historyReasonFilter').value;
|
||||||
const isGrouped = document.getElementById('historyGrouped').checked;
|
const isGrouped = document.getElementById('historyGrouped').checked;
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
@@ -40,17 +40,18 @@ async function loadHistory(page = 1) {
|
|||||||
end_date: endDate
|
end_date: endDate
|
||||||
};
|
};
|
||||||
if (studentId) params.student_id = studentId;
|
if (studentId) params.student_id = studentId;
|
||||||
if (relatedType) params.related_type = relatedType;
|
if (reasonFilter) params.reason_prefix = reasonFilter;
|
||||||
if (isGrouped) params.grouped = true;
|
if (isGrouped) params.grouped = true;
|
||||||
|
|
||||||
const res = await apiGet('/api/admin/conduct/history', params);
|
const res = await apiGet('/api/admin/conduct/history', params);
|
||||||
|
|
||||||
if (res && res.success) {
|
if (res && res.success) {
|
||||||
|
const nowrapStyle = ' style="white-space: nowrap; min-width: 80px;"';
|
||||||
let headHtml = '';
|
let headHtml = '';
|
||||||
if (isGrouped) {
|
if (isGrouped) {
|
||||||
headHtml = '<th>时间</th><th>原因</th><th>分值</th><th>操作人</th><th>涉及学生</th>';
|
headHtml = '<th>时间</th><th>原因</th><th>分值</th><th' + nowrapStyle + '>操作人</th><th>涉及学生</th>';
|
||||||
} else {
|
} else {
|
||||||
headHtml = '<th>时间</th><th>学生</th><th>分数变动</th><th>原因</th><th>操作人</th>';
|
headHtml = '<th>时间</th><th>学生</th><th>分数变动</th><th>原因</th><th' + nowrapStyle + '>操作人</th>';
|
||||||
if (role === '班主任' || role === '班长' || role === '考勤委员') {
|
if (role === '班主任' || role === '班长' || role === '考勤委员') {
|
||||||
headHtml += '<th>操作</th>';
|
headHtml += '<th>操作</th>';
|
||||||
}
|
}
|
||||||
@@ -61,18 +62,13 @@ async function loadHistory(page = 1) {
|
|||||||
if (isGrouped) {
|
if (isGrouped) {
|
||||||
res.data.records.forEach(record => {
|
res.data.records.forEach(record => {
|
||||||
const pointsClass = record.points_change > 0 ? 'plus' : 'minus';
|
const pointsClass = record.points_change > 0 ? 'plus' : 'minus';
|
||||||
const names = record.student_names ? record.student_names.split(', ') : [];
|
const names = record.student_names || '';
|
||||||
let tagsHtml = '<div class="student-tags-container">';
|
|
||||||
names.forEach(name => {
|
|
||||||
tagsHtml += `<span class="student-tag">${escapeHtml(name)}</span>`;
|
|
||||||
});
|
|
||||||
tagsHtml += '</div>';
|
|
||||||
html += `<tr>
|
html += `<tr>
|
||||||
<td>${formatDateTime(record.created_at)}</td>
|
<td>${formatDateTime(record.created_at)}</td>
|
||||||
<td class="preserve-newlines">${escapeHtml(record.reason)}</td>
|
<td class="preserve-newlines">${escapeHtml(record.reason)}</td>
|
||||||
<td class="${pointsClass}">${record.points_change > 0 ? '+' : ''}${record.points_change}×${record.student_count}</td>
|
<td class="${pointsClass}">${record.points_change > 0 ? '+' : ''}${record.points_change}×${record.student_count}</td>
|
||||||
<td>${escapeHtml(record.recorder_name || '')}</td>
|
<td>${escapeHtml(record.recorder_name || '')}</td>
|
||||||
<td>${tagsHtml}</td>
|
<td style="white-space: nowrap;">${escapeHtml(names)}</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
});
|
});
|
||||||
if (res.data.records.length === 0) {
|
if (res.data.records.length === 0) {
|
||||||
@@ -141,12 +137,12 @@ async function exportHistoryRecords() {
|
|||||||
showToast('正在导出历史记录...', 'info');
|
showToast('正在导出历史记录...', 'info');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const relatedType = document.getElementById('historyRelatedType').value;
|
const reasonFilter = document.getElementById('historyReasonFilter').value;
|
||||||
const params = { page: 1, page_size: 1000 };
|
const params = { page: 1, page_size: 1000 };
|
||||||
if (startDate) params.start_date = startDate;
|
if (startDate) params.start_date = startDate;
|
||||||
if (endDate) params.end_date = endDate;
|
if (endDate) params.end_date = endDate;
|
||||||
if (studentId) params.student_id = studentId;
|
if (studentId) params.student_id = studentId;
|
||||||
if (relatedType) params.related_type = relatedType;
|
if (reasonFilter) params.reason_prefix = reasonFilter;
|
||||||
|
|
||||||
const res = await apiGet('/api/admin/conduct/history', params);
|
const res = await apiGet('/api/admin/conduct/history', params);
|
||||||
if (res && res.success && res.data.records) {
|
if (res && res.success && res.data.records) {
|
||||||
|
|||||||
@@ -232,8 +232,8 @@ INSERT IGNORE INTO `subjects` (`subject_name`, `subject_code`, `sort_order`) VAL
|
|||||||
|
|
||||||
-- 初始化系统版本号
|
-- 初始化系统版本号
|
||||||
INSERT INTO `system_settings` (`setting_key`, `setting_value`)
|
INSERT INTO `system_settings` (`setting_key`, `setting_value`)
|
||||||
VALUES ('db_version', '2.3')
|
VALUES ('db_version', '2.4')
|
||||||
ON DUPLICATE KEY UPDATE `setting_value` = '2.3';
|
ON DUPLICATE KEY UPDATE `setting_value` = '2.4';
|
||||||
|
|
||||||
-- 控制台输出初始化结果(含版本号)
|
-- 控制台输出初始化结果(含版本号)
|
||||||
SELECT CONCAT('数据库初始化完成!版本: v', (SELECT setting_value FROM system_settings WHERE setting_key = 'db_version')) AS message;
|
SELECT CONCAT('数据库初始化完成!版本: v', (SELECT setting_value FROM system_settings WHERE setting_key = 'db_version')) AS message;
|
||||||
|
|||||||
11
sql/upgrades/v2.4.sql
Normal file
11
sql/upgrades/v2.4.sql
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
-- ===========================================
|
||||||
|
-- 班级操行分管理系统 - v2.3 → v2.4 升级脚本
|
||||||
|
-- 字符集: utf8mb4
|
||||||
|
--
|
||||||
|
-- 说明: v2.4 为UI优化与功能完善版本,无数据库 schema 变更。
|
||||||
|
-- 主要变更:
|
||||||
|
-- 1. 操行分扣分类型扩展(卫生/课堂/纪律/作业/考勤/劳动/志愿)
|
||||||
|
-- 2. 历史记录支持按扣分原因类型前缀筛选
|
||||||
|
-- 3. 修复科目删除失败问题
|
||||||
|
-- 4. 优化历史记录合并记录UI
|
||||||
|
-- ===========================================
|
||||||
Reference in New Issue
Block a user