Version: 0.9.36.dev.260423

前端:
1. 精排弹窗修复——嵌入课程的任务正确传递宿主 event_id + 被嵌入课程不再单独渲染
- components/assistant/ScheduleFineTuneModal.vue:新增 buildCoursePositionIndex / resolveEmbedCourseEventId 辅助函数,按位置匹配 suggested task 与宿主课程
- buildPlacedItems / handleOfficialSave:embed_course_event_id 优先取后端预览值,兜底走位置索引查找,解决批量应用接口因缺宿主 ID 报日程冲突
- currentWeekEntries:被嵌入的 existing course 过滤不渲染,避免与任务卡片同位重叠导致课程视觉被吞

后端:
2. 预览转换补充嵌入关系桥接(防御性)
- newAgent/conv/schedule_preview.go:ScheduleStateToPreview 中 suggested 任务 EmbedHost 非空时,解析宿主课程 SourceID
  写入 entry.EventID,供前端作为 embed_course_event_id 透传
This commit is contained in:
LoveLosita
2026-04-23 10:51:11 +08:00
parent ad463eb6a1
commit 3c2f3c0b71
2 changed files with 53 additions and 4 deletions

View File

@@ -72,6 +72,13 @@ func ScheduleStateToPreview(
} else {
entry.TaskItemID = t.SourceID
entry.TaskClassID = t.TaskClassID
// 嵌入任务:将宿主课程的 source_id即 event_id桥接到 EventID
// 供前端作为 embed_course_event_id 传递给 BatchApplyPlans 做冲突豁免。
if t.EmbedHost != nil {
if host := state.TaskByStateID(*t.EmbedHost); host != nil {
entry.EventID = host.SourceID
}
}
}
// 嵌入与阻塞语义。

View File

@@ -55,8 +55,30 @@ function nextWeek() {
if (currentWeek.value < weekRange.value.max) currentWeek.value++
}
// 构建现有可嵌入课程的位置索引key 为 "week-day-sectionFrom-sectionTo"
function buildCoursePositionIndex(items: HybridScheduleEntry[]): Map<string, HybridScheduleEntry> {
const index = new Map<string, HybridScheduleEntry>()
for (const e of items) {
if (e.type === 'course' && e.status === 'existing' && e.can_be_embedded) {
index.set(`${e.week}-${e.day_of_week}-${e.section_from}-${e.section_to}`, e)
}
}
return index
}
// 查找 suggested task 同位置的宿主课程 event_id
function resolveEmbedCourseEventId(
task: HybridScheduleEntry,
courseIndex: Map<string, HybridScheduleEntry>,
): number | undefined {
if (task.event_id) return task.event_id
const host = courseIndex.get(`${task.week}-${task.day_of_week}-${task.section_from}-${task.section_to}`)
return host ? host.event_id : undefined
}
// 转换当前状态为后端要求的 PlacedItem 数组
function buildPlacedItems(): PlacedItem[] {
const courseIndex = buildCoursePositionIndex(suggestedItems.value)
return suggestedItems.value
.filter(e => e.type === 'task' && e.status === 'suggested')
.map(e => ({
@@ -65,7 +87,7 @@ function buildPlacedItems(): PlacedItem[] {
day_of_week: e.day_of_week,
start_section: e.section_from,
end_section: e.section_to,
embed_course_event_id: e.event_id || undefined,
embed_course_event_id: resolveEmbedCourseEventId(e, courseIndex),
}))
}
@@ -108,8 +130,8 @@ async function handleOfficialSave() {
isSaving.value = true
try {
const items = buildPlacedItems()
// 按 task_class_id 分组
const courseIndex = buildCoursePositionIndex(suggestedItems.value)
const groups = new Map<number, PlacedItem[]>()
suggestedItems.value.forEach(e => {
if (e.type === 'task' && e.status === 'suggested' && e.task_class_id) {
@@ -120,7 +142,7 @@ async function handleOfficialSave() {
day_of_week: e.day_of_week,
start_section: e.section_from,
end_section: e.section_to,
embed_course_event_id: e.event_id || undefined,
embed_course_event_id: resolveEmbedCourseEventId(e, courseIndex),
})
}
})
@@ -213,8 +235,28 @@ function getItemStyle(item: HybridScheduleEntry) {
}
}
// 被嵌入课程的位置集合:当 suggested task 与 existing course 同位置时,课程不单独渲染
const embeddedCoursePositions = computed(() => {
const positions = new Set<string>()
const courseIndex = buildCoursePositionIndex(suggestedItems.value)
for (const e of suggestedItems.value) {
if (e.type === 'task' && e.status === 'suggested') {
const key = `${e.week}-${e.day_of_week}-${e.section_from}-${e.section_to}`
if (courseIndex.has(key)) positions.add(key)
}
}
return positions
})
const currentWeekEntries = computed(() =>
suggestedItems.value.filter(e => e.week === currentWeek.value)
suggestedItems.value.filter(e => {
if (e.week !== currentWeek.value) return false
if (e.type === 'course' && e.status === 'existing') {
const key = `${e.week}-${e.day_of_week}-${e.section_from}-${e.section_to}`
if (embeddedCoursePositions.value.has(key)) return false
}
return true
})
)
</script>