选课名单/课程修读情况
This commit is contained in:
1 parent
4575758812
commit
ef3fbf92f0
7 files changed
+241
-139
No files matched your search
@@ -171,13 +171,6 @@ const navigationGroups = computed<NavigationGroup[]>(() => [
|
||||
hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin']),
|
||||
{ path: '/course-enrollments', label: '修读课程管理' },
|
||||
),
|
||||
...whenVisible(
|
||||
hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Teacher']),
|
||||
{
|
||||
path: '/course-adjustments',
|
||||
label: isTeacher.value ? '我的调停课' : '调停课审核',
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -247,7 +240,7 @@ const navigationGroups = computed<NavigationGroup[]>(() => [
|
||||
),
|
||||
...whenVisible(
|
||||
hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin', 'Counselor', 'Teacher', 'Student']),
|
||||
{ path: '/approvals', label: '审批中心' },
|
||||
{ path: '/approvals', label: '审批工作台' },
|
||||
),
|
||||
...whenVisible(
|
||||
hasAnyRole(['SuperAdmin', 'AcademicAdmin', 'Counselor', 'Student']),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import {
|
||||
Check,
|
||||
Clock,
|
||||
@@ -49,6 +50,7 @@ interface SubstitutionTarget {
|
||||
}
|
||||
|
||||
const auth = useAuthStore()
|
||||
const route = useRoute()
|
||||
const isManager = computed(() => auth.user?.roles.some(r => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin'].includes(r)) ?? false)
|
||||
const isTeacher = computed(() => auth.user?.roles.includes('Teacher') && !isManager.value)
|
||||
const isStudent = computed(() => auth.user?.roles.includes('Student') && !isManager.value)
|
||||
@@ -92,6 +94,8 @@ const dialogTitle = computed(() => ({
|
||||
substitution: '发起课程替代申请',
|
||||
}[dialogType.value] ?? '发起申请'))
|
||||
|
||||
const actionableApprovalTypes = ['CourseExemption', 'DeferredExam', 'CourseSubstitution', 'CourseAdjustment']
|
||||
|
||||
const selectedCourse = computed(() =>
|
||||
enrolledCourses.value.find(course => course.teachingTaskId === form.teachingTaskId))
|
||||
|
||||
@@ -278,7 +282,7 @@ async function submitForm() {
|
||||
|
||||
async function approveItem(item: any) {
|
||||
try {
|
||||
const ep = `/approvals/${item.type === 'CourseExemption' ? 'exemptions' : item.type === 'DeferredExam' ? 'deferred' : item.type === 'CourseSubstitution' ? 'substitutions' : ''}`
|
||||
const ep = approvalEndpoint(item.type)
|
||||
if (ep) {
|
||||
await http.post(`${ep}/${item.id}/approve`)
|
||||
ElMessage.success('已通过')
|
||||
@@ -298,7 +302,7 @@ function openReject(item: any) {
|
||||
async function rejectSubmit() {
|
||||
if (!rejectItem.value) return
|
||||
try {
|
||||
const ep = `/approvals/${rejectItem.value.type === 'CourseExemption' ? 'exemptions' : rejectItem.value.type === 'DeferredExam' ? 'deferred' : rejectItem.value.type === 'CourseSubstitution' ? 'substitutions' : ''}`
|
||||
const ep = approvalEndpoint(rejectItem.value.type)
|
||||
if (ep) {
|
||||
await http.post(`${ep}/${rejectItem.value.id}/reject`, { comment: rejectComment.value })
|
||||
rejectDialog.value = false
|
||||
@@ -310,6 +314,14 @@ async function rejectSubmit() {
|
||||
}
|
||||
}
|
||||
|
||||
function approvalEndpoint(type: string) {
|
||||
if (type === 'CourseAdjustment') return '/course-adjustments'
|
||||
const segment = type === 'CourseExemption' ? 'exemptions'
|
||||
: type === 'DeferredExam' ? 'deferred'
|
||||
: type === 'CourseSubstitution' ? 'substitutions' : ''
|
||||
return segment ? `/approvals/${segment}` : ''
|
||||
}
|
||||
|
||||
function statusText(status: string) {
|
||||
return status === 'Submitted' ? '待审核' : status === 'Approved' ? '已通过' : '已驳回'
|
||||
}
|
||||
@@ -323,7 +335,10 @@ function courseScheduleText(course: CourseOption) {
|
||||
return course.hasPublishedSchedule ? '课表已发布' : '等待课表发布'
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
onMounted(() => {
|
||||
if (isManager.value && route.query.tab === 'pending') tab.value = 'pending'
|
||||
return load()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -331,8 +346,8 @@ onMounted(load)
|
||||
<section class="page-intro">
|
||||
<div>
|
||||
<span class="section-kicker">APPROVAL CENTER</span>
|
||||
<h2>审批中心</h2>
|
||||
<p>{{ isStudent ? '从实际修读课程发起申请,查看审核进度与处理结果。' : '集中处理教务申请与审核任务。' }}</p>
|
||||
<h2>审批工作台</h2>
|
||||
<p>{{ isStudent ? '从实际修读课程发起申请,查看审核进度与处理结果。' : isTeacher ? '从一个入口发起教学申请,集中追踪处理进度。' : '集中处理全部教务申请与审核任务。' }}</p>
|
||||
</div>
|
||||
<el-button :icon="Refresh" @click="load">刷新</el-button>
|
||||
</section>
|
||||
@@ -367,6 +382,27 @@ onMounted(load)
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section v-if="isTeacher" class="application-launcher teacher-launcher">
|
||||
<button class="launch-card adjustment" type="button" @click="$router.push({ path: '/course-adjustments', query: { create: '1', from: 'approvals' } })">
|
||||
<span class="launch-icon"><Switch /></span>
|
||||
<span class="launch-copy">
|
||||
<small>COURSE ADJUSTMENT</small>
|
||||
<b>发起调停课</b>
|
||||
<em>调课、停课、补课、代课统一从审批工作台发起</em>
|
||||
</span>
|
||||
<span class="launch-meta">填写申请 <Plus /></span>
|
||||
</button>
|
||||
<button class="launch-card grade-modification" type="button" @click="openDialog('gradeMod')">
|
||||
<span class="launch-icon"><DocumentChecked /></span>
|
||||
<span class="launch-copy">
|
||||
<small>GRADE MODIFICATION</small>
|
||||
<b>发起成绩修改</b>
|
||||
<em>提交成绩修正依据,进入同一审批队列</em>
|
||||
</span>
|
||||
<span class="launch-meta">填写申请 <Plus /></span>
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<div class="approval-tabs">
|
||||
<el-segmented
|
||||
v-model="tab"
|
||||
@@ -375,7 +411,6 @@ onMounted(load)
|
||||
{ label: `我的记录${isStudent ? ` (${recordCount})` : ''}`, value: 'mine' },
|
||||
]"
|
||||
/>
|
||||
<el-button v-if="isTeacher" :icon="Plus" type="primary" @click="openDialog('gradeMod')">成绩修改</el-button>
|
||||
</div>
|
||||
|
||||
<section v-if="tab === 'pending'" v-loading="loading" class="appr-list">
|
||||
@@ -386,7 +421,7 @@ onMounted(load)
|
||||
<p>{{ item.desc }}</p>
|
||||
<small>{{ item.college }} · {{ new Date(item.time).toLocaleString('zh-CN') }}</small>
|
||||
</div>
|
||||
<div v-if="['CourseExemption','DeferredExam','CourseSubstitution'].includes(item.type)" class="appr-actions">
|
||||
<div v-if="actionableApprovalTypes.includes(item.type)" class="appr-actions">
|
||||
<el-button size="small" type="danger" plain :icon="Close" @click="openReject(item)">驳回</el-button>
|
||||
<el-button size="small" type="success" :icon="Check" @click="approveItem(item)">通过</el-button>
|
||||
</div>
|
||||
@@ -395,7 +430,7 @@ onMounted(load)
|
||||
size="small"
|
||||
text
|
||||
type="primary"
|
||||
@click="$router.push(item.type==='StudentStatusChange'?'/student-status-changes':item.type==='CourseAdjustment'?'/course-adjustments':item.type==='GradeSheet'?'/grades':'/teacher-attendance')"
|
||||
@click="$router.push(item.type==='StudentStatusChange'?'/student-status-changes':item.type==='GradeSheet'?'/grades':'/teacher-attendance')"
|
||||
>
|
||||
查看 →
|
||||
</el-button>
|
||||
@@ -668,6 +703,9 @@ onMounted(load)
|
||||
.launch-icon svg, .dialog-lead-icon svg { width: 21px; }
|
||||
.deferred .launch-icon, .dialog-lead-icon.deferred { color: #a85d00; background: #fff3dc; }
|
||||
.substitution .launch-icon { color: #087f5b; background: #e8f8f1; }
|
||||
.adjustment .launch-icon { color: #7c3aed; background: #f1eafe; }
|
||||
.grade-modification .launch-icon { color: #0d7490; background: #e1f5f8; }
|
||||
.teacher-launcher { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.launch-copy { display: grid; gap: 3px; min-width: 0; }
|
||||
.launch-copy small { color: #8791a3; font: 600 9px/1.2 ui-monospace, monospace; letter-spacing: .1em; }
|
||||
.launch-copy b { margin-top: 3px; font-size: 17px; }
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { Check, Plus, Refresh } from '@element-plus/icons-vue'
|
||||
import http, { apiErrorMessage } from '../api/http'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
import { academicTermLabel, academicTermOptionClass, defaultAcademicTermId } from '../utils/academicTerms'
|
||||
|
||||
const auth = useAuthStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const isManager = computed(() =>
|
||||
auth.user?.roles.some(r => ['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin'].includes(r)) ?? false)
|
||||
const isTeacher = computed(() => auth.user?.roles.includes('Teacher') && !isManager.value)
|
||||
@@ -246,6 +249,10 @@ onMounted(async () => {
|
||||
ElMessage.error(apiErrorMessage(error))
|
||||
}
|
||||
await load()
|
||||
if (isTeacher.value && route.query.create === '1') {
|
||||
openCreate()
|
||||
await router.replace({ path: '/course-adjustments' })
|
||||
}
|
||||
})
|
||||
|
||||
function changeType() {
|
||||
|
||||
Loaded 3 of 7 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user