教学评价闭环:

新增同行/督导评价、教师改进计划、教师完成申报、管理员复评与退回机制。
闭环数据按教学班和学期归档;管理端可追踪计划数、已复评数及未闭环事项。
保留原学生评教流程;同行不能评价本人教学班,所有查询与写入均受学院数据范围限制。
This commit is contained in:
biss committed 2026-08-18 10:28:36 +08:00
1 parent 390d0b123f
commit 73f870221b
9 files changed
+8314 -103

No files matched your search

+77
View File
@@ -183,6 +183,58 @@ async function loadTeacherResults() {
finally { teacherLoading.value = false }
}
// ── Teaching quality closed loop ──
const closureTasks = ref<any[]>([])
const closurePlans = ref<any[]>([])
const closureTracking = ref<any>(null)
const observationDialog = ref(false)
const planDialog = ref(false)
const reviewDialog = ref(false)
const reviewingPlan = ref<any>(null)
const observationForm = reactive({ teachingTaskId: '', source: 1, score: 80, strengths: '', issues: '', recommendation: '' })
const planForm = reactive({ teachingTaskId: '', problemSummary: '', actions: '', dueDate: '' })
const reviewForm = reactive({ isApproved: true, comment: '' })
const canReview = computed(() => roles.value.some((r: string) => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Leader'].includes(r)))
async function loadClosure() {
try {
const calls: Promise<any>[] = []
if (isStaff.value || isTeacher.value) calls.push(http.get('/evaluations/closure/tasks'))
if (roles.value.includes('Teacher')) calls.push(http.get('/evaluations/closure/my-plans'))
if (canReview.value) calls.push(http.get('/evaluations/closure/tracking'))
const responses = await Promise.all(calls)
let i = 0
if (isStaff.value || isTeacher.value) closureTasks.value = responses[i++].data
if (roles.value.includes('Teacher')) closurePlans.value = responses[i++].data
if (canReview.value) closureTracking.value = responses[i].data
} catch (e) { ElMessage.error(apiErrorMessage(e)) }
}
function openObservation(task: any) {
Object.assign(observationForm, { teachingTaskId: task.id, source: roles.value.includes('Teacher') ? 1 : 2, score: 80, strengths: '', issues: '', recommendation: '' })
observationDialog.value = true
}
async function submitObservation() {
try { await http.post('/evaluations/closure/observations', observationForm); ElMessage.success('评价已纳入教学改进闭环'); observationDialog.value = false; await loadClosure() }
catch (e) { ElMessage.error(apiErrorMessage(e)) }
}
function openPlan(task?: any) {
Object.assign(planForm, { teachingTaskId: task?.id ?? '', problemSummary: '', actions: '', dueDate: '' }); planDialog.value = true
}
async function submitPlan() {
try { await http.post('/evaluations/closure/plans', { ...planForm, dueDate: planForm.dueDate || null }); ElMessage.success('改进计划已提交'); planDialog.value = false; await loadClosure() }
catch (e) { ElMessage.error(apiErrorMessage(e)) }
}
async function completePlan(id: string) {
try { await http.post(`/evaluations/closure/plans/${id}/complete`); ElMessage.success('已提交复评'); await loadClosure() }
catch (e) { ElMessage.error(apiErrorMessage(e)) }
}
function openReview(plan: any) { reviewingPlan.value = plan; Object.assign(reviewForm, { isApproved: true, comment: '' }); reviewDialog.value = true }
async function submitReview() {
try { await http.post(`/evaluations/closure/plans/${reviewingPlan.value.id}/review`, reviewForm); ElMessage.success('复评已完成'); reviewDialog.value = false; await loadClosure() }
catch (e) { ElMessage.error(apiErrorMessage(e)) }
}
function planStatus(status: string) { return ({ Draft: '草稿', Submitted: '待落实', InProgress: '进行中', Completed: '待复评', Rework: '需返工', Reviewed: '复评通过' } as Record<string, string>)[status] ?? status }
// ── Init ──
onMounted(async () => {
if (isAdmin.value || isStaff.value) {
@@ -192,6 +244,7 @@ onMounted(async () => {
}
if (isStudent.value) await loadStudentTasks()
if (isTeacher.value) await loadTeacherResults()
if (!isStudent.value) await loadClosure()
})
</script>
@@ -390,7 +443,28 @@ onMounted(async () => {
</article>
</div>
</section>
<section class="data-card closure-card">
<div class="closure-heading"><div><span class="section-kicker">IMPROVEMENT LOOP</span><h3>教学改进计划</h3><p>把评价结论转为可复评的行动。</p></div><el-button type="primary" :icon="Plus" @click="openPlan()">新建计划</el-button></div>
<el-empty v-if="closurePlans.length === 0" description="暂无改进计划;可针对您承担的教学班建立计划" />
<el-table v-else :data="closurePlans" size="small"><el-table-column prop="termName" label="学期" width="120"/><el-table-column prop="courseName" label="课程" min-width="120"/><el-table-column prop="problemSummary" label="问题概述" min-width="180" show-overflow-tooltip/><el-table-column prop="dueDate" label="完成期限" width="110"/><el-table-column label="状态" width="100"><template #default="{ row }"><el-tag>{{ planStatus(row.status) }}</el-tag></template></el-table-column><el-table-column label="操作" width="90"><template #default="{ row }"><el-button v-if="['Submitted','InProgress','Rework'].includes(row.status)" text type="primary" @click="completePlan(row.id)">提交复评</el-button></template></el-table-column></el-table>
</section>
</template>
<template v-if="!isStudent && (isStaff || isTeacher || canReview)">
<section class="data-card closure-card">
<div class="closure-heading"><div><span class="section-kicker">OBSERVATION</span><h3>同行 / 督导评价</h3><p>观察记录与学生评教并行,避免单一分数决定教学判断。</p></div></div>
<el-table :data="closureTasks" size="small" empty-text="当前学期暂无可评价教学班"><el-table-column prop="termName" label="学期" width="110"/><el-table-column prop="courseName" label="课程" min-width="140"/><el-table-column prop="taskNumber" label="教学班" width="110"/><el-table-column label="教师" min-width="120"><template #default="{ row }">{{ row.teacherNames?.join('、') }}</template></el-table-column><el-table-column label="操作" width="100"><template #default="{ row }"><el-button :disabled="row.isMyTeaching" text type="primary" @click="openObservation(row)">{{ row.isMyTeaching ? '本人教学班' : '去评价' }}</el-button></template></el-table-column></el-table>
</section>
<section v-if="canReview" class="data-card closure-card">
<div class="closure-heading"><div><span class="section-kicker">SEMESTER TRACKING</span><h3>按学期闭环追踪</h3><p>评价、计划、复评均按学期沉淀,可识别未闭环事项。</p></div><div class="metrics-strip"><span class="metric-chip"><b>{{ closureTracking?.totalPlans ?? 0 }}</b> 项计划</span><span class="metric-chip"><b>{{ closureTracking?.reviewedPlans ?? 0 }}</b> 已复评</span></div></div>
<el-table :data="closureTracking?.plans ?? []" size="small" empty-text="暂无改进计划"><el-table-column prop="termName" label="学期" width="120"/><el-table-column prop="teacherName" label="教师" width="100"/><el-table-column prop="courseName" label="课程" min-width="120"/><el-table-column prop="problemSummary" label="改进问题" min-width="170" show-overflow-tooltip/><el-table-column label="状态" width="100"><template #default="{ row }"><el-tag>{{ planStatus(row.status) }}</el-tag></template></el-table-column><el-table-column prop="latestReviewComment" label="最近复评意见" min-width="160" show-overflow-tooltip/><el-table-column label="操作" width="90"><template #default="{ row }"><el-button v-if="row.status === 'Completed'" text type="primary" @click="openReview(row)">复评</el-button></template></el-table-column></el-table>
</section>
</template>
<el-dialog v-model="observationDialog" title="提交同行 / 督导评价" width="620px"><el-form label-position="top"><el-form-item label="评价类型"><el-radio-group v-model="observationForm.source"><el-radio :value="1" :disabled="!roles.includes('Teacher')">同行评价</el-radio><el-radio :value="2" :disabled="!canReview">督导评价</el-radio></el-radio-group></el-form-item><el-form-item label="综合评分"><el-slider v-model="observationForm.score" :max="100" show-input /></el-form-item><el-form-item label="教学亮点"><el-input v-model="observationForm.strengths" type="textarea" :rows="2" /></el-form-item><el-form-item label="待改进问题"><el-input v-model="observationForm.issues" type="textarea" :rows="2" /></el-form-item><el-form-item label="改进建议" required><el-input v-model="observationForm.recommendation" type="textarea" :rows="3" /></el-form-item></el-form><template #footer><el-button @click="observationDialog=false">取消</el-button><el-button type="primary" @click="submitObservation">提交评价</el-button></template></el-dialog>
<el-dialog v-model="planDialog" title="新建教学改进计划" width="620px"><el-form label-position="top"><el-form-item label="教学班" required><el-select v-model="planForm.teachingTaskId" filterable style="width:100%"><el-option v-for="task in closureTasks.filter((x:any) => x.isMyTeaching)" :key="task.id" :value="task.id" :label="`${task.courseName} · ${task.taskNumber}`" /></el-select></el-form-item><el-form-item label="问题概述" required><el-input v-model="planForm.problemSummary" type="textarea" :rows="2" /></el-form-item><el-form-item label="改进措施与证据" required><el-input v-model="planForm.actions" type="textarea" :rows="4" placeholder="说明行动、完成标准和可供复评的证据" /></el-form-item><el-form-item label="完成期限"><el-date-picker v-model="planForm.dueDate" type="date" value-format="YYYY-MM-DD" /></el-form-item></el-form><template #footer><el-button @click="planDialog=false">取消</el-button><el-button type="primary" @click="submitPlan">提交计划</el-button></template></el-dialog>
<el-dialog v-model="reviewDialog" title="改进计划复评" width="560px"><p v-if="reviewingPlan">{{ reviewingPlan.courseName }} · {{ reviewingPlan.teacherName }}</p><el-radio-group v-model="reviewForm.isApproved"><el-radio :value="true">通过,形成闭环</el-radio><el-radio :value="false">退回补充改进</el-radio></el-radio-group><el-input v-model="reviewForm.comment" type="textarea" :rows="4" style="margin-top:16px" placeholder="复评意见(必填)"/><template #footer><el-button @click="reviewDialog=false">取消</el-button><el-button type="primary" @click="submitReview">提交复评</el-button></template></el-dialog>
</div>
</template>
@@ -436,4 +510,7 @@ onMounted(async () => {
.result-dim-item b small { font-size: 11px; color: #909399; font-weight: 400 }
.form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px }
.closure-card { margin-top: 18px }
.closure-heading { display:flex; justify-content:space-between; align-items:flex-start; gap:16px; margin-bottom:16px }
.closure-heading h3 { margin: 2px 0 4px; font-size:18px }.closure-heading p { margin:0; color:#606266; font-size:13px }
</style>