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:
Losita
2026-05-06 12:59:29 +08:00
parent d4afc6ef74
commit 7d324b77aa
27 changed files with 1329 additions and 135 deletions

View File

@@ -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{