diff --git a/backend/api/schedule.go b/backend/api/schedule.go index 0666a40..0907ee3 100644 --- a/backend/api/schedule.go +++ b/backend/api/schedule.go @@ -79,3 +79,30 @@ func (s *ScheduleAPI) DeleteScheduleEvent(c *gin.Context) { //4.返回删除成功的响应给前端 c.JSON(http.StatusOK, respond.Ok) } + +func (s *ScheduleAPI) GetUserRecentCompletedSchedules(c *gin.Context) { + // 1. 从请求上下文中获取用户ID以及其他查询参数(如 index 和 limit) + userID := c.GetInt("user_id") + index := c.Query("index") + limit := c.Query("limit") + intIndex, err := strconv.Atoi(index) + if err != nil { + c.JSON(http.StatusBadRequest, respond.WrongParamType) + return + } + intLimit, err := strconv.Atoi(limit) + if err != nil { + c.JSON(http.StatusBadRequest, respond.WrongParamType) + return + } + //2.调用服务层方法获取用户最近完成的日程事件 + ctx, cancel := context.WithTimeout(c.Request.Context(), 1*time.Second) + defer cancel() // 记得释放资源 + completedSchedules, err := s.scheduleService.GetUserRecentCompletedSchedules(ctx, userID, intIndex, intLimit) + if err != nil { + respond.DealWithError(c, err) + return + } + //3.返回数据给前端 + c.JSON(http.StatusOK, respond.RespWithData(respond.Ok, completedSchedules)) +} diff --git a/backend/conv/schedule.go b/backend/conv/schedule.go index 518aa85..f9a4a5d 100644 --- a/backend/conv/schedule.go +++ b/backend/conv/schedule.go @@ -71,87 +71,6 @@ var sectionTimeMap = map[int][2]string{ 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. 按周次和星期进行第一层分组 (map[week][day]schedules) - // 虽然是“今日日程”,但传入的切片可能包含多天,这样写兼容性更好 - 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{}, - } - - // 2. 在每一天内部,按 EventID 进行第二层聚合(把连续的节次拼成一个 Event) - eventGroups := make(map[int][]model.Schedule) - for _, s := range daySchedules { - eventGroups[s.EventID] = append(eventGroups[s.EventID], s) - } - - // 3. 遍历每个 Event 组,转化为 EventBrief - var tempEvents []model.EventBrief - for eventID, slots := range eventGroups { - // 对当前 Event 的所有原子槽位按节次排序 - sort.Slice(slots, func(i, j int) bool { - return slots[i].Section < slots[j].Section - }) - - firstSlot := slots[0] - lastSlot := slots[len(slots)-1] - - brief := model.EventBrief{ - ID: eventID, - Name: firstSlot.Event.Name, - Location: *firstSlot.Event.Location, - Type: firstSlot.Event.Type, - StartTime: sectionTimeMap[firstSlot.Section][0], // 取第一节的开始时间 - EndTime: sectionTimeMap[lastSlot.Section][1], // 取最后一节的结束时间 - } - - // 💡 4. 检查是否有嵌入任务 (Embedded Task) - // 逻辑:只要这个时间块中任何一个原子槽位带了 EmbeddedTaskID,就提取它 - // 注意:这里假设你在 DAO 层也 Preload 了 Task 信息,或者在此处仅填 ID - for _, slot := range slots { - if slot.EmbeddedTaskID != nil { - brief.EmbeddedTaskInfo = model.TaskBrief{ - ID: *slot.EmbeddedTaskID, - Name: *slot.EmbeddedTask.Content, - Type: "task", - } - break // 找到一个即代表该块已占用 - } - } - - tempEvents = append(tempEvents, brief) - } - - // 5. 对结果进行排序(按开始时间),并分配 Order - sort.Slice(tempEvents, func(i, j int) bool { - return tempEvents[i].StartTime < tempEvents[j].StartTime - }) - - for i := range tempEvents { - tempEvents[i].Order = i + 1 - } - - todayDTO.Events = tempEvents - result = append(result, todayDTO) - } - - return result -}*/ - func SchedulesToUserTodaySchedule(schedules []model.Schedule) []model.UserTodaySchedule { if len(schedules) == 0 { return []model.UserTodaySchedule{} @@ -342,3 +261,20 @@ func SchedulesToUserWeeklySchedule(schedules []model.Schedule) []model.UserWeekS } return result } + +func SchedulesToRecentCompletedSchedules(schedules []model.Schedule) model.UserRecentCompletedScheduleResponse { + var result model.UserRecentCompletedScheduleResponse + for _, s := range schedules { + strTime := s.Event.EndTime.Format("2006-01-02 15:04:05") + temp := model.RecentCompletedEventBrief{ + ID: s.ID, + Name: s.Event.Name, + Type: s.Event.Type, + // CompletedTime 需要从 ScheduleEvent 的 CompletedTime 字段获取 + CompletedTime: strTime, + } + result.Events = append(result.Events, temp) + } + return result + +} diff --git a/backend/dao/schedule.go b/backend/dao/schedule.go index da2204c..18928fc 100644 --- a/backend/dao/schedule.go +++ b/backend/dao/schedule.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "time" "github.com/LoveLosita/smartflow/backend/model" "github.com/LoveLosita/smartflow/backend/respond" @@ -444,3 +445,21 @@ func (d *ScheduleDAO) DeleteScheduleEventByTaskItemID(ctx context.Context, taskI } return nil } + +func (d *ScheduleDAO) GetUserRecentCompletedSchedules(ctx context.Context, nowTime time.Time, userID int, index, limit int) ([]model.Schedule, error) { + var schedules []model.Schedule + err := d.db.WithContext(ctx). + Preload("Event"). + Preload("EmbeddedTask"). + Joins("JOIN schedule_events ON schedule_events.id = schedules.event_id"). + Where("schedules.user_id = ? AND schedule_events.type = ? AND schedule_events.end_time < ?", + userID, "task", nowTime). + Order("schedule_events.end_time DESC"). // 命中索引 + Offset(index). + Limit(limit). + Find(&schedules).Error + if err != nil { + return nil, err + } + return schedules, nil +} diff --git a/backend/model/schedule.go b/backend/model/schedule.go index 618fcda..c18c3cc 100644 --- a/backend/model/schedule.go +++ b/backend/model/schedule.go @@ -1,13 +1,17 @@ package model +import "time" + type ScheduleEvent struct { - ID int `gorm:"primaryKey;autoIncrement" json:"id"` - UserID int `gorm:"column:user_id;index:idx_user_events;not null" json:"user_id"` - Name string `gorm:"column:name;type:varchar(255);not null;comment:课程或任务名称" json:"name"` - Location *string `gorm:"column:location;type:varchar(255);default:'';comment:地点 (教学楼/会议室)" json:"location"` - Type string `gorm:"column:type;type:enum('course','task');not null;comment:日程类型" json:"type"` - RelID *int `gorm:"column:rel_id;comment:关联原始数据ID (如教务系统的课程ID)" json:"rel_id"` - CanBeEmbedded bool `gorm:"column:can_be_embedded;not null;default:0;comment:是否允许在此时段嵌入其他任务" json:"can_be_embedded"` + ID int `gorm:"primaryKey;autoIncrement" json:"id"` + UserID int `gorm:"column:user_id;index:idx_user_events;not null" json:"user_id"` + Name string `gorm:"column:name;type:varchar(255);not null;comment:课程或任务名称" json:"name"` + Location *string `gorm:"column:location;type:varchar(255);default:'';comment:地点 (教学楼/会议室)" json:"location"` + Type string `gorm:"column:type;type:enum('course','task');not null;comment:日程类型" json:"type"` + RelID *int `gorm:"column:rel_id;comment:关联原始数据ID (如教务系统的课程ID)" json:"rel_id"` + CanBeEmbedded bool `gorm:"column:can_be_embedded;not null;default:0;comment:是否允许在此时段嵌入其他任务" json:"can_be_embedded"` + StartTime time.Time `gorm:"column:start_time;type:time;comment:开始时间" json:"start_time"` + EndTime time.Time `gorm:"column:end_time;type:time;comment:结束时间" json:"end_time"` } type Schedule struct { @@ -92,6 +96,17 @@ type UserDeleteScheduleEvent struct { DeleteEmbeddedTask bool `json:"delete_embedded_task"` } +type UserRecentCompletedScheduleResponse struct { + Events []RecentCompletedEventBrief `json:"events"` +} + +type RecentCompletedEventBrief struct { + ID int `json:"id"` //如果是嵌入的任务事件,这个ID是TaskClassItem的ID;如果是课程事件,这个ID是ScheduleEvent的ID + Name string `json:"name"` + Type string `json:"type"` + CompletedTime string `json:"completed_time"` +} + func (ScheduleEvent) TableName() string { return "schedule_events" } func (Schedule) TableName() string { return "schedules" } diff --git a/backend/routers/routers.go b/backend/routers/routers.go index 65eda15..a181f49 100644 --- a/backend/routers/routers.go +++ b/backend/routers/routers.go @@ -78,6 +78,7 @@ func RegisterRouters(handlers *api.ApiHandlers, cache *dao.CacheDAO, limiter *pk scheduleGroup.GET("/today", handlers.ScheduleHandler.GetUserTodaySchedule) scheduleGroup.GET("/week", handlers.ScheduleHandler.GetUserWeeklySchedule) scheduleGroup.DELETE("/delete", middleware.IdempotencyMiddleware(cache), handlers.ScheduleHandler.DeleteScheduleEvent) + scheduleGroup.GET("/recent-completed", middleware.IdempotencyMiddleware(cache), handlers.ScheduleHandler.GetUserRecentCompletedSchedules) } } // 初始化Gin引擎 diff --git a/backend/service/schedule.go b/backend/service/schedule.go index 1657fd9..8c3d12c 100644 --- a/backend/service/schedule.go +++ b/backend/service/schedule.go @@ -236,3 +236,17 @@ func (ss *ScheduleService) DeleteScheduleEvent(ctx context.Context, requests []m } return nil } + +func (ss *ScheduleService) GetUserRecentCompletedSchedules(ctx context.Context, userID, index, limit int) (model.UserRecentCompletedScheduleResponse, error) { + //1.查询用户最近完成的日程安排 + //获取现在的时间 + /*nowTime := time.Now()*/ + nowTime := time.Date(2026, 6, 15, 12, 0, 0, 0, time.Local) //测试数据 + schedules, err := ss.scheduleDAO.GetUserRecentCompletedSchedules(ctx, nowTime, userID, index, limit) + if err != nil { + return model.UserRecentCompletedScheduleResponse{}, err + } + //2.转换为前端需要的格式 + result := conv.SchedulesToRecentCompletedSchedules(schedules) + return result, nil +}