“教学班成绩分析”页面新增“定时刷新设置”,支持启停、刷新间隔、单次处理上限、查看上次/下次扫描时间。
配置保存在独立数据库表 CourseGradeStatisticsRefreshSettings,不是修改 appsettings.json。 后台每 10 秒读取配置,仅到期扫描;成绩录入、导入、审批时不再立即创建统计任务。 扫描发现过期数据后,仍通过持久任务、Outbox 和 RabbitMQ 执行;Redis统计缓存由处理任务统一刷新。
This commit is contained in:
1 parent
e8261714da
commit
cd073885b5
17 files changed
+7661
-46
No files matched your search
@@ -6,7 +6,7 @@ import { useAuthStore } from '../stores/auth'
|
||||
import { academicTermLabel, academicTermOptionClass, defaultAcademicTermId } from '../utils/academicTerms'
|
||||
|
||||
const auth = useAuthStore()
|
||||
const isSuperAdmin = computed(() => auth.user?.roles.includes('SuperAdmin'))
|
||||
const isWarningManager = computed(() => auth.user?.roles.some(r => ['SuperAdmin', 'AcademicAdmin'].includes(r)) ?? false)
|
||||
const isCounselor = computed(() => auth.user?.roles.includes('Counselor') && !auth.user?.roles.some(r => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin'].includes(r)))
|
||||
const isStudent = computed(() => auth.user?.roles.includes('Student') && !auth.user?.roles.some(r => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Counselor'].includes(r)))
|
||||
|
||||
@@ -43,11 +43,11 @@ const ruleRows = reactive([
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
if (isSuperAdmin.value && termId.value) {
|
||||
if (isWarningManager.value && termId.value) {
|
||||
const serverRules = (await http.get('/warnings/rules', { params: { academicTermId: termId.value } })).data
|
||||
// Merge server values into reactive rows
|
||||
for (const row of ruleRows) {
|
||||
const sr = serverRules.find((r: any) => r.type === row.type)
|
||||
const sr = serverRules.find((r: any) => Number(r.type) === row.type)
|
||||
if (sr) {
|
||||
row.threshold = sr.threshold
|
||||
row.isEnabled = sr.isEnabled
|
||||
@@ -62,7 +62,7 @@ async function load() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isSuperAdmin.value || isCounselor.value)
|
||||
if (isWarningManager.value || isCounselor.value)
|
||||
records.value = (await http.get('/warnings/records', { params: { academicTermId: termId.value || undefined, type: filterType.value || undefined } })).data
|
||||
if (isStudent.value) myWarnings.value = (await http.get('/warnings/my-warnings')).data
|
||||
} catch (e) { ElMessage.error(apiErrorMessage(e)) } finally { loading.value = false }
|
||||
@@ -72,6 +72,7 @@ async function saveRules() {
|
||||
try {
|
||||
const payload = ruleRows.map(r => ({ type: r.type, name: r.name, threshold: r.threshold, isEnabled: r.isEnabled, notifyStudent: r.notifyStudent, notifyCounselor: r.notifyCounselor, description: r.description, autoCheckEnabled: r.autoCheckEnabled, checkDayOfWeek: r.checkDayOfWeek, checkHour: r.checkHour, checkMinute: r.checkMinute }))
|
||||
await http.put('/warnings/rules', payload, { params: { academicTermId: termId.value } })
|
||||
await load()
|
||||
ElMessage.success('预警规则已保存')
|
||||
} catch (e) { ElMessage.error(apiErrorMessage(e)) }
|
||||
}
|
||||
@@ -102,7 +103,7 @@ onMounted(async () => {
|
||||
<template>
|
||||
<div class="page-stack warn-page">
|
||||
<section class="page-intro">
|
||||
<div><span class="section-kicker">ACADEMIC WARNING</span><h2>{{ isStudent ? '我的预警' : '学业预警' }}</h2><p>{{ isSuperAdmin ? '配置预警规则,执行检测,查看预警记录。' : isCounselor ? '查看所管学生的学业预警情况。' : '查看并确认您的学业预警通知。' }}</p></div>
|
||||
<div><span class="section-kicker">ACADEMIC WARNING</span><h2>{{ isStudent ? '我的预警' : '学业预警' }}</h2><p>{{ isWarningManager ? '配置预警规则,执行检测,查看预警记录。' : isCounselor ? '查看所管学生的学业预警情况。' : '查看并确认您的学业预警通知。' }}</p></div>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<el-select v-model="termId" clearable @change="load" style="width:240px"><el-option v-for="t in terms" :key="t.id" :label="academicTermLabel(t)" :value="t.id" :class="academicTermOptionClass(t)" /></el-select>
|
||||
<el-button :icon="Refresh" @click="load">刷新</el-button>
|
||||
@@ -110,7 +111,7 @@ onMounted(async () => {
|
||||
</section>
|
||||
|
||||
<!-- Admin: Rule config -->
|
||||
<section v-if="isSuperAdmin && termId" class="warn-rules">
|
||||
<section v-if="isWarningManager && termId" class="warn-rules">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
|
||||
<h3 style="margin:0">预警规则配置</h3>
|
||||
<div>
|
||||
@@ -160,7 +161,7 @@ onMounted(async () => {
|
||||
</section>
|
||||
|
||||
<!-- Records -->
|
||||
<section v-if="isSuperAdmin || isCounselor" v-loading="loading" class="warn-records">
|
||||
<section v-if="isWarningManager || isCounselor" v-loading="loading" class="warn-records">
|
||||
<div style="display:flex;gap:12px;align-items:center;margin-bottom:12px">
|
||||
<h3 style="margin:0">预警记录</h3>
|
||||
<el-select v-model="filterType" clearable placeholder="全部类型" @change="load" style="width:140px"><el-option v-for="(v,k) in typeLabels" :key="k" :label="v" :value="Number(k)" /></el-select>
|
||||
|
||||
Reference in new issue
Block a user