From 3c2f3c0b71a6074f4b240974e9524541491a305e Mon Sep 17 00:00:00 2001 From: LoveLosita <2810873701@qq.com> Date: Thu, 23 Apr 2026 10:51:11 +0800 Subject: [PATCH] =?UTF-8?q?Version:=200.9.36.dev.260423=20=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=EF=BC=9A=201.=20=E7=B2=BE=E6=8E=92=E5=BC=B9=E7=AA=97?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E2=80=94=E2=80=94=E5=B5=8C=E5=85=A5=E8=AF=BE?= =?UTF-8?q?=E7=A8=8B=E7=9A=84=E4=BB=BB=E5=8A=A1=E6=AD=A3=E7=A1=AE=E4=BC=A0?= =?UTF-8?q?=E9=80=92=E5=AE=BF=E4=B8=BB=20event=5Fid=20+=20=E8=A2=AB?= =?UTF-8?q?=E5=B5=8C=E5=85=A5=E8=AF=BE=E7=A8=8B=E4=B8=8D=E5=86=8D=E5=8D=95?= =?UTF-8?q?=E7=8B=AC=E6=B8=B2=E6=9F=93=20-=20components/assistant/Schedule?= =?UTF-8?q?FineTuneModal.vue=EF=BC=9A=E6=96=B0=E5=A2=9E=20buildCoursePosit?= =?UTF-8?q?ionIndex=20/=20resolveEmbedCourseEventId=20=E8=BE=85=E5=8A=A9?= =?UTF-8?q?=E5=87=BD=E6=95=B0=EF=BC=8C=E6=8C=89=E4=BD=8D=E7=BD=AE=E5=8C=B9?= =?UTF-8?q?=E9=85=8D=20suggested=20task=20=E4=B8=8E=E5=AE=BF=E4=B8=BB?= =?UTF-8?q?=E8=AF=BE=E7=A8=8B=20-=20buildPlacedItems=20/=20handleOfficialS?= =?UTF-8?q?ave=EF=BC=9Aembed=5Fcourse=5Fevent=5Fid=20=E4=BC=98=E5=85=88?= =?UTF-8?q?=E5=8F=96=E5=90=8E=E7=AB=AF=E9=A2=84=E8=A7=88=E5=80=BC=EF=BC=8C?= =?UTF-8?q?=E5=85=9C=E5=BA=95=E8=B5=B0=E4=BD=8D=E7=BD=AE=E7=B4=A2=E5=BC=95?= =?UTF-8?q?=E6=9F=A5=E6=89=BE=EF=BC=8C=E8=A7=A3=E5=86=B3=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E6=8E=A5=E5=8F=A3=E5=9B=A0=E7=BC=BA=E5=AE=BF?= =?UTF-8?q?=E4=B8=BB=20ID=20=E6=8A=A5=E6=97=A5=E7=A8=8B=E5=86=B2=E7=AA=81?= =?UTF-8?q?=20-=20currentWeekEntries=EF=BC=9A=E8=A2=AB=E5=B5=8C=E5=85=A5?= =?UTF-8?q?=E7=9A=84=20existing=20course=20=E8=BF=87=E6=BB=A4=E4=B8=8D?= =?UTF-8?q?=E6=B8=B2=E6=9F=93=EF=BC=8C=E9=81=BF=E5=85=8D=E4=B8=8E=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E5=8D=A1=E7=89=87=E5=90=8C=E4=BD=8D=E9=87=8D=E5=8F=A0?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E8=AF=BE=E7=A8=8B=E8=A7=86=E8=A7=89=E8=A2=AB?= =?UTF-8?q?=E5=90=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端: 2. 预览转换补充嵌入关系桥接(防御性) - newAgent/conv/schedule_preview.go:ScheduleStateToPreview 中 suggested 任务 EmbedHost 非空时,解析宿主课程 SourceID 写入 entry.EventID,供前端作为 embed_course_event_id 透传 --- backend/newAgent/conv/schedule_preview.go | 7 +++ .../assistant/ScheduleFineTuneModal.vue | 50 +++++++++++++++++-- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/backend/newAgent/conv/schedule_preview.go b/backend/newAgent/conv/schedule_preview.go index 55c9a1e..5a835a3 100644 --- a/backend/newAgent/conv/schedule_preview.go +++ b/backend/newAgent/conv/schedule_preview.go @@ -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 + } + } } // 嵌入与阻塞语义。 diff --git a/frontend/src/components/assistant/ScheduleFineTuneModal.vue b/frontend/src/components/assistant/ScheduleFineTuneModal.vue index d61bb64..a339d88 100644 --- a/frontend/src/components/assistant/ScheduleFineTuneModal.vue +++ b/frontend/src/components/assistant/ScheduleFineTuneModal.vue @@ -55,8 +55,30 @@ function nextWeek() { if (currentWeek.value < weekRange.value.max) currentWeek.value++ } +// 构建现有可嵌入课程的位置索引,key 为 "week-day-sectionFrom-sectionTo" +function buildCoursePositionIndex(items: HybridScheduleEntry[]): Map { + const index = new Map() + 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, +): 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() 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() + 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 + }) )