实验成绩计算
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { Calendar, Check, Plus, Refresh, Tickets, User } from '@element-plus/icons-vue'
|
||||
import { Calendar, Check, Plus, Refresh, User } from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import http, { apiErrorMessage } from '../api/http'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
@@ -665,7 +665,7 @@ onMounted(async () => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="lab-rail" aria-label="实验安排方式说明">
|
||||
<section v-if="!isStudent" class="lab-rail" aria-label="实验安排方式说明">
|
||||
<article class="rail-lane centralized">
|
||||
<span class="rail-code">FIXED / 集中</span>
|
||||
<b>统一课次</b>
|
||||
@@ -707,7 +707,7 @@ onMounted(async () => {
|
||||
<el-option label="已关闭" value="Closed" />
|
||||
</el-select>
|
||||
</template>
|
||||
<span class="result-note">共 {{ total }} 个教学班课程</span>
|
||||
<span class="result-note">{{ isStudent ? `共 ${projectGroups.length} 门课程、${total} 个实验项目` : `共 ${total} 个教学班课程` }}</span>
|
||||
</section>
|
||||
|
||||
<section v-if="!isStudent" class="experiment-batch-bar">
|
||||
@@ -760,6 +760,16 @@ onMounted(async () => {
|
||||
<div v-else-if="row.sessions.length" class="table-schedule">
|
||||
<b>{{ row.sessions.length }} 个开放场次</b>
|
||||
<small>{{ row.sessions.map((session: any) => formatSessionTime(session)).join(';') }}</small>
|
||||
<el-popover placement="bottom" :width="520" trigger="click">
|
||||
<template #reference><el-button size="small" text :icon="User">查看场次与名单</el-button></template>
|
||||
<div class="student-session-menu">
|
||||
<div v-for="session in row.sessions" :key="session.id" class="student-session-option">
|
||||
<div><b>{{ formatSessionTime(session) }}</b><span>{{ session.campusName }} · {{ session.buildingName }} {{ session.classroomName }}</span></div>
|
||||
<el-button size="small" text @click="showParticipants(row, session)">预约名单</el-button>
|
||||
<el-button v-if="session.status !== 'Cancelled'" size="small" text type="danger" @click="cancelSession(row, session)">{{ row.status === 'Draft' ? '删除' : '取消' }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
<span v-else class="table-muted">尚未安排</span>
|
||||
</template>
|
||||
@@ -793,211 +803,77 @@ onMounted(async () => {
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<article
|
||||
v-else
|
||||
v-for="project in group.projects"
|
||||
:key="project.id"
|
||||
class="project-card"
|
||||
:class="[
|
||||
project.arrangementMode === 'Centralized' ? 'is-centralized' : 'is-flexible',
|
||||
`is-${project.status.toLowerCase()}`,
|
||||
]"
|
||||
>
|
||||
<header class="project-head">
|
||||
<div class="project-identity">
|
||||
<span class="project-code">{{ project.courseCode }} · {{ project.code }}</span>
|
||||
<h3>{{ project.name }}</h3>
|
||||
<p>{{ project.arrangementMode === 'Centralized' ? '统一到场实验' : '开放预约实验' }}</p>
|
||||
</div>
|
||||
<div class="project-tags">
|
||||
<el-checkbox
|
||||
v-if="!isStudent && project.status === 'Draft'"
|
||||
:model-value="selectedProjectIds.includes(project.id)"
|
||||
@change="toggleProjectSelection(project.id, $event)"
|
||||
aria-label="选择实验项目"
|
||||
/>
|
||||
<el-tag
|
||||
:type="project.arrangementMode === 'Centralized' ? 'primary' : 'success'"
|
||||
effect="plain"
|
||||
>
|
||||
{{ modeMeta[project.arrangementMode].label }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
:type="project.status === 'Published' ? 'success' : project.status === 'Closed' ? 'info' : 'warning'"
|
||||
>
|
||||
{{ statusLabels[project.status] }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="project-context">
|
||||
<span><b>开放日期</b>{{ project.startDate }} — {{ project.endDate }}</span>
|
||||
<span><b>任课教师</b>{{ project.teacherNames.join('、') || '待定' }}</span>
|
||||
<span v-if="!isStudent"><b>面向班级</b>{{ project.classNames.join('、') || '选课学生' }}</span>
|
||||
</div>
|
||||
|
||||
<p v-if="project.description" class="project-description">{{ project.description }}</p>
|
||||
<div v-if="project.requirements" class="requirement-strip">
|
||||
<b>实验要求</b>
|
||||
<span>{{ project.requirements }}</span>
|
||||
</div>
|
||||
|
||||
<div class="session-board">
|
||||
<div class="session-board-head">
|
||||
<div>
|
||||
<span>{{ project.arrangementMode === 'Centralized' ? '统一实验课次' : '可选实验场次' }}</span>
|
||||
<b>{{ modeMeta[project.arrangementMode].note }}</b>
|
||||
</div>
|
||||
<el-button
|
||||
v-if="!isStudent && project.status !== 'Closed' && project.arrangementMode === 'SelfScheduled'"
|
||||
size="small"
|
||||
:icon="Calendar"
|
||||
@click="openSession(project)"
|
||||
>
|
||||
安排场次
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div v-if="project.arrangementMode === 'Centralized' && project.scheduleEntry" class="session-grid">
|
||||
<article class="session-ticket">
|
||||
<div class="ticket-date"><strong>课表</strong><span>固定</span></div>
|
||||
<div class="ticket-body">
|
||||
<b>第 {{ project.scheduleEntry.projectWeek ?? `${project.scheduleEntry.startWeek}–${project.scheduleEntry.endWeek}` }} 周 · 星期 {{ project.scheduleEntry.dayOfWeek }} · 第 {{ project.scheduleEntry.startPeriod }}–{{ project.scheduleEntry.startPeriod + project.scheduleEntry.periodCount - 1 }} 节</b>
|
||||
<span>{{ project.scheduleEntry.campusName }} · {{ project.scheduleEntry.buildingName }} {{ project.scheduleEntry.classroomName }}</span>
|
||||
</div>
|
||||
<div class="ticket-action"><el-tag type="primary" size="small" effect="plain">课表已安排</el-tag></div>
|
||||
</article>
|
||||
</div>
|
||||
<div v-else-if="project.sessions.length" class="session-grid">
|
||||
<article
|
||||
v-for="session in project.sessions"
|
||||
:key="session.id"
|
||||
class="session-ticket"
|
||||
:class="{
|
||||
'is-selected': project.myBooking?.experimentSessionId === session.id,
|
||||
'is-cancelled': session.status === 'Cancelled',
|
||||
}"
|
||||
>
|
||||
<div class="ticket-date">
|
||||
<strong>{{ session.sessionDate.slice(8, 10) }}</strong>
|
||||
<span>{{ session.sessionDate.slice(5, 7) }} 月</span>
|
||||
</div>
|
||||
<div class="ticket-body">
|
||||
<b>第 {{ session.startPeriod }}–{{ session.startPeriod + session.periodCount - 1 }} 节</b>
|
||||
<span>{{ session.campusName }} · {{ session.buildingName }} {{ session.classroomName }}</span>
|
||||
<small v-if="session.notes">{{ session.notes }}</small>
|
||||
</div>
|
||||
<div class="ticket-action">
|
||||
<template v-if="project.arrangementMode === 'SelfScheduled'">
|
||||
<el-tag
|
||||
v-if="session.status !== 'Cancelled'"
|
||||
:type="sessionTagType(session)"
|
||||
size="small"
|
||||
effect="plain"
|
||||
>
|
||||
余 {{ session.capacity - session.reservedCount }} / {{ session.capacity }}
|
||||
</el-tag>
|
||||
<el-tag v-else type="info" size="small">已取消</el-tag>
|
||||
<el-table v-else :data="group.projects" class="project-table student-project-table" size="small">
|
||||
<el-table-column type="expand" width="44">
|
||||
<template #default="{ row }">
|
||||
<div class="student-project-detail">
|
||||
<p v-if="row.description"><b>项目说明</b>{{ row.description }}</p>
|
||||
<p v-if="row.requirements"><b>实验要求</b>{{ row.requirements }}</p>
|
||||
<p><b>开放日期</b>{{ row.startDate }} — {{ row.endDate }}</p>
|
||||
<p><b>任课教师</b>{{ row.teacherNames.join('、') || '待定' }}</p>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实验项目" min-width="210">
|
||||
<template #default="{ row }">
|
||||
<div class="table-project-name">
|
||||
<b>{{ row.code }} · {{ row.name }}</b>
|
||||
<small>{{ row.description || modeMeta[row.arrangementMode].note }}</small>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实验安排" min-width="300">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.arrangementMode === 'Centralized' && row.scheduleEntry" class="table-schedule">
|
||||
<b>第 {{ row.scheduleEntry.projectWeek ?? `${row.scheduleEntry.startWeek}–${row.scheduleEntry.endWeek}` }} 周 · 星期 {{ row.scheduleEntry.dayOfWeek }} · 第 {{ row.scheduleEntry.startPeriod }}–{{ row.scheduleEntry.startPeriod + row.scheduleEntry.periodCount - 1 }} 节</b>
|
||||
<small>{{ row.scheduleEntry.campusName }} · {{ row.scheduleEntry.buildingName }} {{ row.scheduleEntry.classroomName }}</small>
|
||||
</div>
|
||||
<div v-else-if="row.myBooking && selectedSession(row)" class="table-schedule is-booked">
|
||||
<b>{{ formatSessionTime(selectedSession(row)) }}</b>
|
||||
<small>{{ selectedSession(row).campusName }} · {{ selectedSession(row).buildingName }} {{ selectedSession(row).classroomName }}</small>
|
||||
</div>
|
||||
<div v-else-if="row.sessions.length" class="table-schedule">
|
||||
<b>{{ row.sessions.length }} 个可选场次</b>
|
||||
<small>尚未预约,请在右侧选择实验时间</small>
|
||||
</div>
|
||||
<span v-else class="table-muted">暂无有效实验安排</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="方式" width="100" align="center">
|
||||
<template #default="{ row }"><el-tag :type="row.arrangementMode === 'Centralized' ? 'primary' : 'success'" effect="plain" size="small">{{ modeMeta[row.arrangementMode].label }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="88" align="center">
|
||||
<template #default="{ row }"><el-tag :type="row.status === 'Published' ? 'success' : 'info'" size="small">{{ statusLabels[row.status] }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="预约操作" width="190" fixed="right" align="right">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.arrangementMode === 'SelfScheduled'">
|
||||
<el-popover placement="bottom-end" :width="540" trigger="click">
|
||||
<template #reference>
|
||||
<el-button size="small" :type="row.myBooking ? 'success' : 'primary'" plain>
|
||||
{{ row.myBooking ? '查看场次' : '选择场次' }}
|
||||
</el-button>
|
||||
</template>
|
||||
<div class="student-session-menu">
|
||||
<header><b>{{ row.code }} · {{ row.name }}</b><span>每个实验项目限约一个场次</span></header>
|
||||
<div v-for="session in row.sessions" :key="session.id" class="student-session-option" :class="{ 'is-selected': row.myBooking?.experimentSessionId === session.id }">
|
||||
<div>
|
||||
<b>{{ formatSessionTime(session) }}</b>
|
||||
<span>{{ session.campusName }} · {{ session.buildingName }} {{ session.classroomName }}</span>
|
||||
</div>
|
||||
<el-tag :type="sessionTagType(session)" size="small" effect="plain">余 {{ session.remainingCount }} / {{ session.capacity }}</el-tag>
|
||||
<el-button v-if="row.myBooking?.experimentSessionId === session.id" size="small" type="success" :icon="Check" disabled>已预约</el-button>
|
||||
<el-button v-else size="small" type="primary" plain :disabled="row.status !== 'Published' || !!row.myBooking || session.remainingCount <= 0" @click="book(row, session)">预约</el-button>
|
||||
</div>
|
||||
<el-empty v-if="!row.sessions.length" :image-size="42" description="暂无有效实验场次" />
|
||||
</div>
|
||||
</el-popover>
|
||||
<el-button v-if="row.myBooking && row.status === 'Published'" size="small" text type="danger" @click="cancelBooking(row)">取消</el-button>
|
||||
</template>
|
||||
<el-tag v-else type="primary" size="small" effect="plain">
|
||||
统一到场
|
||||
</el-tag>
|
||||
|
||||
<template v-if="isStudent && project.arrangementMode === 'SelfScheduled'">
|
||||
<el-button
|
||||
v-if="project.myBooking?.experimentSessionId === session.id"
|
||||
size="small"
|
||||
type="success"
|
||||
:icon="Check"
|
||||
disabled
|
||||
>
|
||||
已预约
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
size="small"
|
||||
type="primary"
|
||||
plain
|
||||
:disabled="project.status !== 'Published' || !!project.myBooking || session.remainingCount <= 0"
|
||||
@click="book(project, session)"
|
||||
>
|
||||
预约此场
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-if="!isStudent">
|
||||
<el-button
|
||||
size="small"
|
||||
text
|
||||
:icon="User"
|
||||
@click="showParticipants(project, session)"
|
||||
>
|
||||
{{ project.arrangementMode === 'Centralized' ? '应到名单' : '预约名单' }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="session.status !== 'Cancelled'"
|
||||
size="small"
|
||||
text
|
||||
type="danger"
|
||||
@click="cancelSession(project, session)"
|
||||
>
|
||||
{{ project.status === 'Draft' ? '删除' : '取消' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<el-empty
|
||||
v-else
|
||||
:image-size="58"
|
||||
:description="project.arrangementMode === 'Centralized' ? '尚未绑定已发布课表中的实验课' : (project.status === 'Draft' ? '尚未开放场次,安排后才能发布' : '暂无有效实验场次')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<footer v-if="!isStudent" class="project-actions">
|
||||
<template v-if="project.status === 'Draft'">
|
||||
<el-button size="small" @click="openEditProject(project)">编辑项目</el-button>
|
||||
<el-button size="small" type="danger" plain @click="deleteProject(project)">删除</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
:disabled="project.arrangementMode === 'Centralized'
|
||||
? !project.scheduleEntry
|
||||
: !project.sessions.some((item: any) => item.status !== 'Cancelled')"
|
||||
@click="publishProject(project)"
|
||||
>
|
||||
发布给学生
|
||||
</el-button>
|
||||
</template>
|
||||
<el-button
|
||||
v-else-if="project.status === 'Published'"
|
||||
size="small"
|
||||
@click="closeProject(project)"
|
||||
>
|
||||
关闭项目
|
||||
</el-button>
|
||||
</footer>
|
||||
<footer
|
||||
v-else-if="project.arrangementMode === 'SelfScheduled' && project.myBooking"
|
||||
class="student-booking-summary"
|
||||
>
|
||||
<div>
|
||||
<Tickets />
|
||||
<span>
|
||||
<b>当前预约</b>
|
||||
{{ formatSessionTime(selectedSession(project)) }}
|
||||
</span>
|
||||
</div>
|
||||
<el-button
|
||||
v-if="project.status === 'Published'"
|
||||
size="small"
|
||||
type="danger"
|
||||
plain
|
||||
@click="cancelBooking(project)"
|
||||
>
|
||||
取消并重选
|
||||
</el-button>
|
||||
</footer>
|
||||
</article>
|
||||
<span v-else class="table-muted">无需预约</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1390,11 +1266,25 @@ onMounted(async () => {
|
||||
.project-table { --el-table-border-color: #dce5ea; --el-table-header-bg-color: #f7fafb; --el-table-row-hover-bg-color: #f6fafb; width: 100%; }
|
||||
.project-table :deep(th.el-table__cell) { padding: 8px 0; color: #617986; font-size: 11px; font-weight: 650; }
|
||||
.project-table :deep(td.el-table__cell) { padding: 9px 0; vertical-align: top; }
|
||||
.student-project-table :deep(td.el-table__cell) { vertical-align: middle; }
|
||||
.table-project-name, .table-schedule { display: grid; gap: 4px; min-width: 0; }
|
||||
.table-project-name b { color: var(--lab-ink); font-size: 13px; }
|
||||
.table-project-name small, .table-schedule small { overflow: hidden; color: #667f8d; font-size: 11px; line-height: 1.45; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.table-schedule b { color: #365364; font-size: 12px; font-weight: 600; }
|
||||
.table-schedule.is-booked b { color: var(--lab-teal); }
|
||||
.table-muted { color: #8b9aa4; font-size: 12px; }
|
||||
.student-project-detail { display: grid; grid-template-columns: repeat(2, minmax(220px, 1fr)); gap: 7px 20px; padding: 11px 18px 13px; background: #f7fafb; }
|
||||
.student-project-detail p { margin: 0; display: grid; grid-template-columns: 70px 1fr; gap: 8px; color: #5f7682; font-size: 11px; line-height: 1.55; }
|
||||
.student-project-detail b { color: #304d5d; }
|
||||
.student-session-menu { display: grid; gap: 7px; }
|
||||
.student-session-menu > header { display: flex; justify-content: space-between; gap: 12px; padding-bottom: 8px; border-bottom: 1px solid #dfe7ea; }
|
||||
.student-session-menu > header b { color: var(--lab-ink); font-size: 12px; }
|
||||
.student-session-menu > header span { color: #71858e; font-size: 10px; }
|
||||
.student-session-option { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; gap: 9px; padding: 8px 9px; border: 1px solid #e0e7ea; background: #fff; }
|
||||
.student-session-option.is-selected { border-color: #79afa5; box-shadow: inset 3px 0 0 var(--lab-teal); background: #f3faf8; }
|
||||
.student-session-option > div { display: grid; gap: 2px; min-width: 0; }
|
||||
.student-session-option > div b { color: #304d5d; font-size: 11px; }
|
||||
.student-session-option > div span { overflow: hidden; color: #6a808b; font-size: 10px; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.project-card {
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
@@ -1521,6 +1411,7 @@ onMounted(async () => {
|
||||
.session-ticket { grid-template-columns: 54px minmax(0, 1fr); }
|
||||
.ticket-action { grid-column: 1 / -1; justify-content: flex-start; padding: 0 10px 10px; }
|
||||
.project-actions, .student-booking-summary { padding-inline: 14px; }
|
||||
.student-project-detail { grid-template-columns: 1fr; }
|
||||
.mode-choice { grid-template-columns: 1fr; }
|
||||
.intro-actions { flex-wrap: wrap; justify-content: flex-end; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user