作家大纲拖拽调整顺序

This commit is contained in:
董宏凯 committed 2026-05-17 18:00:07 +08:00
1 parent 9526268f1f
commit 6cab62d140
2 files changed
+48 -3

No files matched your search

@@ -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<number | null>(null);
const [overIndex, setOverIndex] = useState<number | null>(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);
}}
/>
))}
</div>
@@ -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 (
<div className="rounded border border-border/60 bg-background text-xs">
<div
draggable={!readOnly}
onDragStart={(e) => { 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',
)}
>
{/* 章节头 */}
<div className="flex items-center gap-1.5 px-2.5 py-2">
{!readOnly && <GripVertical className="size-3 text-muted-foreground/40 shrink-0 cursor-grab" />}
{!readOnly && (
<GripVertical className="size-3 text-muted-foreground/40 shrink-0 cursor-grab active:cursor-grabbing" />
)}
<button
type="button"
onClick={() => setExpanded((v) => !v)}