Version: 0.9.79.dev.260506
后端: 1. 本地后端启动体系收口到 `backend/scripts`,移除 `cmd/all` 聚合入口,并将仓库根兼容启动语义收敛为 `StartAPI` 别名;新增 dev-up / dev-down / services-up / services-down / dev-status / dev-logs / service-restart 脚本,统一托管多服务进程、日志、PID 与基础设施启动。 2. 课表服务超时口径统一放宽到 5 分钟,覆盖 gateway / client / rpc server / config example,避免课表导入与图片识别在长耗时场景下被内层提前截断。 3. `today` 课表查询修正为读取真实当前日期,不再使用硬编码测试日期;同时剔除旧缓存与返回结果里的 `empty` 占位事件,后端只返回真实日程,空档改由前端时间轴自行补齐。 前端: 4. 首页路由切回改为复用 `DashboardView` 实例,补 `keep-alive`、`onActivated` 与双帧缩放重算,修复从侧栏返回首页时首帧布局放大与重复加载闪动问题。 5. 首页加载态与今日时间线口径收口:移除额外 800ms `pageLoading` 人为延迟,task / schedule 改为分开驱动;时间线忽略 `empty` 事件,并统一空档文案为“无课”。 6. 收敛助手页与首页若干进场/弹性动画,降低结果卡片、微调弹窗、思考区与面板切换时的抖动感。 仓库: 7. README 补充后端本地快速启动说明,`.gitignore` 忽略 `backend/.dev` 脚本运行态产物。
This commit is contained in:
@@ -13,8 +13,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
defaultListenOn = "0.0.0.0:9087"
|
||||
defaultTimeout = 10 * time.Second
|
||||
defaultListenOn = "0.0.0.0:9087"
|
||||
// 课表导入与图片识别都可能持续较久,服务端默认超时统一放宽到 5 分钟,避免 zrpc 提前取消上下文。
|
||||
defaultTimeout = 5 * time.Minute
|
||||
defaultMaxRPCMessageSize = 8 * 1024 * 1024
|
||||
rpcMessageSizePadding = 1024 * 1024
|
||||
)
|
||||
|
||||
@@ -163,6 +163,97 @@ func SchedulesToUserTodaySchedule(schedules []model.Schedule) []model.UserTodayS
|
||||
return result
|
||||
}
|
||||
|
||||
// SchedulesToExistingUserTodaySchedule 只返回真实存在的日程事件,不再补“无课/empty”占位。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 负责把 DAO 返回的当天原子节次合并成前端展示事件。
|
||||
// 2. 负责保留真实课程/任务与嵌入任务信息。
|
||||
// 3. 不负责生成空档占位,空档展示交给前端按固定时间轴自行补齐。
|
||||
func SchedulesToExistingUserTodaySchedule(schedules []model.Schedule) []model.UserTodaySchedule {
|
||||
if len(schedules) == 0 {
|
||||
return []model.UserTodaySchedule{}
|
||||
}
|
||||
|
||||
dayGroups := make(map[string][]model.Schedule)
|
||||
dayKeys := make([]string, 0, len(schedules))
|
||||
for _, s := range schedules {
|
||||
dayKey := fmt.Sprintf("%d-%d", s.Week, s.DayOfWeek)
|
||||
if _, ok := dayGroups[dayKey]; !ok {
|
||||
dayKeys = append(dayKeys, dayKey)
|
||||
}
|
||||
dayGroups[dayKey] = append(dayGroups[dayKey], s)
|
||||
}
|
||||
sort.Strings(dayKeys)
|
||||
|
||||
result := make([]model.UserTodaySchedule, 0, len(dayKeys))
|
||||
for _, dayKey := range dayKeys {
|
||||
daySchedules := dayGroups[dayKey]
|
||||
todayDTO := model.UserTodaySchedule{
|
||||
Week: daySchedules[0].Week,
|
||||
DayOfWeek: daySchedules[0].DayOfWeek,
|
||||
Events: []model.EventBrief{},
|
||||
}
|
||||
|
||||
sectionMap := make(map[int]model.Schedule, len(daySchedules))
|
||||
for _, s := range daySchedules {
|
||||
sectionMap[s.Section] = s
|
||||
}
|
||||
|
||||
order := 1
|
||||
for curr := 1; curr <= 12; {
|
||||
slot, ok := sectionMap[curr]
|
||||
if !ok {
|
||||
curr++
|
||||
continue
|
||||
}
|
||||
|
||||
end := curr
|
||||
for next := curr + 1; next <= 12; next++ {
|
||||
nextSlot, exist := sectionMap[next]
|
||||
if !exist || nextSlot.EventID != slot.EventID {
|
||||
break
|
||||
}
|
||||
end = next
|
||||
}
|
||||
|
||||
location := ""
|
||||
if slot.Event.Location != nil {
|
||||
location = *slot.Event.Location
|
||||
}
|
||||
|
||||
brief := model.EventBrief{
|
||||
ID: slot.EventID,
|
||||
Order: order,
|
||||
Name: slot.Event.Name,
|
||||
Location: location,
|
||||
Type: slot.Event.Type,
|
||||
StartTime: sectionTimeMap[curr][0],
|
||||
EndTime: sectionTimeMap[end][1],
|
||||
Span: end - curr + 1,
|
||||
}
|
||||
|
||||
for i := curr; i <= end; i++ {
|
||||
if s, exist := sectionMap[i]; exist && s.EmbeddedTask != nil && s.EmbeddedTask.Content != 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++
|
||||
}
|
||||
|
||||
result = append(result, todayDTO)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func SchedulesToUserWeeklySchedule(schedules []model.Schedule) *model.UserWeekSchedule {
|
||||
if len(schedules) == 0 {
|
||||
return &model.UserWeekSchedule{
|
||||
|
||||
@@ -22,10 +22,13 @@ type ScheduleService struct {
|
||||
scheduleDAO *scheduledao.ScheduleDAO
|
||||
taskClassDAO *rootdao.TaskClassDAO
|
||||
repoManager *rootdao.RepoManager // 统一管理多个 DAO 的事务
|
||||
cacheDAO *rootdao.CacheDAO // 需要在 ScheduleService 中使用缓存
|
||||
cacheDAO *rootdao.CacheDAO // 负责 today/week/ongoing 等课表缓存读写
|
||||
applyAdapter *applyadapter.GormApplyAdapter
|
||||
}
|
||||
|
||||
// scheduleNow 允许测试注入当前时钟,避免 today 接口再次退回到硬编码日期。
|
||||
var scheduleNow = time.Now
|
||||
|
||||
func NewScheduleService(scheduleDAO *scheduledao.ScheduleDAO, taskClassDAO *rootdao.TaskClassDAO, repoManager *rootdao.RepoManager, cacheDAO *rootdao.CacheDAO) *ScheduleService {
|
||||
return &ScheduleService{
|
||||
scheduleDAO: scheduleDAO,
|
||||
@@ -50,6 +53,13 @@ func (ss *ScheduleService) SetApplyAdapter(applyAdapter *applyadapter.GormApplyA
|
||||
func (ss *ScheduleService) GetUserTodaySchedule(ctx context.Context, userID int) ([]model.UserTodaySchedule, error) {
|
||||
//1.先尝试从缓存获取数据
|
||||
cachedResp, err := ss.cacheDAO.GetUserTodayScheduleFromCache(ctx, userID)
|
||||
if err == nil {
|
||||
normalized, changed := filterPlaceholderTodayEvents(cachedResp)
|
||||
if changed {
|
||||
_ = ss.cacheDAO.SetUserTodayScheduleToCache(ctx, userID, normalized)
|
||||
}
|
||||
cachedResp = normalized
|
||||
}
|
||||
if err == nil {
|
||||
// 缓存命中,直接返回
|
||||
return cachedResp, nil
|
||||
@@ -59,19 +69,18 @@ func (ss *ScheduleService) GetUserTodaySchedule(ctx context.Context, userID int)
|
||||
return nil, err
|
||||
}
|
||||
//2.获取当前日期
|
||||
/*curTime := time.Now().Format("2006-01-02")*/
|
||||
curTime := "2026-03-02" //测试数据
|
||||
week, dayOfWeek, err := conv.RealDateToRelativeDate(curTime)
|
||||
week, dayOfWeek, err := currentRelativeWeekAndDay()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//3.查询用户当天的日程安排
|
||||
schedules, err := ss.scheduleDAO.GetUserTodaySchedule(ctx, userID, week, dayOfWeek) //测试数据
|
||||
schedules, err := ss.scheduleDAO.GetUserTodaySchedule(ctx, userID, week, dayOfWeek)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//4.转换为前端需要的格式
|
||||
todaySchedules := conv.SchedulesToUserTodaySchedule(schedules)
|
||||
todaySchedules := conv.SchedulesToExistingUserTodaySchedule(schedules)
|
||||
todaySchedules, _ = filterPlaceholderTodayEvents(todaySchedules)
|
||||
//5.将查询结果存入缓存,设置过期时间为当天结束
|
||||
err = ss.cacheDAO.SetUserTodayScheduleToCache(ctx, userID, todaySchedules)
|
||||
return todaySchedules, nil
|
||||
@@ -114,6 +123,38 @@ func (ss *ScheduleService) GetUserWeeklySchedule(ctx context.Context, userID, we
|
||||
return weeklySchedule, nil
|
||||
}
|
||||
|
||||
// currentRelativeWeekAndDay 只负责把“当前时间”换算成课表内部使用的周次与星期。
|
||||
func currentRelativeWeekAndDay() (int, int, error) {
|
||||
currentDate := scheduleNow().Format(conv.DateFormat)
|
||||
return conv.RealDateToRelativeDate(currentDate)
|
||||
}
|
||||
|
||||
// filterPlaceholderTodayEvents 负责剔除旧缓存里遗留的 empty 占位事件,保证 today 接口只返回真实日程。
|
||||
func filterPlaceholderTodayEvents(schedules []model.UserTodaySchedule) ([]model.UserTodaySchedule, bool) {
|
||||
if len(schedules) == 0 {
|
||||
return []model.UserTodaySchedule{}, false
|
||||
}
|
||||
|
||||
result := make([]model.UserTodaySchedule, 0, len(schedules))
|
||||
changed := false
|
||||
for _, day := range schedules {
|
||||
filteredEvents := make([]model.EventBrief, 0, len(day.Events))
|
||||
for _, event := range day.Events {
|
||||
if strings.EqualFold(strings.TrimSpace(event.Type), "empty") {
|
||||
changed = true
|
||||
continue
|
||||
}
|
||||
filteredEvents = append(filteredEvents, event)
|
||||
}
|
||||
if len(filteredEvents) != len(day.Events) {
|
||||
changed = true
|
||||
}
|
||||
day.Events = filteredEvents
|
||||
result = append(result, day)
|
||||
}
|
||||
return result, changed
|
||||
}
|
||||
|
||||
func (ss *ScheduleService) DeleteScheduleEvent(ctx context.Context, requests []model.UserDeleteScheduleEvent, userID int) error {
|
||||
err := ss.repoManager.Transaction(ctx, func(txM *rootdao.RepoManager) error {
|
||||
for _, req := range requests {
|
||||
|
||||
Reference in New Issue
Block a user