From 6cab62d140af921013d6586708500a5342d38d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AE=8F=E5=87=AF?= Date: Sun, 17 May 2026 18:00:07 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BD=9C=E5=AE=B6=E5=A4=A7=E7=BA=B2=E6=8B=96?= =?UTF-8?q?=E6=8B=BD=E8=B0=83=E6=95=B4=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ai-writing/OutlineCard.tsx | 23 +++++++++++++++ .../components/ai-writing/OutlineSection.tsx | 28 +++++++++++++++++-- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/frontend-web/src/open-canvas/components/ai-writing/OutlineCard.tsx b/frontend-web/src/open-canvas/components/ai-writing/OutlineCard.tsx index 07bafdaa..08facb11 100644 --- a/frontend-web/src/open-canvas/components/ai-writing/OutlineCard.tsx +++ b/frontend-web/src/open-canvas/components/ai-writing/OutlineCard.tsx @@ -14,6 +14,8 @@ export function OutlineCard({ outline, onChange, readOnly = false }: OutlineCard const [collapsed, setCollapsed] = useState(false); const [editingTitle, setEditingTitle] = useState(false); const [titleDraft, setTitleDraft] = useState(outline.title); + const [dragIndex, setDragIndex] = useState(null); + const [overIndex, setOverIndex] = useState(null); const editable = !readOnly && !!onChange; @@ -37,6 +39,14 @@ export function OutlineCard({ outline, onChange, readOnly = false }: OutlineCard onChange?.({ ...outline, sections: outline.sections.filter((_, idx) => idx !== i) }); } + function reorderSections(from: number, to: number) { + if (from === to) return; + const sections = [...outline.sections]; + const [moved] = sections.splice(from, 1); + sections.splice(to, 0, moved); + onChange?.({ ...outline, sections }); + } + function addSection() { onChange?.({ ...outline, @@ -96,6 +106,19 @@ export function OutlineCard({ outline, onChange, readOnly = false }: OutlineCard onChange={(updated) => updateSection(i, updated)} onRemove={editable ? () => removeSection(i) : undefined} readOnly={!editable} + isDragging={dragIndex === i} + isOver={overIndex === i && dragIndex !== null && dragIndex !== i} + onDragStart={() => setDragIndex(i)} + onDragEnter={() => setOverIndex(i)} + onDrop={() => { + if (dragIndex !== null && dragIndex !== i) reorderSections(dragIndex, i); + setDragIndex(null); + setOverIndex(null); + }} + onDragEnd={() => { + setDragIndex(null); + setOverIndex(null); + }} /> ))} diff --git a/frontend-web/src/open-canvas/components/ai-writing/OutlineSection.tsx b/frontend-web/src/open-canvas/components/ai-writing/OutlineSection.tsx index 57869505..747df029 100644 --- a/frontend-web/src/open-canvas/components/ai-writing/OutlineSection.tsx +++ b/frontend-web/src/open-canvas/components/ai-writing/OutlineSection.tsx @@ -9,9 +9,19 @@ interface OutlineSectionProps { onChange: (updated: OutlineSectionType) => void; onRemove?: () => void; readOnly?: boolean; + // Drag-and-drop + isDragging?: boolean; + isOver?: boolean; + onDragStart?: () => void; + onDragEnter?: () => void; + onDrop?: () => void; + onDragEnd?: () => void; } -export function OutlineSection({ section, index, onChange, onRemove, readOnly }: OutlineSectionProps) { +export function OutlineSection({ + section, index, onChange, onRemove, readOnly, + isDragging, isOver, onDragStart, onDragEnter, onDrop, onDragEnd, +}: OutlineSectionProps) { const [expanded, setExpanded] = useState(false); const [editingTitle, setEditingTitle] = useState(false); const [titleDraft, setTitleDraft] = useState(section.sectionTitle); @@ -41,10 +51,22 @@ export function OutlineSection({ section, index, onChange, onRemove, readOnly }: } return ( -
+
{ e.stopPropagation(); onDragStart?.(); }} + onDragOver={(e) => { e.preventDefault(); e.stopPropagation(); onDragEnter?.(); }} + onDrop={(e) => { e.preventDefault(); e.stopPropagation(); onDrop?.(); }} + onDragEnd={(e) => { e.stopPropagation(); onDragEnd?.(); }} + className={cn( + 'rounded border bg-background text-xs transition-all duration-150 select-none', + isDragging ? 'opacity-40 border-border/60' : isOver ? 'border-blue-400 ring-1 ring-blue-400/40' : 'border-border/60', + )} + > {/* 章节头 */}
- {!readOnly && } + {!readOnly && ( + + )}