同学期、同课程、同教学班的实验项目合并展示,组内连续排列。
管理列表改为数据库筛选、按教学任务服务端分页(10–100 条/页)。 增加开课学院、课程名称/编码、教学班、任课教师、教学任务号筛选。 增加当前页草稿项目的批量选中、批量发布、批量删除;后端会再次校验权限、状态和发布条件。 确认框改为固定居中白色底板与阴影,不再出现只有文字按钮、没有底色的情况。
This commit is contained in:
@@ -343,6 +343,29 @@ button { cursor: pointer; }
|
||||
.form-grid.compact { align-items: center; }
|
||||
.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 的定位与底色不能依赖其伪元素:部分浏览器在遮罩层中会将其压到左上角。 */
|
||||
.el-overlay.is-message-box {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
}
|
||||
.el-overlay.is-message-box .el-overlay-message-box {
|
||||
display: block !important;
|
||||
width: min(420px, calc(100vw - 32px));
|
||||
min-height: 0 !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.el-overlay.is-message-box .el-overlay-message-box::after { display: none !important; }
|
||||
.el-overlay.is-message-box .el-message-box {
|
||||
width: 100%;
|
||||
margin: 0 !important;
|
||||
color: var(--ink);
|
||||
background: #fff !important;
|
||||
box-shadow: 0 18px 48px rgba(18, 37, 63, .28);
|
||||
}
|
||||
.password-reset-form { margin-top: 18px; }
|
||||
|
||||
.registry-switch { display: grid; grid-template-columns: 1fr 1fr 180px; min-height: 112px; border: 1px solid var(--line); background: white; }
|
||||
|
||||
@@ -16,14 +16,24 @@ const loading = ref(false)
|
||||
const terms = ref<any[]>([])
|
||||
const termId = ref('')
|
||||
const projects = ref<any[]>([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const selectedProjectIds = ref<string[]>([])
|
||||
const options = reactive({
|
||||
tasks: [] as any[],
|
||||
colleges: [] as any[],
|
||||
scheduleEntries: [] as any[],
|
||||
classrooms: [] as any[],
|
||||
periods: [] as any[],
|
||||
})
|
||||
const modeFilter = ref('')
|
||||
const statusFilter = ref('')
|
||||
const offeringCollegeId = ref('')
|
||||
const courseKeyword = ref('')
|
||||
const classKeyword = ref('')
|
||||
const teacherKeyword = ref('')
|
||||
const taskKeyword = ref('')
|
||||
|
||||
const projectDialog = ref(false)
|
||||
const editingProjectId = ref('')
|
||||
@@ -68,6 +78,51 @@ const batchProjectOptions = computed(() =>
|
||||
projects.value.filter((project) =>
|
||||
project.status !== 'Closed' && project.arrangementMode === 'SelfScheduled'),
|
||||
)
|
||||
const taskGroups = computed(() => {
|
||||
const groups = new Map<string, any>()
|
||||
options.tasks.forEach((task) => {
|
||||
const key = `${task.academicTermId}-${task.courseId}`
|
||||
const group = groups.get(key) ?? {
|
||||
key,
|
||||
termName: task.termName,
|
||||
courseCode: task.courseCode,
|
||||
courseName: task.courseName,
|
||||
tasks: [],
|
||||
}
|
||||
group.tasks.push(task)
|
||||
groups.set(key, group)
|
||||
})
|
||||
return [...groups.values()]
|
||||
})
|
||||
const projectGroups = computed(() => {
|
||||
const groups = new Map<string, any>()
|
||||
projects.value.forEach((project) => {
|
||||
const classKey = [...(project.classNames ?? [])].sort().join('|')
|
||||
const key = `${project.academicTermId}-${project.courseCode}-${classKey}`
|
||||
const group = groups.get(key) ?? {
|
||||
key,
|
||||
termName: project.termName,
|
||||
courseCode: project.courseCode,
|
||||
courseName: project.courseName,
|
||||
classNames: project.classNames,
|
||||
teacherNames: project.teacherNames,
|
||||
taskNumbers: [],
|
||||
projects: [],
|
||||
}
|
||||
if (!group.taskNumbers.includes(project.taskNumber)) group.taskNumbers.push(project.taskNumber)
|
||||
group.projects.push(project)
|
||||
groups.set(key, group)
|
||||
})
|
||||
return [...groups.values()]
|
||||
})
|
||||
const draftSelectedProjectIds = computed(() =>
|
||||
selectedProjectIds.value.filter((id) =>
|
||||
projects.value.some((project) => project.id === id && project.status === 'Draft')),
|
||||
)
|
||||
const allPageDraftSelected = computed(() => {
|
||||
const ids = projects.value.filter((project) => project.status === 'Draft').map((project) => project.id)
|
||||
return ids.length > 0 && ids.every((id) => selectedProjectIds.value.includes(id))
|
||||
})
|
||||
const activePeriods = computed(() =>
|
||||
options.periods.filter((period) =>
|
||||
!selectedProject.value
|
||||
@@ -119,6 +174,11 @@ function onScheduleEntryChange() {
|
||||
onTaskChange()
|
||||
}
|
||||
|
||||
function formatScheduleEntry(entry: any) {
|
||||
const classes = entry.classNames?.join('、') || '选课学生'
|
||||
return `${entry.courseCode} · ${entry.courseName} · ${entry.taskNumber} · ${classes} · 星期 ${entry.dayOfWeek} 第 ${entry.startPeriod}–${entry.startPeriod + entry.periodCount - 1} 节 · ${entry.campusName} ${entry.buildingName} ${entry.classroomName}`
|
||||
}
|
||||
|
||||
function openCreateProject() {
|
||||
resetProjectForm()
|
||||
projectDialog.value = true
|
||||
@@ -141,10 +201,10 @@ function openEditProject(project: any) {
|
||||
|
||||
async function saveProject() {
|
||||
if (!projectForm.teachingTaskIds.length ||
|
||||
(projectForm.arrangementMode === 'Centralized' && !projectForm.scheduleEntryId) ||
|
||||
(editingProjectId.value && projectForm.arrangementMode === 'Centralized' && !projectForm.scheduleEntryId) ||
|
||||
!projectForm.code.trim()
|
||||
|| !projectForm.name.trim() || projectForm.dates.length !== 2) {
|
||||
ElMessage.warning(projectForm.arrangementMode === 'Centralized'
|
||||
ElMessage.warning(projectForm.arrangementMode === 'Centralized' && editingProjectId.value
|
||||
? '请选择课表实验课,并填写项目编码、名称和开放日期'
|
||||
: '请填写教学任务、项目编码、名称和开放日期')
|
||||
return
|
||||
@@ -164,16 +224,15 @@ async function saveProject() {
|
||||
if (editingProjectId.value) {
|
||||
await http.put(`/experiments/${editingProjectId.value}`, payload)
|
||||
ElMessage.success('实验项目已更新')
|
||||
} else if (projectForm.arrangementMode === 'Centralized') {
|
||||
await http.post('/experiments', payload)
|
||||
ElMessage.success('实验项目已绑定课表实验课')
|
||||
} else {
|
||||
await http.post('/experiments/batch', {
|
||||
...payload,
|
||||
teachingTaskId: undefined,
|
||||
teachingTaskIds: projectForm.teachingTaskIds,
|
||||
})
|
||||
ElMessage.success(`已为 ${projectForm.teachingTaskIds.length} 个教学任务创建实验项目`)
|
||||
ElMessage.success(projectForm.arrangementMode === 'Centralized'
|
||||
? `已按 ${projectForm.teachingTaskIds.length} 个教学班的全部实验课生成项目`
|
||||
: `已为 ${projectForm.teachingTaskIds.length} 个教学任务创建实验项目`)
|
||||
}
|
||||
projectDialog.value = false
|
||||
await load()
|
||||
@@ -336,6 +395,57 @@ async function deleteProject(project: any) {
|
||||
}
|
||||
}
|
||||
|
||||
function togglePageDraftSelection(value: unknown) {
|
||||
const ids = projects.value.filter((project) => project.status === 'Draft').map((project) => project.id)
|
||||
selectedProjectIds.value = Boolean(value)
|
||||
? [...new Set([...selectedProjectIds.value, ...ids])]
|
||||
: selectedProjectIds.value.filter((id) => !ids.includes(id))
|
||||
}
|
||||
|
||||
function toggleProjectSelection(projectId: string, value: unknown) {
|
||||
selectedProjectIds.value = Boolean(value)
|
||||
? [...new Set([...selectedProjectIds.value, projectId])]
|
||||
: selectedProjectIds.value.filter((id) => id !== projectId)
|
||||
}
|
||||
|
||||
async function publishSelectedProjects() {
|
||||
const ids = draftSelectedProjectIds.value
|
||||
if (!ids.length) return
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`将发布选中的 ${ids.length} 个草稿实验项目,并通知对应学生。`,
|
||||
'批量发布实验项目',
|
||||
{ confirmButtonText: '确认发布', cancelButtonText: '取消', type: 'warning' },
|
||||
)
|
||||
await http.post('/experiments/batch/publish', { projectIds: ids })
|
||||
ElMessage.success(`已发布 ${ids.length} 个实验项目`)
|
||||
selectedProjectIds.value = []
|
||||
await load()
|
||||
} catch (error: any) {
|
||||
if (error === 'cancel' || error === 'close') return
|
||||
ElMessage.error(apiErrorMessage(error))
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteSelectedProjects() {
|
||||
const ids = draftSelectedProjectIds.value
|
||||
if (!ids.length) return
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`将删除选中的 ${ids.length} 个草稿实验项目及其未发布场次。`,
|
||||
'批量删除实验项目',
|
||||
{ confirmButtonText: '删除', cancelButtonText: '取消', type: 'error' },
|
||||
)
|
||||
await http.delete('/experiments/batch', { data: { projectIds: ids } })
|
||||
ElMessage.success(`已删除 ${ids.length} 个实验项目`)
|
||||
selectedProjectIds.value = []
|
||||
await load()
|
||||
} catch (error: any) {
|
||||
if (error === 'cancel' || error === 'close') return
|
||||
ElMessage.error(apiErrorMessage(error))
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelSession(project: any, session: any) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
@@ -424,14 +534,20 @@ async function loadOptions() {
|
||||
if (isStudent.value) return
|
||||
try {
|
||||
const { data } = await http.get('/experiments/options', {
|
||||
params: { academicTermId: termId.value || undefined },
|
||||
params: {
|
||||
academicTermId: termId.value || undefined,
|
||||
offeringCollegeId: offeringCollegeId.value || undefined,
|
||||
courseKeyword: courseKeyword.value.trim() || undefined,
|
||||
},
|
||||
})
|
||||
options.tasks = data.tasks
|
||||
options.colleges = data.colleges
|
||||
options.scheduleEntries = data.scheduleEntries
|
||||
options.classrooms = data.classrooms
|
||||
options.periods = data.periods
|
||||
} catch (error) {
|
||||
options.tasks = []
|
||||
options.colleges = []
|
||||
options.classrooms = []
|
||||
options.periods = []
|
||||
ElMessage.error(apiErrorMessage(error))
|
||||
@@ -445,17 +561,28 @@ async function load() {
|
||||
projects.value = (await http.get('/experiments/student', {
|
||||
params: { academicTermId: termId.value || undefined },
|
||||
})).data
|
||||
total.value = projects.value.length
|
||||
} else {
|
||||
projects.value = (await http.get('/experiments/management', {
|
||||
const { data } = await http.get('/experiments/management', {
|
||||
params: {
|
||||
academicTermId: termId.value || undefined,
|
||||
arrangementMode: modeFilter.value || undefined,
|
||||
status: statusFilter.value || undefined,
|
||||
offeringCollegeId: offeringCollegeId.value || undefined,
|
||||
courseKeyword: courseKeyword.value.trim() || undefined,
|
||||
classKeyword: classKeyword.value.trim() || undefined,
|
||||
teacherKeyword: teacherKeyword.value.trim() || undefined,
|
||||
taskKeyword: taskKeyword.value.trim() || undefined,
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
},
|
||||
})).data
|
||||
})
|
||||
projects.value = data.items
|
||||
total.value = data.total
|
||||
}
|
||||
} catch (error) {
|
||||
projects.value = []
|
||||
total.value = 0
|
||||
ElMessage.error(apiErrorMessage(error))
|
||||
} finally {
|
||||
loading.value = false
|
||||
@@ -463,10 +590,30 @@ async function load() {
|
||||
}
|
||||
|
||||
async function changeTerm() {
|
||||
page.value = 1
|
||||
selectedProjectIds.value = []
|
||||
await loadOptions()
|
||||
await load()
|
||||
}
|
||||
|
||||
async function searchTaskOptions() {
|
||||
page.value = 1
|
||||
selectedProjectIds.value = []
|
||||
await loadOptions()
|
||||
await load()
|
||||
}
|
||||
|
||||
async function changePage(nextPage: number) {
|
||||
page.value = nextPage
|
||||
selectedProjectIds.value = []
|
||||
await load()
|
||||
}
|
||||
|
||||
async function changePageSize(size: number) {
|
||||
pageSize.value = size
|
||||
await changePage(1)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
terms.value = (await http.get('/base-data/terms')).data
|
||||
@@ -524,6 +671,13 @@ onMounted(async () => {
|
||||
/>
|
||||
</el-select>
|
||||
<template v-if="!isStudent">
|
||||
<el-select v-model="offeringCollegeId" clearable filterable placeholder="开课学院" @change="searchTaskOptions">
|
||||
<el-option v-for="college in options.colleges" :key="college.collegeId" :label="college.collegeName" :value="college.collegeId" />
|
||||
</el-select>
|
||||
<el-input v-model="courseKeyword" clearable placeholder="课程名称或编码" @keyup.enter="searchTaskOptions" @clear="searchTaskOptions" />
|
||||
<el-input v-model="classKeyword" clearable placeholder="教学班" @keyup.enter="searchTaskOptions" @clear="searchTaskOptions" />
|
||||
<el-input v-model="teacherKeyword" clearable placeholder="任课教师" @keyup.enter="searchTaskOptions" @clear="searchTaskOptions" />
|
||||
<el-input v-model="taskKeyword" clearable placeholder="教学任务号" @keyup.enter="searchTaskOptions" @clear="searchTaskOptions" />
|
||||
<el-select v-model="modeFilter" clearable placeholder="全部安排方式" @change="load">
|
||||
<el-option label="集中安排" value="Centralized" />
|
||||
<el-option label="自行安排" value="SelfScheduled" />
|
||||
@@ -534,12 +688,33 @@ onMounted(async () => {
|
||||
<el-option label="已关闭" value="Closed" />
|
||||
</el-select>
|
||||
</template>
|
||||
<span class="result-note">共 {{ projects.length }} 个实验项目</span>
|
||||
<span class="result-note">共 {{ total }} 个教学班课程</span>
|
||||
</section>
|
||||
|
||||
<section v-if="!isStudent" class="experiment-batch-bar">
|
||||
<el-checkbox :model-value="allPageDraftSelected" :indeterminate="!!draftSelectedProjectIds.length && !allPageDraftSelected" @change="togglePageDraftSelection">
|
||||
选中当前页全部草稿
|
||||
</el-checkbox>
|
||||
<span>已选 {{ draftSelectedProjectIds.length }} 个草稿项目</span>
|
||||
<el-button size="small" type="primary" :disabled="!draftSelectedProjectIds.length" @click="publishSelectedProjects">批量发布</el-button>
|
||||
<el-button size="small" type="danger" plain :disabled="!draftSelectedProjectIds.length" @click="deleteSelectedProjects">批量删除</el-button>
|
||||
</section>
|
||||
|
||||
<section v-loading="loading" class="project-list">
|
||||
<section v-for="group in projectGroups" :key="group.key" class="project-group">
|
||||
<header class="project-group-head">
|
||||
<div>
|
||||
<span>{{ group.termName }}</span>
|
||||
<h3>{{ group.courseCode }} · {{ group.courseName }}</h3>
|
||||
</div>
|
||||
<div class="project-group-task">
|
||||
<b>{{ group.taskNumbers.join('、') }}</b>
|
||||
<span>{{ group.classNames?.join('、') || '选课学生' }}</span>
|
||||
</div>
|
||||
</header>
|
||||
<div class="project-group-projects">
|
||||
<article
|
||||
v-for="project in projects"
|
||||
v-for="project in group.projects"
|
||||
:key="project.id"
|
||||
class="project-card"
|
||||
:class="[
|
||||
@@ -551,9 +726,15 @@ onMounted(async () => {
|
||||
<div class="project-identity">
|
||||
<span class="project-code">{{ project.courseCode }} · {{ project.code }}</span>
|
||||
<h3>{{ project.name }}</h3>
|
||||
<p>{{ project.courseName }} · {{ project.taskNumber }}</p>
|
||||
<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"
|
||||
@@ -600,8 +781,8 @@ onMounted(async () => {
|
||||
<article class="session-ticket">
|
||||
<div class="ticket-date"><strong>课表</strong><span>固定</span></div>
|
||||
<div class="ticket-body">
|
||||
<b>星期 {{ project.scheduleEntry.dayOfWeek }} · 第 {{ project.scheduleEntry.startPeriod }}–{{ project.scheduleEntry.startPeriod + project.scheduleEntry.periodCount - 1 }} 节</b>
|
||||
<span>第 {{ project.scheduleEntry.startWeek }}–{{ project.scheduleEntry.endWeek }} 周 · {{ project.scheduleEntry.campusName }} · {{ project.scheduleEntry.buildingName }} {{ project.scheduleEntry.classroomName }}</span>
|
||||
<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>
|
||||
@@ -736,6 +917,8 @@ onMounted(async () => {
|
||||
</el-button>
|
||||
</footer>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<el-empty
|
||||
v-if="!loading && !projects.length"
|
||||
@@ -747,6 +930,19 @@ onMounted(async () => {
|
||||
</el-empty>
|
||||
</section>
|
||||
|
||||
<el-pagination
|
||||
v-if="!isStudent && total > pageSize"
|
||||
class="experiment-pagination"
|
||||
background
|
||||
layout="total, sizes, prev, pager, next"
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="total"
|
||||
@current-change="changePage"
|
||||
@size-change="changePageSize"
|
||||
/>
|
||||
|
||||
<el-dialog
|
||||
v-model="projectDialog"
|
||||
:title="editingProjectId ? '编辑实验项目' : '批量设置实验任务'"
|
||||
@@ -756,26 +952,24 @@ onMounted(async () => {
|
||||
<el-form label-position="top" class="experiment-form">
|
||||
<div class="form-section">
|
||||
<header><span>PROJECT</span><b>规定实验项目</b></header>
|
||||
<el-form-item v-if="projectForm.arrangementMode === 'Centralized'" label="已发布课表中的实验课" required>
|
||||
<el-form-item v-if="editingProjectId && projectForm.arrangementMode === 'Centralized'" label="已发布课表中的实验课" required>
|
||||
<el-select v-model="projectForm.scheduleEntryId" filterable :disabled="!!editingProjectId" placeholder="选择已安排实验室的实验课" @change="onScheduleEntryChange">
|
||||
<el-option
|
||||
v-for="entry in options.scheduleEntries"
|
||||
:key="entry.id"
|
||||
:label="`${entry.courseCode} · ${entry.courseName} · ${entry.taskNumber} · 星期 ${entry.dayOfWeek} 第 ${entry.startPeriod}–${entry.startPeriod + entry.periodCount - 1} 节 · ${entry.campusName} ${entry.buildingName} ${entry.classroomName}`"
|
||||
:label="formatScheduleEntry(entry)"
|
||||
:value="entry.id"
|
||||
/>
|
||||
</el-select>
|
||||
<small class="form-help">集中实验复用课表的时间和实验室,不会再生成独立实验场次。</small>
|
||||
<small class="form-help">显示当前管理范围内全部已发布、已安排实验室的实验课;集中实验复用其时间和实验室,不会再生成独立实验场次。</small>
|
||||
</el-form-item>
|
||||
<el-form-item v-else :label="editingProjectId ? '所属教学任务' : '适用教学任务(可多选)'" required>
|
||||
<el-form-item v-else :label="editingProjectId ? '所属教学任务' : '适用教学班(可多选)'" required>
|
||||
<el-select
|
||||
v-if="editingProjectId"
|
||||
v-model="projectForm.teachingTaskIds"
|
||||
filterable
|
||||
:multiple="!editingProjectId"
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
:disabled="!!editingProjectId"
|
||||
placeholder="选择同一学期、同一课程的已发布教学任务"
|
||||
disabled
|
||||
placeholder="所属教学任务"
|
||||
@change="onTaskChange"
|
||||
>
|
||||
<el-option
|
||||
@@ -783,13 +977,45 @@ onMounted(async () => {
|
||||
:key="task.id"
|
||||
:label="`${task.courseCode} · ${task.courseName} · ${task.taskNumber}`"
|
||||
:value="task.id"
|
||||
:disabled="!!selectedTask
|
||||
&& (task.academicTermId !== selectedTask.academicTermId
|
||||
|| task.courseId !== selectedTask.courseId)"
|
||||
/>
|
||||
</el-select>
|
||||
<el-checkbox-group
|
||||
v-else
|
||||
v-model="projectForm.teachingTaskIds"
|
||||
class="grouped-task-picker"
|
||||
@change="onTaskChange"
|
||||
>
|
||||
<div class="task-option-filters">
|
||||
<el-select v-model="offeringCollegeId" clearable filterable placeholder="开课学院" @change="searchTaskOptions">
|
||||
<el-option v-for="college in options.colleges" :key="college.collegeId" :label="college.collegeName" :value="college.collegeId" />
|
||||
</el-select>
|
||||
<el-input v-model="courseKeyword" clearable placeholder="课程名称或代码" @keyup.enter="searchTaskOptions" />
|
||||
<el-button @click="searchTaskOptions">查询</el-button>
|
||||
</div>
|
||||
<section v-for="group in taskGroups" :key="group.key" class="task-picker-course">
|
||||
<header>
|
||||
<span>{{ group.termName }}</span>
|
||||
<b>{{ group.courseCode }} · {{ group.courseName }}</b>
|
||||
</header>
|
||||
<div>
|
||||
<el-checkbox
|
||||
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)"
|
||||
>
|
||||
<b>{{ task.taskNumber }}</b>
|
||||
<span>{{ task.classNames.join('、') || task.name }}</span>
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</section>
|
||||
<el-empty v-if="!taskGroups.length" :image-size="46" description="没有匹配的已发布教学班,请调整筛选条件" />
|
||||
</el-checkbox-group>
|
||||
<small v-if="!editingProjectId" class="form-help">
|
||||
已选 {{ projectForm.teachingTaskIds.length }} 个;实验编码、名称、内容和开放日期将一次应用到这些教学任务。
|
||||
<template v-if="projectForm.arrangementMode === 'Centralized'">已选 {{ projectForm.teachingTaskIds.length }} 个教学班;系统会为每个教学班的全部已发布实验课生成项目。</template>
|
||||
<template v-else>已选 {{ projectForm.teachingTaskIds.length }} 个教学班;只能勾选同一学期、同一课程,实验编码、名称、内容和开放日期将一次应用到这些教学任务。</template>
|
||||
</small>
|
||||
</el-form-item>
|
||||
<div class="form-grid two">
|
||||
@@ -1066,14 +1292,27 @@ onMounted(async () => {
|
||||
.rail-switch i { width: 30px; height: 7px; border: 2px solid #7794a5; border-radius: 999px; background: #fff; }
|
||||
.experiment-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 16px; }
|
||||
.experiment-toolbar .el-select { width: 210px; }
|
||||
.experiment-toolbar .el-input { width: 172px; }
|
||||
.result-note { margin-left: auto; color: var(--muted); font-size: 12px; }
|
||||
.experiment-batch-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; min-height: 46px; margin: -6px 0 16px; padding: 8px 12px; border: 1px solid #cddce3; background: #f7fafb; }
|
||||
.experiment-batch-bar > span { margin-right: auto; color: #5c7481; font-size: 12px; }
|
||||
.experiment-pagination { justify-content: flex-end; margin-top: 4px; }
|
||||
.project-list { display: grid; gap: 14px; min-height: 160px; }
|
||||
.project-group { overflow: hidden; border: 1px solid #cddce3; background: #f6fafb; }
|
||||
.project-group-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 13px 17px; border-left: 5px solid var(--lab-blue); background: #edf4f6; }
|
||||
.project-group-head > div:first-child { display: grid; gap: 3px; }
|
||||
.project-group-head h3 { margin: 0; color: var(--lab-ink); font-size: 15px; }
|
||||
.project-group-head > div:first-child span { color: #68808d; font: 700 10px/1 Consolas, monospace; letter-spacing: .06em; }
|
||||
.project-group-task { display: grid; justify-items: end; gap: 3px; color: #5c7481; font-size: 11px; text-align: right; }
|
||||
.project-group-task b { color: #365364; font-size: 12px; }
|
||||
.project-group-projects { display: grid; gap: 0; padding: 0; }
|
||||
.project-card {
|
||||
overflow: hidden;
|
||||
border: 1px solid #dce5ea;
|
||||
border-top: 4px solid var(--lab-blue);
|
||||
border: 0;
|
||||
border-top: 1px solid #dce5ea;
|
||||
border-left: 4px solid var(--lab-blue);
|
||||
background: #fff;
|
||||
box-shadow: 0 7px 22px rgb(35 67 85 / 5%);
|
||||
box-shadow: none;
|
||||
}
|
||||
.project-card.is-flexible { border-top-color: var(--lab-teal); }
|
||||
.project-card.is-closed { opacity: .82; }
|
||||
@@ -1140,6 +1379,16 @@ onMounted(async () => {
|
||||
.student-booking-summary span { display: grid; gap: 2px; color: #365f5b; font-size: 12px; }
|
||||
.student-booking-summary b { font-size: 10px; text-transform: uppercase; letter-spacing: .06em; }
|
||||
.experiment-form { display: grid; gap: 13px; }
|
||||
.grouped-task-picker { display: grid; gap: 10px; max-height: 310px; overflow: auto; padding: 10px; border: 1px solid #d8e3e8; background: #fff; }
|
||||
.task-option-filters { display: grid; grid-template-columns: minmax(150px, 1fr) minmax(180px, 1.2fr) auto; gap: 8px; }
|
||||
.task-picker-course { overflow: hidden; border: 1px solid #e0e8eb; }
|
||||
.task-picker-course header { display: flex; align-items: baseline; gap: 8px; padding: 8px 10px; background: #f3f7f8; }
|
||||
.task-picker-course header span { color: #738994; font: 700 10px/1 Consolas, monospace; }
|
||||
.task-picker-course header b { color: #314f60; font-size: 12px; }
|
||||
.task-picker-course > div { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 2px 12px; padding: 8px 10px; }
|
||||
.task-picker-course :deep(.el-checkbox) { height: auto; margin-right: 0; white-space: normal; }
|
||||
.task-picker-course :deep(.el-checkbox__label) { display: inline-flex; gap: 6px; min-width: 0; color: #536b78; font-size: 12px; }
|
||||
.task-picker-course :deep(.el-checkbox__label b) { flex: 0 0 auto; color: #284757; }
|
||||
.form-help { display: block; margin-top: 7px; color: var(--muted); line-height: 1.5; }
|
||||
.form-section { padding: 14px 15px 1px; border: 1px solid var(--line); background: #fbfcfd; }
|
||||
.form-section > header { display: flex; align-items: baseline; gap: 9px; margin-bottom: 13px; }
|
||||
|
||||
Reference in New Issue
Block a user