Files
Academic-Affairs-System/web/src/views/TeachingPreferencesView.vue
T
2026-07-24 19:35:49 +08:00

235 lines
9.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import { Plus, Refresh } from '@element-plus/icons-vue'
import http, { apiErrorMessage } from '../api/http'
import { useAuthStore } from '../stores/auth'
const auth = useAuthStore()
const isTeacher = computed(() => auth.user?.roles.includes('Teacher') ?? false)
const loading = ref(false)
const rows = ref<any[]>([])
const terms = ref<any[]>([])
const courses = ref<any[]>([])
const submitDialog = ref(false)
const reviewDialog = ref(false)
const form = reactive<Record<string, any>>({})
const reviewForm = reactive<Record<string, any>>({})
const filters = reactive({
academicTermId: undefined as string | undefined,
status: isTeacher.value ? undefined : 'Pending' as string | undefined,
})
const statusLabels: Record<string, string> = {
Pending: '待审核',
Approved: '已通过',
Rejected: '已驳回',
Withdrawn: '已撤回',
}
const statusTypes: Record<string, 'success' | 'warning' | 'danger' | 'info'> = {
Pending: 'warning',
Approved: 'success',
Rejected: 'danger',
Withdrawn: 'info',
}
async function load() {
loading.value = true
try {
const endpoint = isTeacher.value
? '/teacher-course-applications/mine'
: '/teacher-course-applications/reviews'
const { data } = await http.get(endpoint, { params: filters })
rows.value = data
} catch (error) {
ElMessage.error(apiErrorMessage(error))
} finally {
loading.value = false
}
}
function openSubmit() {
Object.assign(form, {
academicTermId: filters.academicTermId ??
terms.value.find((item) => item.isCurrent)?.id,
courseId: undefined,
statement: '',
})
submitDialog.value = true
}
async function submit() {
if (!form.academicTermId || !form.courseId) {
ElMessage.warning('请选择学期和课程。')
return
}
try {
await http.post('/teacher-course-applications/mine', form)
submitDialog.value = false
ElMessage.success('课程申报已提交,等待学院审核')
await load()
} catch (error) {
ElMessage.error(apiErrorMessage(error))
}
}
async function withdraw(row: any) {
try {
await ElMessageBox.confirm(`撤回“${row.courseName}”的授课申报?`, '撤回申报', {
type: 'warning',
})
await http.delete(`/teacher-course-applications/mine/${row.id}`)
await load()
} catch (error: any) {
if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
}
}
function openReview(row: any) {
Object.assign(reviewForm, {
id: row.id,
title: `${row.teacherName} · ${row.courseCode} ${row.courseName}`,
statement: row.statement,
status: 'Approved',
reviewComment: '',
})
reviewDialog.value = true
}
async function review() {
try {
await http.post(`/teacher-course-applications/${reviewForm.id}/review`, {
status: reviewForm.status,
reviewComment: reviewForm.reviewComment,
})
reviewDialog.value = false
ElMessage.success(reviewForm.status === 'Approved' ? '已审核通过' : '已驳回申报')
await load()
} catch (error) {
ElMessage.error(apiErrorMessage(error))
}
}
onMounted(async () => {
const requests: Promise<any>[] = [http.get('/base-data/terms')]
if (isTeacher.value) {
requests.push(http.get('/courses', {
params: { page: 1, pageSize: 100, isEnabled: true },
}))
}
const [termRes, courseRes] = await Promise.all(requests)
terms.value = termRes.data
courses.value = courseRes?.data.items ?? []
filters.academicTermId = terms.value.find((item) => item.isCurrent)?.id
await load()
})
</script>
<template>
<div class="page-stack">
<section class="page-intro">
<div>
<span class="section-kicker">TEACHING ELIGIBILITY</span>
<h2>{{ isTeacher ? '授课科目申报' : '授课资格审核' }}</h2>
<p v-if="isTeacher">在排课前申报本学期愿意承担的课程学院审核通过后才能分配公共课教学任务</p>
<p v-else>审核教师的授课科目申报通过后该教师进入相应学期和课程的可分配教师池</p>
</div>
<el-button v-if="isTeacher" type="primary" :icon="Plus" @click="openSubmit">
申报课程
</el-button>
</section>
<section class="eligibility-flow">
<div class="active"><span>教师</span><b>选择学期与科目</b></div>
<i></i>
<div><span>学院</span><b>审核授课资格</b></div>
<i></i>
<div><span>教务</span><b>合班并分配教师</b></div>
<i></i>
<div><span>排课</span><b>生成课表</b></div>
</section>
<section class="data-card">
<div class="filter-bar">
<el-select v-model="filters.academicTermId" clearable placeholder="全部学期" @change="load">
<el-option v-for="item in terms" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="filters.status" clearable placeholder="全部状态" @change="load">
<el-option v-for="(label, value) in statusLabels" :key="value" :label="label" :value="value" />
</el-select>
<el-button :icon="Refresh" @click="load">刷新</el-button>
<span> {{ rows.length }} </span>
</div>
<el-table v-loading="loading" :data="rows" class="data-table">
<el-table-column v-if="!isTeacher" label="申报教师" min-width="170">
<template #default="{ row }">
<div class="course-name"><b>{{ row.teacherName }}</b><span>{{ row.teacherNumber }} · {{ row.teacherCollegeName }}</span></div>
</template>
</el-table-column>
<el-table-column label="申报课程" min-width="220">
<template #default="{ row }">
<div class="course-name"><b>{{ row.courseName }}</b><span>{{ row.courseCode }} · {{ row.courseCollegeName || row.collegeName }}</span></div>
</template>
</el-table-column>
<el-table-column prop="termName" label="学期" min-width="180" />
<el-table-column label="申报说明" min-width="180">
<template #default="{ row }">{{ row.statement || '—' }}</template>
</el-table-column>
<el-table-column label="状态" width="100">
<template #default="{ row }"><el-tag :type="statusTypes[row.status]">{{ statusLabels[row.status] }}</el-tag></template>
</el-table-column>
<el-table-column label="审核意见" min-width="160">
<template #default="{ row }">{{ row.reviewComment || '—' }}</template>
</el-table-column>
<el-table-column label="操作" width="110" fixed="right">
<template #default="{ row }">
<el-button v-if="isTeacher && row.status === 'Pending'" link type="danger" @click="withdraw(row)">撤回</el-button>
<el-button v-if="!isTeacher && row.status === 'Pending'" link type="primary" @click="openReview(row)">审核</el-button>
<span v-if="row.status !== 'Pending'" class="muted-action">已处理</span>
</template>
</el-table-column>
<template #empty><el-empty :description="isTeacher ? '尚未申报授课科目' : '没有待处理的授课申报'" /></template>
</el-table>
</section>
<el-dialog v-model="submitDialog" title="申报授课科目" width="600px">
<el-form label-position="top">
<div class="form-grid">
<el-form-item label="学期" required>
<el-select v-model="form.academicTermId">
<el-option v-for="item in terms" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="拟授课程" required>
<el-select v-model="form.courseId" filterable>
<el-option v-for="item in courses" :key="item.id" :label="`${item.code} · ${item.name}`" :value="item.id" />
</el-select>
</el-form-item>
</div>
<el-form-item label="申报说明">
<el-input v-model="form.statement" type="textarea" :rows="4" maxlength="500" show-word-limit placeholder="可填写相关教学经历、研究方向或其他说明" />
</el-form-item>
</el-form>
<template #footer><el-button @click="submitDialog = false">取消</el-button><el-button type="primary" @click="submit">提交学院审核</el-button></template>
</el-dialog>
<el-dialog v-model="reviewDialog" title="审核授课资格" width="580px">
<div class="constraint-title">{{ reviewForm.title }}</div>
<el-descriptions :column="1" border>
<el-descriptions-item label="教师说明">{{ reviewForm.statement || '未填写' }}</el-descriptions-item>
</el-descriptions>
<el-form label-position="top" style="margin-top: 16px">
<el-form-item label="审核结论">
<el-radio-group v-model="reviewForm.status">
<el-radio-button value="Approved">通过</el-radio-button>
<el-radio-button value="Rejected">驳回</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="审核意见">
<el-input v-model="reviewForm.reviewComment" type="textarea" :rows="3" maxlength="500" show-word-limit />
</el-form-item>
</el-form>
<template #footer><el-button @click="reviewDialog = false">取消</el-button><el-button type="primary" @click="review">确认审核</el-button></template>
</el-dialog>
</div>
</template>