修复实验成绩管理:
管理列表改为服务端分页,支持 10/20/50 条切换。 增加学期、成绩状态、开课学院以及项目/课程/教学班/教师关键词筛选。 数据范围分级:校级管理员可查看全校,院级管理员仅限本学院,教师仅限本人任课项目。 修复任课教师提交审核时报“无权限”:工作流查询现在会加载任课教师关系。 增加权限、学院隔离、分页和筛选回归测试。
This commit is contained in:
+2
-1
@@ -344,7 +344,7 @@ button { cursor: pointer; }
|
||||
.form-grid.three { grid-template-columns: repeat(3, 1fr); }
|
||||
.visually-hidden { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
|
||||
|
||||
/* MessageBox 的定位与底色不能依赖其伪元素:部分浏览器在遮罩层中会将其压到左上角。 */
|
||||
/* 由遮罩层 flex 居中;MessageBox 默认的全屏 fixed 包装层会脱离该布局。 */
|
||||
.el-overlay.is-message-box {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
@@ -352,6 +352,7 @@ button { cursor: pointer; }
|
||||
padding: 16px;
|
||||
}
|
||||
.el-overlay.is-message-box .el-overlay-message-box {
|
||||
position: static !important;
|
||||
display: block !important;
|
||||
width: min(420px, calc(100vw - 32px));
|
||||
min-height: 0 !important;
|
||||
|
||||
@@ -19,12 +19,21 @@ import {
|
||||
|
||||
const auth = useAuthStore()
|
||||
const isStudent = computed(() => auth.user?.roles.includes('Student') ?? false)
|
||||
const canViewAllColleges = computed(() =>
|
||||
auth.user?.roles.some((role) => ['SuperAdmin', 'AcademicAdmin'].includes(role)) ?? false,
|
||||
)
|
||||
const loading = ref(false)
|
||||
const detailLoading = ref(false)
|
||||
const saving = ref(false)
|
||||
const terms = ref<any[]>([])
|
||||
const colleges = ref<any[]>([])
|
||||
const termId = ref('')
|
||||
const statusFilter = ref('')
|
||||
const collegeId = ref('')
|
||||
const projectKeyword = ref('')
|
||||
const projectPage = ref(1)
|
||||
const projectPageSize = ref(20)
|
||||
const projectTotal = ref(0)
|
||||
const projects = ref<any[]>([])
|
||||
const studentResults = ref<any[]>([])
|
||||
const detailDrawer = ref(false)
|
||||
@@ -177,12 +186,20 @@ async function load() {
|
||||
if (isStudent.value) {
|
||||
studentResults.value = (await http.get('/experiment-grades/mine')).data
|
||||
} else {
|
||||
projects.value = (await http.get('/experiment-grades/management', {
|
||||
const data = (await http.get('/experiment-grades/management', {
|
||||
params: {
|
||||
academicTermId: termId.value || undefined,
|
||||
status: statusFilter.value || undefined,
|
||||
collegeId: collegeId.value || undefined,
|
||||
keyword: projectKeyword.value.trim() || undefined,
|
||||
page: projectPage.value,
|
||||
pageSize: projectPageSize.value,
|
||||
},
|
||||
})).data
|
||||
projects.value = data.items
|
||||
projectTotal.value = data.total
|
||||
projectPage.value = data.page
|
||||
projectPageSize.value = data.pageSize
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error(apiErrorMessage(error))
|
||||
@@ -367,7 +384,11 @@ function scoreTone(result: any) {
|
||||
onMounted(async () => {
|
||||
if (!isStudent.value) {
|
||||
try {
|
||||
terms.value = (await http.get('/base-data/terms')).data
|
||||
const requests: Promise<any>[] = [http.get('/base-data/terms')]
|
||||
if (canViewAllColleges.value) requests.push(http.get('/base-data/colleges'))
|
||||
const [termResponse, collegeResponse] = await Promise.all(requests)
|
||||
terms.value = termResponse.data
|
||||
colleges.value = collegeResponse?.data ?? []
|
||||
termId.value = defaultAcademicTermId(terms.value) ?? ''
|
||||
} catch (error) {
|
||||
ElMessage.error(apiErrorMessage(error))
|
||||
@@ -438,7 +459,7 @@ onMounted(async () => {
|
||||
|
||||
<template v-else>
|
||||
<section class="grade-toolbar">
|
||||
<el-select v-model="termId" clearable placeholder="全部学期" @change="load">
|
||||
<el-select v-model="termId" clearable placeholder="全部学期" @change="projectPage = 1; load()">
|
||||
<el-option
|
||||
v-for="term in terms"
|
||||
:key="term.id"
|
||||
@@ -447,10 +468,27 @@ onMounted(async () => {
|
||||
:class="academicTermOptionClass(term)"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select v-model="statusFilter" clearable placeholder="全部成绩状态" @change="load">
|
||||
<el-select v-model="statusFilter" clearable placeholder="全部成绩状态" @change="projectPage = 1; load()">
|
||||
<el-option v-for="(label, value) in statusLabels" :key="value" :label="label" :value="value" />
|
||||
</el-select>
|
||||
<span>每个实验项目独立建单;课程只读取已发布的汇总快照。</span>
|
||||
<el-select
|
||||
v-if="canViewAllColleges"
|
||||
v-model="collegeId"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="全部开课学院"
|
||||
@change="projectPage = 1; load()"
|
||||
>
|
||||
<el-option v-for="college in colleges" :key="college.id" :label="college.name" :value="college.id" />
|
||||
</el-select>
|
||||
<el-input
|
||||
v-model="projectKeyword"
|
||||
clearable
|
||||
placeholder="项目、课程、教学班或教师"
|
||||
@keyup.enter="projectPage = 1; load()"
|
||||
@clear="projectPage = 1; load()"
|
||||
/>
|
||||
<el-button type="primary" @click="projectPage = 1; load()">查询</el-button>
|
||||
</section>
|
||||
|
||||
<section v-loading="loading" class="grade-project-list">
|
||||
@@ -488,6 +526,15 @@ onMounted(async () => {
|
||||
</article>
|
||||
<el-empty v-if="!loading && !projects.length" description="当前筛选条件下没有可评分实验项目" />
|
||||
</section>
|
||||
<el-pagination
|
||||
v-model:current-page="projectPage"
|
||||
v-model:page-size="projectPageSize"
|
||||
:total="projectTotal"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@current-change="load"
|
||||
@size-change="projectPage = 1; load()"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<el-dialog
|
||||
@@ -724,6 +771,7 @@ onMounted(async () => {
|
||||
.assessment-flow > i { display: grid; place-items: center; color: #8aa0aa; font-style: normal; background: #f4f7f8; }
|
||||
.grade-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
|
||||
.grade-toolbar .el-select { width: 220px; }
|
||||
.grade-toolbar .el-input { width: min(320px, 100%); }
|
||||
.grade-toolbar > span { margin-left: auto; color: var(--muted); font-size: 12px; }
|
||||
.grade-project-list { display: grid; gap: 12px; min-height: 180px; }
|
||||
.grade-project-card { min-width: 0; padding: 16px 18px; display: grid; grid-template-columns: 130px minmax(220px, 1.4fr) minmax(220px, 1fr) auto; align-items: center; gap: 18px; border: 1px solid #d9e4e8; border-left: 5px solid var(--grade-blue); background: #fff; box-shadow: 0 6px 18px rgb(30 68 86 / 5%); }
|
||||
@@ -812,7 +860,7 @@ onMounted(async () => {
|
||||
.assessment-flow { grid-template-columns: 1fr; }
|
||||
.assessment-flow > i { display: none; }
|
||||
.grade-toolbar { align-items: stretch; flex-direction: column; }
|
||||
.grade-toolbar .el-select { width: 100%; }
|
||||
.grade-toolbar .el-select, .grade-toolbar .el-input { width: 100%; }
|
||||
.grade-toolbar > span { margin-left: 0; }
|
||||
.grade-project-card { grid-template-columns: 1fr; }
|
||||
.sheet-progress, .sheet-empty, .grade-project-card > .el-button { grid-column: 1; grid-row: auto; width: 100%; }
|
||||
|
||||
Reference in New Issue
Block a user