package conv import ( "fmt" "github.com/LoveLosita/smartflow/backend/model" ) import "sort" func SchedulesToScheduleConflictDetail(schedules []model.Schedule) []model.ScheduleConflictDetail { if len(schedules) == 0 { return []model.ScheduleConflictDetail{} } // 1. 使用 Map 进行逻辑分组 // Key 格式: EventID-Week-Day (防止同一事件在不同天出现时被混为一谈) groups := make(map[string]*model.ScheduleConflictDetail) for _, s := range schedules { key := fmt.Sprintf("%d-%d-%d", s.EventID, s.Week, s.DayOfWeek) if _, ok := groups[key]; !ok { // 初始化该分组 groups[key] = &model.ScheduleConflictDetail{ EventID: s.EventID, Name: s.Event.Name, Location: *s.Event.Location, // 假设字段是 *string Type: s.Event.Type, Week: s.Week, DayOfWeek: s.DayOfWeek, } } // 将当前节次加入数组 groups[key].Sections = append(groups[key].Sections, s.Section) } // 2. 处理每个分组的区间逻辑 res := make([]model.ScheduleConflictDetail, 0, len(groups)) for _, detail := range groups { // 排序节次,例如把 [3, 1, 2] 变成 [1, 2, 3] sort.Ints(detail.Sections) // 最小值即起始,最大值即结束 detail.StartSection = detail.Sections[0] detail.EndSection = detail.Sections[len(detail.Sections)-1] res = append(res, *detail) } // 3. 可选:对结果集按时间排序,让前端收到的 DTO 也是有序的 sort.Slice(res, func(i, j int) bool { if res[i].Week != res[j].Week { return res[i].Week < res[j].Week } if res[i].DayOfWeek != res[j].DayOfWeek { return res[i].DayOfWeek < res[j].DayOfWeek } return res[i].StartSection < res[j].StartSection }) return res } // SectionToTime 映射表:将原子节次转为起始/结束时间点 // 此处以重邮为例 var sectionTimeMap = map[int][2]string{ 1: {"08:00", "08:45"}, 2: {"08:55", "09:40"}, 3: {"10:15", "11:00"}, 4: {"11:10", "11:55"}, 5: {"14:00", "14:45"}, 6: {"14:55", "15:40"}, 7: {"16:15", "17:00"}, 8: {"17:10", "17:55"}, 9: {"19:00", "19:45"}, 10: {"19:55", "20:40"}, 11: {"20:50", "21:35"}, 12: {"21:45", "22:30"}, } func SchedulesToUserTodaySchedule(schedules []model.Schedule) []model.UserTodaySchedule { if len(schedules) == 0 { return []model.UserTodaySchedule{} } // 1. 数据预处理:按 Week-Day 分组 dayGroups := make(map[string][]model.Schedule) for _, s := range schedules { dayKey := fmt.Sprintf("%d-%d", s.Week, s.DayOfWeek) dayGroups[dayKey] = append(dayGroups[dayKey], s) } var result []model.UserTodaySchedule for _, daySchedules := range dayGroups { todayDTO := model.UserTodaySchedule{ Week: daySchedules[0].Week, DayOfWeek: daySchedules[0].DayOfWeek, Events: []model.EventBrief{}, } // 💡 关键点:建立一个 Section 查找表,方便 O(1) 确定某节课是什么 sectionMap := make(map[int]model.Schedule) for _, s := range daySchedules { sectionMap[s.Section] = s } order := 1 // 💡 线性扫描:从第 1 节巡检到第 12 节 for curr := 1; curr <= 12; { if slot, ok := sectionMap[curr]; ok { // === A 场景:当前节次有课 === // 1. 寻找该事件的连续范围(比如 9-12 节连上) // 我们向后探测,直到 EventID 变化或节次断开 end := curr for next := curr + 1; next <= 12; next++ { if nextSlot, exist := sectionMap[next]; exist && nextSlot.EventID == slot.EventID { end = next } else { break } } // 2. 封装 EventBrief brief := model.EventBrief{ ID: slot.EventID, Order: order, Name: slot.Event.Name, Location: *slot.Event.Location, Type: slot.Event.Type, StartTime: sectionTimeMap[curr][0], EndTime: sectionTimeMap[end][1], Span: end - curr + 1, } // 3. 处理嵌入任务 // 只要这几个连续节次里有一个有任务,就带上 for i := curr; i <= end; i++ { if s, exist := sectionMap[i]; exist && s.EmbeddedTask != nil { brief.EmbeddedTaskInfo = model.TaskBrief{ ID: s.EmbeddedTask.ID, Name: *s.EmbeddedTask.Content, Type: "task", } break } } todayDTO.Events = append(todayDTO.Events, brief) // 💡 指针跳跃:直接跳过已处理的节次 curr = end + 1 order++ } else { // === B 场景:当前节次没课(Type = "empty") === // 逻辑:按照学校标准大节(1-2, 3-4...)进行空位合并 // 如果当前是奇数节(1, 3, 5...)且下一节也没课,就合并成一个空块 emptyEnd := curr if curr%2 != 0 && curr < 12 { if _, nextHasClass := sectionMap[curr+1]; !nextHasClass { emptyEnd = curr + 1 } } todayDTO.Events = append(todayDTO.Events, model.EventBrief{ ID: 0, // 空课 ID 为 0 Order: order, Name: "无课", Type: "empty", StartTime: sectionTimeMap[curr][0], EndTime: sectionTimeMap[emptyEnd][1], Location: "休息时间", }) curr = emptyEnd + 1 order++ } } result = append(result, todayDTO) } return result } func SchedulesToUserWeeklySchedule(schedules []model.Schedule) *model.UserWeekSchedule { if len(schedules) == 0 { return &model.UserWeekSchedule{ Week: 0, Events: []model.WeeklyEventBrief{}, } } // 1. 初始化返回结构 (默认取第一条数据的周次) weekDTO := &model.UserWeekSchedule{ Week: schedules[0].Week, Events: []model.WeeklyEventBrief{}, } // 2. 建立 [天][节次] 的快速索引地图 // indexMap[day][section] -> model.Schedule indexMap := make(map[int]map[int]model.Schedule) for d := 1; d <= 7; d++ { indexMap[d] = make(map[int]model.Schedule) } for _, s := range schedules { indexMap[s.DayOfWeek][s.Section] = s } // 3. 线性扫描 1-7 天 for day := 1; day <= 7; day++ { order := 1 // 每一天开始时,内部显示顺序重置 // 4. 线性扫描 1-12 节 for curr := 1; curr <= 12; { // 场景 A:当前槽位有课/有任务 if slot, hasClass := indexMap[day][curr]; hasClass { end := curr // 探测逻辑:合并相同 EventID 的连续节次 (Span 计算) for next := curr + 1; next <= 12; next++ { if nextSlot, exist := indexMap[day][next]; exist && nextSlot.EventID == slot.EventID { end = next } else { break } } span := end - curr + 1 brief := model.WeeklyEventBrief{ ID: slot.EventID, Order: order, DayOfWeek: day, Name: slot.Event.Name, Location: *slot.Event.Location, Type: slot.Event.Type, StartTime: sectionTimeMap[curr][0], // 使用你定义的映射表 EndTime: sectionTimeMap[end][1], Span: span, } // 提取嵌入任务信息 (逻辑同前,探测整个 Span) for i := curr; i <= end; i++ { if s, exist := indexMap[day][i]; exist && s.EmbeddedTask != nil { brief.EmbeddedTaskInfo = model.TaskBrief{ ID: s.EmbeddedTask.ID, Name: *s.EmbeddedTask.Content, Type: "task", } break } } weekDTO.Events = append(weekDTO.Events, brief) curr = end + 1 // 指针跳跃到下一块 order++ } else { // 场景 B:无课 (Type="empty"),进行逻辑合并 emptyEnd := curr // 奇数节起步且下一节也空,则合并为大节 (1-2, 3-4...) if curr%2 != 0 && curr < 12 { if _, nextHasClass := indexMap[day][curr+1]; !nextHasClass { emptyEnd = curr + 1 } } weekDTO.Events = append(weekDTO.Events, model.WeeklyEventBrief{ ID: 0, Order: order, DayOfWeek: day, Name: "无课", Type: "empty", StartTime: sectionTimeMap[curr][0], EndTime: sectionTimeMap[emptyEnd][1], Span: emptyEnd - curr + 1, Location: "", }) curr = emptyEnd + 1 order++ } } } return weekDTO } func SchedulesToRecentCompletedSchedules(schedules []model.Schedule) *model.UserRecentCompletedScheduleResponse { // 1. 初始化结果集,确保即使为空也返回空数组而非 nil result := &model.UserRecentCompletedScheduleResponse{ Events: make([]model.RecentCompletedEventBrief, 0), } if len(schedules) == 0 { return result } // 💡 核心:去重地图,key 是 EventID seen := make(map[int]bool) for _, s := range schedules { // 2. 检查这个逻辑事件(课程或任务块)是否已经处理过 if seen[s.EventID] { continue } // 3. 确定显示的“名分”和“类型” displayName := s.Event.Name displayType := s.Event.Type // 🚀 关键逻辑:如果存在嵌入任务,则“鸠占鹊巢” // 即使载体是 course,只要里面塞了任务,我们就对外宣称这是一个 task if s.EmbeddedTask != nil && s.EmbeddedTask.Content != nil { displayName = *s.EmbeddedTask.Content displayType = "embedded_task" } // 4. 格式化结束时间 (即完成时间) strTime := s.Event.EndTime.Format("2006-01-02 15:04:05") // 5. 构造 Brief temp := model.RecentCompletedEventBrief{ // ID 统一使用 EventID,确保唯一性且方便前端追踪逻辑块 ID: s.EventID, Name: displayName, Type: displayType, CompletedTime: strTime, } result.Events = append(result.Events, temp) // 6. 标记该事件已处理 seen[s.EventID] = true } return result } func SchedulesToUserOngoingSchedule(schedules []model.Schedule) *model.OngoingSchedule { if len(schedules) == 0 { return nil } //取第一个 Schedule 的 Event 作为正在进行的事件 ongoing := schedules[0] return &model.OngoingSchedule{ ID: ongoing.EventID, Name: ongoing.Event.Name, Type: ongoing.Event.Type, Location: *ongoing.Event.Location, StartTime: ongoing.Event.StartTime, EndTime: ongoing.Event.EndTime, } } // 这里我们使用一个临时的内部结构来兼容“实日程”和“虚计划” type slotInfo struct { schedule *model.Schedule plan *model.TaskClassItem } func PlanningResultToUserWeekSchedules(userSchedule []model.Schedule, plans []model.TaskClassItem) []model.UserWeekSchedule { // 1. 周次范围探测与数据分桶 (保持高效的 O(N) 复杂度) minW, maxW := 25, 1 weekMap := make(map[int][]model.Schedule) for _, s := range userSchedule { if s.Week < minW { minW = s.Week } if s.Week > maxW { maxW = s.Week } weekMap[s.Week] = append(weekMap[s.Week], s) } planMap := make(map[int][]model.TaskClassItem) for _, p := range plans { if p.EmbeddedTime == nil { continue } w := p.EmbeddedTime.Week if w < minW { minW = w } if w > maxW { maxW = w } planMap[w] = append(planMap[w], p) } var results []model.UserWeekSchedule for w := minW; w <= maxW; w++ { // 构建当前周的逻辑网格 indexMap := make(map[int]map[int]slotInfo) for d := 1; d <= 7; d++ { indexMap[d] = make(map[int]slotInfo) } for _, s := range weekMap[w] { indexMap[s.DayOfWeek][s.Section] = slotInfo{schedule: &s} } for _, p := range planMap[w] { for sec := p.EmbeddedTime.SectionFrom; sec <= p.EmbeddedTime.SectionTo; sec++ { info := indexMap[p.EmbeddedTime.DayOfWeek][sec] info.plan = &p indexMap[p.EmbeddedTime.DayOfWeek][sec] = info } } weekDTO := &model.UserWeekSchedule{Week: w, Events: []model.WeeklyEventBrief{}} for day := 1; day <= 7; day++ { order := 1 for curr := 1; curr <= 12; { slot := indexMap[day][curr] if slot.schedule != nil || slot.plan != nil { end := curr // 🚀 修复逻辑 A:精准探测合并边界 for next := curr + 1; next <= 12; next++ { nextSlot := indexMap[day][next] isSame := false if slot.schedule != nil && nextSlot.schedule != nil { // 场景:都是课,且是同一门课 isSame = slot.schedule.EventID == nextSlot.schedule.EventID } else if slot.schedule == nil && nextSlot.schedule == nil && slot.plan != nil && nextSlot.plan != nil { // 场景:都是新排任务,且是同一个 TaskItem (修复了之前会合并不同任务的 Bug) isSame = slot.plan.ID == nextSlot.plan.ID } if isSame { end = next } else { break } } // 🚀 修复逻辑 B:直接计算 span 并传值,消除重复计算 span := end - curr + 1 brief := buildBrief(slot, day, curr, end, span, order) weekDTO.Events = append(weekDTO.Events, brief) curr = end + 1 order++ } else { // 场景 B:留空处理 (逻辑保持原子化) emptyEnd := curr if curr%2 != 0 && curr < 12 { if next := indexMap[day][curr+1]; next.schedule == nil && next.plan == nil { emptyEnd = curr + 1 } } weekDTO.Events = append(weekDTO.Events, model.WeeklyEventBrief{ Name: "无课", Type: "empty", DayOfWeek: day, Order: order, StartTime: sectionTimeMap[curr][0], EndTime: sectionTimeMap[emptyEnd][1], Span: emptyEnd - curr + 1, }) curr = emptyEnd + 1 order++ } } } results = append(results, *weekDTO) } return results } func buildBrief(slot slotInfo, day, start, end, span, order int) model.WeeklyEventBrief { brief := model.WeeklyEventBrief{ DayOfWeek: day, Order: order, StartTime: sectionTimeMap[start][0], EndTime: sectionTimeMap[end][1], Span: span, Status: "normal", // 默认设为正常状态 } if slot.schedule != nil { // 场景 A:它是数据库里原有的课 (实日程) brief.ID = slot.schedule.EventID brief.Name = slot.schedule.Event.Name brief.Location = *slot.schedule.Event.Location brief.Type = slot.schedule.Event.Type // 如果这节课里被算法“塞”进了一个计划任务 if slot.plan != nil { brief.Status = "suggested" // 标记为建议状态,前端据此高亮整块 brief.EmbeddedTaskInfo = model.TaskBrief{ ID: slot.plan.ID, Name: *slot.plan.Content, Type: "task", } } } else if slot.plan != nil { // 场景 B:它是算法在空地新建的任务块 (虚日程) brief.Name = *slot.plan.Content brief.Type = "task" brief.Status = "suggested" // 标记为建议状态 brief.ID = slot.plan.ID // 虚日程的 ID 直接使用 TaskClassItem 的 ID,方便前端追踪和操作 } return brief }