diff --git a/src/Jiaowu.Api/Controllers/VenueDisplaysController.cs b/src/Jiaowu.Api/Controllers/VenueDisplaysController.cs index ffabf50..c0b460c 100644 --- a/src/Jiaowu.Api/Controllers/VenueDisplaysController.cs +++ b/src/Jiaowu.Api/Controllers/VenueDisplaysController.cs @@ -30,9 +30,33 @@ public sealed class VenueDisplaysController( term.Id, null, false, null, null, cancellationToken); var today = DateOnly.FromDateTime(DateTime.Now); var (week, day) = ClassroomReservationAvailabilityService.ResolveTeachingWeek(term, today); - var entries = sheet?.Entries.Where(x => x.DayOfWeek == day && x.StartWeek <= week && x.EndWeek >= week && + var entries = sheet?.Entries + .Where(x => x.DayOfWeek == day && x.StartWeek <= week && x.EndWeek >= week && (x.WeekPattern == WeekPattern.All || x.WeekPattern == WeekPattern.Odd && week % 2 == 1 || x.WeekPattern == WeekPattern.Even && week % 2 == 0)) - .Select(x => new { x.CourseName, x.TeacherNames, x.ClassNames, x.StartPeriod, x.PeriodCount }) ?? []; + .Select(x => new VenueDisplayClassroomEntry( + x.CourseName, + x.TeacherNames, + x.ClassNames, + x.StartPeriod, + x.PeriodCount, + false)) + .ToList() ?? []; + var reservations = await db.ClassroomReservations.AsNoTracking() + .Where(x => x.AcademicTermId == term.Id && + x.ClassroomId == classroomId && + x.Status == ClassroomReservationStatus.Approved && + x.ReservationDate == today) + .OrderBy(x => x.StartPeriod) + .Select(x => new VenueDisplayClassroomEntry( + "教室借用", + new[] { x.ApplicantName }, + new[] { x.Purpose }, + x.StartPeriod, + x.PeriodCount, + true)) + .ToListAsync(cancellationToken); + entries.AddRange(reservations); + entries.Sort((left, right) => left.StartPeriod.CompareTo(right.StartPeriod)); return Ok(new { Classroom = classroom, Term = term.Name, Today = today, Week = week, Slots = sheet?.Slots ?? [], Entries = entries }); } @@ -59,3 +83,11 @@ public sealed class VenueDisplaysController( private Task CurrentTermAsync(CancellationToken cancellationToken) => db.AcademicTerms.AsNoTracking() .Where(x => x.IsEnabled && x.IsCurrent).FirstOrDefaultAsync(cancellationToken); } + +public sealed record VenueDisplayClassroomEntry( + string CourseName, + IEnumerable TeacherNames, + IEnumerable ClassNames, + int StartPeriod, + int PeriodCount, + bool IsReservation); diff --git a/tests/Jiaowu.Api.Tests/VenueDisplaysControllerTests.cs b/tests/Jiaowu.Api.Tests/VenueDisplaysControllerTests.cs new file mode 100644 index 0000000..a3d10d8 --- /dev/null +++ b/tests/Jiaowu.Api.Tests/VenueDisplaysControllerTests.cs @@ -0,0 +1,66 @@ +using System.Text.Json; +using Jiaowu.Api.Controllers; +using Jiaowu.Api.Domain.Academic; +using Jiaowu.Api.Domain.Identity; +using Jiaowu.Api.Infrastructure.Persistence; +using Jiaowu.Api.Infrastructure.Timetables; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; + +namespace Jiaowu.Api.Tests; + +public sealed class VenueDisplaysControllerTests +{ + [Fact] + public async Task Classroom_IncludesApprovedReservationForToday() + { + await using var connection = new SqliteConnection("Data Source=:memory:"); + await connection.OpenAsync(); + var db = new AppDbContext(new DbContextOptionsBuilder() + .UseSqlite(connection) + .Options); + await db.Database.EnsureCreatedAsync(); + + var today = DateOnly.FromDateTime(DateTime.Now); + var campus = new Campus { Code = "MAIN", Name = "主校区" }; + var building = new Building { Code = "A", Name = "明德楼", CampusId = campus.Id }; + var classroom = new Classroom { Code = "A101", Name = "A101", BuildingId = building.Id, Capacity = 60 }; + var college = new College { Code = "CS", Name = "计算机学院" }; + var applicant = new ApplicationUser + { + Id = Guid.NewGuid(), UserName = "applicant", NormalizedUserName = "APPLICANT", + DisplayName = "张老师", CollegeId = college.Id + }; + var term = new AcademicTerm + { + Code = "CURRENT", Name = "当前学期", AcademicYear = "2026-2027", Season = TermSeason.Autumn, + StartDate = today.AddDays(-7), EndDate = today.AddDays(120), IsCurrent = true + }; + db.AddRange(campus, building, classroom, college, applicant, term); + db.ClassroomReservations.Add(new ClassroomReservation + { + ApplicantUserId = applicant.Id, ApplicantName = applicant.DisplayName, + ApplicantCollegeId = college.Id, AcademicTermId = term.Id, ClassroomId = classroom.Id, + ReservationDate = today, StartPeriod = 3, PeriodCount = 2, AttendeeCount = 30, + Purpose = "学院学术讲座", ContactPhone = "13800000000", + Status = ClassroomReservationStatus.Approved + }); + await db.SaveChangesAsync(); + + var controller = new VenueDisplaysController( + db, + new TimetableDataService(db), + new ClassroomReservationAvailabilityService(db)); + + var result = await controller.Classroom(classroom.Id, CancellationToken.None); + + var response = Assert.IsType(result).Value; + using var document = JsonDocument.Parse(JsonSerializer.Serialize(response)); + var entry = Assert.Single(document.RootElement.GetProperty("Entries").EnumerateArray()); + Assert.Equal("教室借用", entry.GetProperty("CourseName").GetString()); + Assert.Equal("学院学术讲座", entry.GetProperty("ClassNames")[0].GetString()); + Assert.Equal("张老师", entry.GetProperty("TeacherNames")[0].GetString()); + Assert.True(entry.GetProperty("IsReservation").GetBoolean()); + } +} diff --git a/web/src/views/VenueDisplayView.vue b/web/src/views/VenueDisplayView.vue index 08db9cf..f3ab4d7 100644 --- a/web/src/views/VenueDisplayView.vue +++ b/web/src/views/VenueDisplayView.vue @@ -36,7 +36,7 @@ onBeforeUnmount(() => { window.clearInterval(timer); window.clearInterval(pageTi
明序大学 · 智慧教学空间

{{ title?.name ?? '正在加载' }}

{{ title?.campusName }} · {{ title?.buildingName ?? title?.code }} · {{ data?.term ?? '当前教学日' }}

-
第 {{ entry.startPeriod }}-{{ entry.startPeriod + entry.periodCount - 1 }} 节

{{ entry.courseName }}

{{ entry.classNames?.join('、') }} · {{ entry.teacherNames?.join('、') }}

+
第 {{ entry.startPeriod }}-{{ entry.startPeriod + entry.periodCount - 1 }} 节 已借用

{{ entry.courseName }}

{{ entry.classNames?.join('、') }} · {{ entry.teacherNames?.join('、') }}

今日暂无排课
此教室可供安排使用
{{ slot.periodNumber }}{{ slot.startsAt?.slice(0,5) }}
{{ room.name }}{{ room.freePeriods?.includes(slot.periodNumber) ? '空闲' : '占用' }}
@@ -45,6 +45,6 @@ onBeforeUnmount(() => { window.clearInterval(timer); window.clearInterval(pageTi