第 {{ group.semester }} 学期
+-
+
-
+ + {{ course.moduleName }} + {{ course.courseName }} + {{ course.courseCode }} · {{ courseTypeLabels[course.type] }} ++ {{ course.credits }}学分 +
+
本学期暂未安排课程
+diff --git a/web/src/style.css b/web/src/style.css index 351df9e..8ab55fb 100644 --- a/web/src/style.css +++ b/web/src/style.css @@ -431,6 +431,8 @@ button { cursor: pointer; } .module-toolbar b, .module-toolbar span { display: block; } .module-toolbar b { font-size: 15px; } .module-toolbar span { margin-top: 4px; color: var(--muted); font-size: 10px; } +.curriculum-view-actions { display: flex; align-items: center; gap: 10px; } +.curriculum-view-actions .el-button + .el-button { margin-left: 0; } .curriculum-module { margin-top: 12px; border: 1px solid var(--line); } .curriculum-module > header { min-height: 72px; padding: 13px 16px; display: flex; align-items: center; gap: 18px; background: #f8fafb; border-bottom: 1px solid var(--line); } .curriculum-module > header > div:first-child { min-width: 160px; } @@ -439,6 +441,24 @@ button { cursor: pointer; } .curriculum-module > header p { margin: 0 auto 0 0; color: var(--muted); font-size: 11px; } .curriculum-module > header > div:last-child { display: flex; align-items: center; white-space: nowrap; } .curriculum-course-table { min-height: 80px; } +.semester-view { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; } +.semester-card { min-width: 0; border: 1px solid var(--line); background: #fff; } +.semester-card > header { min-height: 70px; padding: 14px 16px; display: flex; align-items: center; justify-content: space-between; background: linear-gradient(135deg, #f4faf9, #f8fafb); border-bottom: 1px solid var(--line); } +.semester-card > header span { color: var(--teal); font: 700 9px/1 Consolas, monospace; letter-spacing: .12em; } +.semester-card h4 { margin: 7px 0 0; font-size: 15px; } +.semester-card > header > b { color: #1d6b68; font: 700 18px/1 Consolas, monospace; } +.semester-summary { display: flex; gap: 14px; padding: 9px 16px; color: var(--muted); font-size: 11px; border-bottom: 1px solid var(--line); } +.semester-summary span + span { padding-left: 14px; border-left: 1px solid var(--line); } +.semester-courses { margin: 0; padding: 0; list-style: none; } +.semester-courses li { min-height: 68px; padding: 11px 16px; display: flex; align-items: center; justify-content: space-between; gap: 12px; border-bottom: 1px solid #edf0f2; } +.semester-courses li:last-child { border-bottom: none; } +.semester-courses li > div { min-width: 0; display: grid; gap: 3px; } +.semester-courses li span { color: var(--teal); font-size: 10px; } +.semester-courses li b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; } +.semester-courses li small { color: var(--muted); font-size: 10px; } +.semester-courses li > strong { flex: none; font: 700 15px/1 Consolas, monospace; } +.semester-courses li > strong small { margin-left: 3px; font: 400 9px/1 inherit; } +.semester-empty { margin: 0; padding: 25px 16px; color: var(--muted); font-size: 11px; text-align: center; } .task-summary { min-height: 82px; padding: 15px 22px; display: flex; align-items: center; gap: 28px; color: white; background: linear-gradient(108deg, #17295a, #263f80); } .task-summary > div { min-width: 170px; display: flex; align-items: baseline; gap: 8px; } .task-summary span, .task-summary small { color: #b8c1de; font-size: 10px; } @@ -1280,6 +1300,9 @@ button { cursor: pointer; } .plan-metrics > div:nth-child(-n+2) { border-bottom: 1px solid var(--line); } .curriculum-module > header { align-items: flex-start; flex-wrap: wrap; } .curriculum-module > header p { order: 3; width: 100%; } + .module-toolbar { align-items: flex-start; flex-direction: column; gap: 12px; } + .curriculum-view-actions { width: 100%; justify-content: space-between; } + .semester-view { grid-template-columns: 1fr; } .task-summary { align-items: flex-start; flex-direction: column; gap: 12px; } .task-summary > div { width: 100%; } .task-summary p { padding: 12px 0 0; border-left: none; border-top: 1px solid rgba(255,255,255,.16); line-height: 1.6; } diff --git a/web/src/views/CurriculumView.vue b/web/src/views/CurriculumView.vue index fcdcd1e..50c1c12 100644 --- a/web/src/views/CurriculumView.vue +++ b/web/src/views/CurriculumView.vue @@ -18,6 +18,7 @@ const planDialog = ref(false) const moduleDialog = ref(false) const courseDialog = ref(false) const cloneDialog = ref(false) +const detailView = ref<'structure' | 'semester'>('structure') const editingPlanId = ref('') const editingPlanStatus = ref('') const editingModuleId = ref('') @@ -74,6 +75,34 @@ const configuredCredits = computed(() => 0, ) ?? 0, ) +const semesterGroups = computed(() => { + if (!selected.value) return [] + + const courses = selected.value.modules.flatMap((module: any) => + module.courses.map((course: any) => ({ + ...course, + moduleCode: module.code, + moduleName: module.name, + })), + ) + const semesterCount = Number(selected.value.schoolingYears) * 2 + + return Array.from({ length: semesterCount }, (_, index) => { + const semester = index + 1 + const items = courses + .filter((course: any) => Number(course.recommendedSemester) === semester) + .sort((first: any, second: any) => + first.moduleCode.localeCompare(second.moduleCode) || + first.courseCode.localeCompare(second.courseCode), + ) + return { + semester, + courses: items, + credits: items.reduce((sum: number, course: any) => sum + Number(course.credits), 0), + totalHours: items.reduce((sum: number, course: any) => sum + Number(course.totalHours), 0), + } + }) +}) async function loadPlans(keepSelection = true) { loading.value = true @@ -450,12 +479,21 @@ onMounted(async () => {
+本学期暂未安排课程
+