自选项目实验优化
This commit is contained in:
@@ -25,6 +25,7 @@ const options = reactive({
|
||||
colleges: [] as any[],
|
||||
scheduleEntries: [] as any[],
|
||||
classrooms: [] as any[],
|
||||
teachers: [] as any[],
|
||||
periods: [] as any[],
|
||||
})
|
||||
const modeFilter = ref('')
|
||||
@@ -49,6 +50,7 @@ const projectForm = reactive({
|
||||
description: '',
|
||||
requirements: '',
|
||||
dates: [] as string[],
|
||||
selectionTimes: [] as string[],
|
||||
})
|
||||
|
||||
const batchRenameDialog = ref(false)
|
||||
@@ -64,6 +66,7 @@ const sessionForm = reactive({
|
||||
periodCount: 2,
|
||||
capacity: 30,
|
||||
notes: '',
|
||||
instructorTeacherIds: [] as string[],
|
||||
})
|
||||
|
||||
const batchSessionDialog = ref(false)
|
||||
@@ -171,6 +174,7 @@ function resetProjectForm() {
|
||||
description: '',
|
||||
requirements: '',
|
||||
dates: [],
|
||||
selectionTimes: [],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -211,6 +215,8 @@ function openEditProject(project: any) {
|
||||
description: project.description ?? '',
|
||||
requirements: project.requirements ?? '',
|
||||
dates: [project.startDate, project.endDate],
|
||||
selectionTimes: project.selectionStartsAt && project.selectionEndsAt
|
||||
? [project.selectionStartsAt, project.selectionEndsAt] : [],
|
||||
})
|
||||
projectDialog.value = true
|
||||
}
|
||||
@@ -234,10 +240,11 @@ async function saveProject() {
|
||||
if ((!correctingPublishedProject.value && !projectForm.teachingTaskIds.length) ||
|
||||
(editingProjectId.value && projectForm.arrangementMode === 'Centralized' && !projectForm.scheduleEntryId) ||
|
||||
!projectForm.code.trim()
|
||||
|| !hasValidName || projectForm.dates.length !== 2) {
|
||||
|| !hasValidName || projectForm.dates.length !== 2
|
||||
|| (projectForm.arrangementMode === 'SelfScheduled' && projectForm.selectionTimes.length !== 2)) {
|
||||
ElMessage.warning(projectForm.arrangementMode === 'Centralized' && editingProjectId.value
|
||||
? '请选择课表实验课,并填写项目编码、名称和开放日期'
|
||||
: '请填写教学任务、项目编码、名称和开放日期')
|
||||
: '请填写教学任务、项目编码、名称、开放日期和选课时间')
|
||||
return
|
||||
}
|
||||
const payload = {
|
||||
@@ -250,6 +257,10 @@ async function saveProject() {
|
||||
requirements: projectForm.requirements || null,
|
||||
startDate: projectForm.dates[0],
|
||||
endDate: projectForm.dates[1],
|
||||
selectionStartsAt: projectForm.arrangementMode === 'SelfScheduled'
|
||||
? projectForm.selectionTimes[0] || null : null,
|
||||
selectionEndsAt: projectForm.arrangementMode === 'SelfScheduled'
|
||||
? projectForm.selectionTimes[1] || null : null,
|
||||
}
|
||||
try {
|
||||
if (correctingPublishedProject.value) {
|
||||
@@ -288,24 +299,47 @@ function openBatchSession() {
|
||||
}
|
||||
|
||||
function syncBatchSessionRows() {
|
||||
const existing = new Map(batchSessionRows.value.map((row) => [row.projectId, row]))
|
||||
batchSessionRows.value = batchSessionProjectIds.value.map((projectId) => {
|
||||
const current = existing.get(projectId)
|
||||
if (current) return current
|
||||
const project = projects.value.find((item) => item.id === projectId)
|
||||
const firstPeriod = options.periods.find((item) =>
|
||||
item.academicTermId === project?.academicTermId,
|
||||
)
|
||||
return {
|
||||
projectId,
|
||||
classroomId: '',
|
||||
sessionDate: project?.startDate ?? '',
|
||||
startPeriod: firstPeriod?.periodNumber,
|
||||
periodCount: 2,
|
||||
capacity: 30,
|
||||
notes: '',
|
||||
}
|
||||
const existing = new Map<string, any[]>()
|
||||
batchSessionRows.value.forEach((row) => {
|
||||
const rows = existing.get(row.projectId) ?? []
|
||||
rows.push(row)
|
||||
existing.set(row.projectId, rows)
|
||||
})
|
||||
batchSessionRows.value = batchSessionProjectIds.value.flatMap((projectId) => {
|
||||
const current = existing.get(projectId)
|
||||
if (current?.length) return current
|
||||
return [newBatchSessionRow(projectId)]
|
||||
})
|
||||
}
|
||||
|
||||
function newBatchSessionRow(projectId: string, source?: any) {
|
||||
const project = projects.value.find((item) => item.id === projectId)
|
||||
const firstPeriod = options.periods.find((item) =>
|
||||
item.academicTermId === project?.academicTermId,
|
||||
)
|
||||
return {
|
||||
rowKey: crypto.randomUUID(),
|
||||
projectId,
|
||||
classroomId: source?.classroomId ?? '',
|
||||
sessionDate: source?.sessionDate ?? project?.startDate ?? '',
|
||||
startPeriod: source?.startPeriod ?? firstPeriod?.periodNumber,
|
||||
periodCount: source?.periodCount ?? 2,
|
||||
capacity: source?.capacity ?? 30,
|
||||
notes: '',
|
||||
instructorTeacherIds: source?.instructorTeacherIds ? [...source.instructorTeacherIds] : [],
|
||||
}
|
||||
}
|
||||
|
||||
function addBatchSessionRow(projectId: string, source: any) {
|
||||
batchSessionRows.value.push(newBatchSessionRow(projectId, source))
|
||||
}
|
||||
|
||||
function removeBatchSessionRow(row: any) {
|
||||
const sameProjectRows = batchSessionRows.value.filter((item) => item.projectId === row.projectId)
|
||||
if (sameProjectRows.length === 1) {
|
||||
batchSessionProjectIds.value = batchSessionProjectIds.value.filter((id) => id !== row.projectId)
|
||||
}
|
||||
batchSessionRows.value = batchSessionRows.value.filter((item) => item.rowKey !== row.rowKey)
|
||||
}
|
||||
|
||||
function applyFirstBatchTime() {
|
||||
@@ -336,9 +370,9 @@ async function saveBatchSessions() {
|
||||
return
|
||||
}
|
||||
if (batchSessionRows.value.some((row) =>
|
||||
!row.classroomId || !row.sessionDate || !row.startPeriod || !row.periodCount,
|
||||
!row.classroomId || !row.sessionDate || !row.startPeriod || !row.periodCount || !row.instructorTeacherIds?.length,
|
||||
)) {
|
||||
ElMessage.warning('请完整填写每个项目的日期、节次、实验室和容量')
|
||||
ElMessage.warning('请完整填写每个项目的日期、节次、实验室、指导老师和容量')
|
||||
return
|
||||
}
|
||||
try {
|
||||
@@ -365,14 +399,15 @@ function openSession(project: any) {
|
||||
periodCount: 2,
|
||||
capacity: 30,
|
||||
notes: '',
|
||||
instructorTeacherIds: [],
|
||||
})
|
||||
sessionDialog.value = true
|
||||
}
|
||||
|
||||
async function saveSession() {
|
||||
if (!selectedProject.value || !sessionForm.classroomId
|
||||
|| !sessionForm.sessionDate || !sessionForm.startPeriod) {
|
||||
ElMessage.warning('请选择实验日期、节次和教室')
|
||||
|| !sessionForm.sessionDate || !sessionForm.startPeriod || !sessionForm.instructorTeacherIds.length) {
|
||||
ElMessage.warning('请选择实验日期、节次、实验室和指导老师')
|
||||
return
|
||||
}
|
||||
try {
|
||||
@@ -435,6 +470,15 @@ async function deleteProject(project: any) {
|
||||
}
|
||||
}
|
||||
|
||||
function instructorsForProject(projectId: string) {
|
||||
return options.teachers.filter((teacher) => teacher.teachingTaskId === projectId
|
||||
|| teacher.teachingTaskId === batchProject(projectId)?.teachingTaskId)
|
||||
}
|
||||
|
||||
function instructorsForSelectedProject() {
|
||||
return selectedProject.value ? instructorsForProject(selectedProject.value.id) : []
|
||||
}
|
||||
|
||||
function openBatchRename() {
|
||||
batchRenameProjectIds.value = projects.value
|
||||
.filter((project) => draftSelectedProjectIds.value.includes(project.id))
|
||||
@@ -627,11 +671,13 @@ async function loadOptions() {
|
||||
options.colleges = data.colleges
|
||||
options.scheduleEntries = data.scheduleEntries
|
||||
options.classrooms = data.classrooms
|
||||
options.teachers = data.teachers
|
||||
options.periods = data.periods
|
||||
} catch (error) {
|
||||
options.tasks = []
|
||||
options.colleges = []
|
||||
options.classrooms = []
|
||||
options.teachers = []
|
||||
options.periods = []
|
||||
ElMessage.error(apiErrorMessage(error))
|
||||
}
|
||||
@@ -829,9 +875,9 @@ onMounted(async () => {
|
||||
<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><b>{{ formatSessionTime(session) }}</b><span>{{ session.campusName }} · {{ session.buildingName }} {{ session.classroomName }} · 指导:{{ session.instructorNames?.join('、') || '待指定' }}</span></div>
|
||||
<el-button v-if="session.canManage" size="small" text @click="showParticipants(row, session)">预约名单</el-button>
|
||||
<el-button v-if="session.canManage && session.status !== 'Cancelled'" size="small" text type="danger" @click="cancelSession(row, session)">{{ row.status === 'Draft' ? '删除' : '取消' }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
@@ -927,7 +973,7 @@ onMounted(async () => {
|
||||
<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>
|
||||
<span>{{ session.campusName }} · {{ session.buildingName }} {{ session.classroomName }} · 指导:{{ session.instructorNames?.join('、') || '待指定' }}</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>
|
||||
@@ -1033,9 +1079,10 @@ onMounted(async () => {
|
||||
v-for="task in group.tasks"
|
||||
:key="task.id"
|
||||
:value="task.id"
|
||||
:disabled="projectForm.arrangementMode === 'SelfScheduled' && !!selectedTask
|
||||
&& (task.academicTermId !== selectedTask.academicTermId
|
||||
|| task.courseId !== selectedTask.courseId)"
|
||||
:disabled="projectForm.arrangementMode === 'SelfScheduled'
|
||||
&& (task.schedulingMode !== 'Flexible'
|
||||
|| (!!selectedTask && (task.academicTermId !== selectedTask.academicTermId
|
||||
|| task.courseId !== selectedTask.courseId)))"
|
||||
>
|
||||
<b>{{ task.taskNumber }}</b>
|
||||
<span>{{ task.classNames.join('、') || task.name }}</span>
|
||||
@@ -1046,7 +1093,7 @@ onMounted(async () => {
|
||||
</el-checkbox-group>
|
||||
<small v-if="!editingProjectId" class="form-help">
|
||||
<template v-if="projectForm.arrangementMode === 'Centralized'">已选 {{ projectForm.teachingTaskIds.length }} 个教学班;系统会为每个教学班的全部已发布实验课生成项目。</template>
|
||||
<template v-else>已选 {{ projectForm.teachingTaskIds.length }} 个教学班;只能勾选同一学期、同一课程,实验编码、名称、内容和开放日期将一次应用到这些教学任务。</template>
|
||||
<template v-else>自行安排仅可选择“非排时课程”的教学任务;只能勾选同一学期、同一课程,实验编码、名称、内容和开放日期将一次应用到这些教学任务。</template>
|
||||
</small>
|
||||
</el-form-item>
|
||||
<div class="form-grid two">
|
||||
@@ -1101,6 +1148,17 @@ onMounted(async () => {
|
||||
end-placeholder="结束日期"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="projectForm.arrangementMode === 'SelfScheduled'" label="学生选课时间" required>
|
||||
<el-date-picker
|
||||
v-model="projectForm.selectionTimes"
|
||||
type="datetimerange"
|
||||
value-format="YYYY-MM-DDTHH:mm:ss"
|
||||
range-separator="至"
|
||||
start-placeholder="开始选课"
|
||||
end-placeholder="截至选课"
|
||||
/>
|
||||
<small class="form-help">仅在此时间段内,学生可以从该项目的有效实验场次中选择一次;退选或场次取消后可重新选择。</small>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
@@ -1210,6 +1268,16 @@ onMounted(async () => {
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="指导老师(可多选)" required>
|
||||
<el-select v-model="sessionForm.instructorTeacherIds" multiple filterable placeholder="选择该教学任务的指导老师">
|
||||
<el-option
|
||||
v-for="teacher in instructorsForSelectedProject()"
|
||||
:key="teacher.teacherId"
|
||||
:label="`${teacher.teacherNumber} · ${teacher.teacherName}`"
|
||||
:value="teacher.teacherId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="场次备注">
|
||||
<el-input
|
||||
v-model="sessionForm.notes"
|
||||
@@ -1256,11 +1324,11 @@ onMounted(async () => {
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div v-if="batchSessionRows.length" class="batch-session-tools">
|
||||
<span>共 {{ batchSessionRows.length }} 条排课</span>
|
||||
<span>共 {{ batchSessionRows.length }} 条候选时段;可为同一项目追加多个时段。</span>
|
||||
<el-button size="small" @click="applyFirstBatchTime">套用首行日期与节次</el-button>
|
||||
</div>
|
||||
<div class="batch-session-table">
|
||||
<article v-for="(row, index) in batchSessionRows" :key="row.projectId" class="batch-session-row">
|
||||
<article v-for="(row, index) in batchSessionRows" :key="row.rowKey" class="batch-session-row">
|
||||
<div class="batch-project-cell">
|
||||
<span>{{ index + 1 }}</span>
|
||||
<div>
|
||||
@@ -1291,7 +1359,19 @@ onMounted(async () => {
|
||||
:value="room.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select v-model="row.instructorTeacherIds" multiple filterable collapse-tags placeholder="指导老师">
|
||||
<el-option
|
||||
v-for="teacher in instructorsForProject(row.projectId)"
|
||||
:key="teacher.teacherId"
|
||||
:label="teacher.teacherName"
|
||||
:value="teacher.teacherId"
|
||||
/>
|
||||
</el-select>
|
||||
<el-input-number v-model="row.capacity" :min="1" :max="10000" controls-position="right" />
|
||||
<span class="batch-session-actions">
|
||||
<el-button size="small" text type="primary" @click="addBatchSessionRow(row.projectId, row)">追加时段</el-button>
|
||||
<el-button size="small" text type="danger" @click="removeBatchSessionRow(row)">移除</el-button>
|
||||
</span>
|
||||
</article>
|
||||
</div>
|
||||
<el-empty v-if="!batchSessionRows.length" :image-size="60" description="选择实验项目后,在同一张表中完成排课" />
|
||||
@@ -1498,7 +1578,7 @@ onMounted(async () => {
|
||||
.batch-session-table { display: grid; gap: 8px; overflow-x: auto; padding-bottom: 4px; }
|
||||
.batch-session-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(230px, 1.4fr) 150px 120px 110px minmax(230px, 1.3fr) 110px;
|
||||
grid-template-columns: minmax(210px, 1.2fr) 145px 110px 100px minmax(200px, 1.1fr) minmax(150px, .8fr) 100px auto;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
min-width: 1000px;
|
||||
@@ -1506,6 +1586,7 @@ onMounted(async () => {
|
||||
border: 1px solid #dce5e9;
|
||||
background: #f9fbfc;
|
||||
}
|
||||
.batch-session-actions { display: flex; align-items: center; gap: 2px; white-space: nowrap; }
|
||||
.batch-project-cell { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
||||
.batch-project-cell > span { display: grid; width: 25px; height: 25px; place-items: center; border-radius: 50%; background: #e5f0f5; color: var(--lab-blue); font: 700 11px/1 Consolas, monospace; }
|
||||
.batch-project-cell > div { display: grid; gap: 3px; min-width: 0; }
|
||||
|
||||
Reference in New Issue
Block a user