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 && ( + + )}