From 7603a1a26ee60cb240ea39e091919ac2ccf5d472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8C=97=E6=9E=B3?= <7854742+wang_rumeng@user.noreply.gitee.com> Date: Fri, 4 Jul 2025 17:06:26 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=BC=96=E8=AF=91=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/pages/script-overview.tsx | 70 ++- components/pages/storyboard-view.tsx | 67 ++- .../script-overview/scene-card-list.tsx | 40 +- components/script-overview/scene-card.tsx | 93 ++-- .../storyboard/storyboard-card-list.tsx | 36 +- components/storyboard/storyboard-card.tsx | 500 ++++++++++-------- components/work-flow/api.ts | 28 +- 7 files changed, 478 insertions(+), 356 deletions(-) diff --git a/components/pages/script-overview.tsx b/components/pages/script-overview.tsx index 7e49ea6..ff7969f 100644 --- a/components/pages/script-overview.tsx +++ b/components/pages/script-overview.tsx @@ -2,7 +2,21 @@ import { useState } from 'react'; import { motion } from 'framer-motion'; -import { DragDropContext, DropResult } from 'react-beautiful-dnd'; +import { + DndContext, + closestCenter, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, +} from '@dnd-kit/core'; +import { + arrayMove, + SortableContext, + sortableKeyboardCoordinates, + verticalListSortingStrategy, +} from '@dnd-kit/sortable'; +import type { DragEndEvent } from '@dnd-kit/core'; import { ScriptMetaInfo } from '../script-overview/script-meta-info'; import { SceneFilmstrip } from '../script-overview/scene-filmstrip'; import { SceneCardList } from '../script-overview/scene-card-list'; @@ -30,6 +44,14 @@ export interface ScriptMeta { } export default function ScriptOverview() { + // Configure drag sensors + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }) + ); + // Example Data const [scriptMeta] = useState({ title: "Lost Stars", @@ -164,14 +186,21 @@ export default function ScriptOverview() { const [selectedSceneId, setSelectedSceneId] = useState(); // Handle scene drag and drop sorting - const handleDragEnd = (result: DropResult) => { - if (!result.destination) return; + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event; - const items = Array.from(scenes); - const [reorderedItem] = items.splice(result.source.index, 1); - items.splice(result.destination.index, 0, reorderedItem); + if (active.id === over?.id) { + return; + } - setScenes(items); + const oldIndex = scenes.findIndex(scene => scene.id === active.id); + const newIndex = scenes.findIndex(scene => scene.id === over?.id); + + if (oldIndex === -1 || newIndex === -1) { + return; + } + + setScenes(arrayMove(scenes, oldIndex, newIndex)); }; // Handle scene updates @@ -215,15 +244,24 @@ export default function ScriptOverview() {
{/* Scene Card List */}
- - - + + scene.id)} + strategy={verticalListSortingStrategy} + > + + +
{/* Filmstrip Preview */} diff --git a/components/pages/storyboard-view.tsx b/components/pages/storyboard-view.tsx index eccdfd4..5c14d27 100644 --- a/components/pages/storyboard-view.tsx +++ b/components/pages/storyboard-view.tsx @@ -2,7 +2,21 @@ import { useState } from 'react'; import { motion } from 'framer-motion'; -import { DragDropContext, DropResult } from 'react-beautiful-dnd'; +import { + DndContext, + closestCenter, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, +} from '@dnd-kit/core'; +import { + arrayMove, + SortableContext, + sortableKeyboardCoordinates, + verticalListSortingStrategy, +} from '@dnd-kit/sortable'; +import type { DragEndEvent } from '@dnd-kit/core'; import { ScriptMetaInfo } from '../script-overview/script-meta-info'; import { SceneFilmstrip } from '../script-overview/scene-filmstrip'; import { StoryboardCardList } from '../storyboard/storyboard-card-list'; @@ -145,14 +159,21 @@ export default function StoryboardView() { const [selectedSceneId, setSelectedSceneId] = useState(); // 处理场景拖拽排序 - const handleDragEnd = (result: DropResult) => { - if (!result.destination) return; + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event; - const items = Array.from(scenes); - const [reorderedItem] = items.splice(result.source.index, 1); - items.splice(result.destination.index, 0, reorderedItem); + if (active.id === over?.id) { + return; + } - setScenes(items); + const oldIndex = scenes.findIndex(scene => scene.id === active.id); + const newIndex = scenes.findIndex(scene => scene.id === over?.id); + + if (oldIndex === -1 || newIndex === -1) { + return; + } + + setScenes(arrayMove(scenes, oldIndex, newIndex)); }; // 处理场景更新 @@ -209,15 +230,29 @@ export default function StoryboardView() {
{/* Scene Card List */}
- - - + + scene.id)} + strategy={verticalListSortingStrategy} + > + + +
{/* Filmstrip Preview */} diff --git a/components/script-overview/scene-card-list.tsx b/components/script-overview/scene-card-list.tsx index 06af1f1..cd60143 100644 --- a/components/script-overview/scene-card-list.tsx +++ b/components/script-overview/scene-card-list.tsx @@ -1,4 +1,3 @@ -import { Droppable } from 'react-beautiful-dnd'; import { Scene } from '../pages/script-overview'; import { SceneCard } from './scene-card'; @@ -18,31 +17,18 @@ export function SceneCardList({ onSceneDuplicate }: SceneCardListProps) { return ( - - {(provided, snapshot) => ( -
- {scenes.map((scene, index) => ( - onSceneUpdate(scene.id, updates)} - onDelete={() => onSceneDelete(scene.id)} - onDuplicate={() => onSceneDuplicate(scene.id)} - /> - ))} - {provided.placeholder} -
- )} -
+
+ {scenes.map((scene, index) => ( + onSceneUpdate(scene.id, updates)} + onDelete={() => onSceneDelete(scene.id)} + onDuplicate={() => onSceneDuplicate(scene.id)} + /> + ))} +
); } \ No newline at end of file diff --git a/components/script-overview/scene-card.tsx b/components/script-overview/scene-card.tsx index bcd3330..e1e2af4 100644 --- a/components/script-overview/scene-card.tsx +++ b/components/script-overview/scene-card.tsx @@ -1,6 +1,7 @@ import { useState, useRef, useEffect } from 'react'; import { motion } from 'framer-motion'; -import { Draggable } from 'react-beautiful-dnd'; +import { useSortable } from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; import { Trash2, Copy, RefreshCw, GripVertical } from 'lucide-react'; import { Scene } from '../pages/script-overview'; import Image from 'next/image'; @@ -29,6 +30,20 @@ export function SceneCard({ const cardRef = useRef(null); const timeoutRef = useRef(); + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id: scene.id }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + }; + // 处理自动保存 const handleContentChange = ( field: keyof Scene, @@ -67,42 +82,48 @@ export function SceneCard({ }, [isSelected]); return ( - - {(provided, snapshot) => ( +
+ setIsHovered(true)} + onMouseLeave={() => { + setIsHovered(false); + setShowDeleteConfirm(false); + }} + > + {/* Drag Handle */}
- setIsHovered(true)} - onMouseLeave={() => { - setIsHovered(false); - setShowDeleteConfirm(false); - }} - > + +
+ {/* Scene Image */}
- )} - ); } \ No newline at end of file diff --git a/components/storyboard/storyboard-card-list.tsx b/components/storyboard/storyboard-card-list.tsx index 3f897ef..84b9aed 100644 --- a/components/storyboard/storyboard-card-list.tsx +++ b/components/storyboard/storyboard-card-list.tsx @@ -1,4 +1,3 @@ -import { Droppable } from 'react-beautiful-dnd'; import { StoryboardScene } from '../pages/storyboard-view'; import { StoryboardCard } from './storyboard-card'; @@ -18,27 +17,18 @@ export function StoryboardCardList({ onSceneDuplicate }: StoryboardCardListProps) { return ( - - {(provided) => ( -
- {scenes.map((scene, index) => ( - onSceneUpdate(scene.id, updates)} - onDelete={() => onSceneDelete(scene.id)} - onDuplicate={() => onSceneDuplicate(scene.id)} - /> - ))} - {provided.placeholder} -
- )} -
+
+ {scenes.map((scene, index) => ( + onSceneUpdate(scene.id, updates)} + onDelete={() => onSceneDelete(scene.id)} + onDuplicate={() => onSceneDuplicate(scene.id)} + /> + ))} +
); } \ No newline at end of file diff --git a/components/storyboard/storyboard-card.tsx b/components/storyboard/storyboard-card.tsx index 8e3d5e3..4acd0a0 100644 --- a/components/storyboard/storyboard-card.tsx +++ b/components/storyboard/storyboard-card.tsx @@ -1,7 +1,8 @@ import { useState, useRef, useEffect } from 'react'; import { motion } from 'framer-motion'; -import { Draggable } from 'react-beautiful-dnd'; -import { Trash2, Copy, RefreshCw } from 'lucide-react'; +import { useSortable } from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { Trash2, Copy, RefreshCw, GripVertical } from 'lucide-react'; import { StoryboardScene } from '../pages/storyboard-view'; import Image from 'next/image'; @@ -29,6 +30,20 @@ export function StoryboardCard({ const cardRef = useRef(null); const timeoutRef = useRef(); + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id: scene.id }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + }; + // 处理自动保存 const handleContentChange = ( field: keyof StoryboardScene, @@ -67,244 +82,261 @@ export function StoryboardCard({ }, [isSelected]); return ( - - {(provided, snapshot) => ( +
setIsHovered(true)} + onMouseLeave={() => { + setIsHovered(false); + setShowDeleteConfirm(false); + }} + > + setIsHovered(true)} + onMouseLeave={() => { + setIsHovered(false); + setShowDeleteConfirm(false); + }} + > + {/* Drag Handle */}
- setIsHovered(true)} - onMouseLeave={() => { - setIsHovered(false); - setShowDeleteConfirm(false); + +
+ + {/* Scene Image */} +
+ {scene.name} +
+
+ + {/* Content Area */} +
+ {/* Scrollable Content */} +
+ {/* Scene Name */} + +
setIsEditing(true)} + onBlur={(e) => { + setIsEditing(false); + handleContentChange('name', e.currentTarget.textContent || '', e.currentTarget); + }} + className="outline-none focus:bg-white/5 rounded px-2 py-1 + transition-colors" + dangerouslySetInnerHTML={{ __html: scene.name }} + /> + + + {/* Scene Description */} + +
setIsEditing(true)} + onBlur={(e) => { + setIsEditing(false); + handleContentChange('description', e.currentTarget.textContent || '', e.currentTarget); + }} + className="outline-none focus:bg-white/5 rounded px-2 py-1 + transition-colors" + dangerouslySetInnerHTML={{ __html: scene.description }} + /> + + + {/* Shot Description */} +
+ +
setIsEditing(true)} + onBlur={(e) => { + setIsEditing(false); + handleContentChange('shot', e.currentTarget.textContent || '', e.currentTarget); + }} + className="outline-none focus:bg-white/5 rounded px-2 py-1 + transition-colors text-white/80" + dangerouslySetInnerHTML={{ __html: scene.shot }} + /> +
+ + {/* Frame Description */} +
+ +
setIsEditing(true)} + onBlur={(e) => { + setIsEditing(false); + handleContentChange('frame', e.currentTarget.textContent || '', e.currentTarget); + }} + className="outline-none focus:bg-white/5 rounded px-2 py-1 + transition-colors text-white/80" + dangerouslySetInnerHTML={{ __html: scene.frame }} + /> +
+ + {/* Atmosphere */} +
+ +
setIsEditing(true)} + onBlur={(e) => { + setIsEditing(false); + handleContentChange('atmosphere', e.currentTarget.textContent || '', e.currentTarget); + }} + className="outline-none focus:bg-white/5 rounded px-2 py-1 + transition-colors text-white/80" + dangerouslySetInnerHTML={{ __html: scene.atmosphere }} + /> +
+
+
+ + {/* Floating Action Bar */} + + {/* Delete Button */} + { + e.stopPropagation(); + setShowDeleteConfirm(true); }} > - {/* Scene Image */} -
- {scene.name} -
-
- - {/* Content Area */} -
- {/* Scrollable Content */} -
- {/* Scene Name */} - -
setIsEditing(true)} - onBlur={(e) => { - setIsEditing(false); - handleContentChange('name', e.currentTarget.textContent || '', e.currentTarget); - }} - className="outline-none focus:bg-white/5 rounded px-2 py-1 - transition-colors" - dangerouslySetInnerHTML={{ __html: scene.name }} - /> - - - {/* Scene Description */} - -
setIsEditing(true)} - onBlur={(e) => { - setIsEditing(false); - handleContentChange('description', e.currentTarget.textContent || '', e.currentTarget); - }} - className="outline-none focus:bg-white/5 rounded px-2 py-1 - transition-colors" - dangerouslySetInnerHTML={{ __html: scene.description }} - /> - - - {/* Shot Description */} -
- -
setIsEditing(true)} - onBlur={(e) => { - setIsEditing(false); - handleContentChange('shot', e.currentTarget.textContent || '', e.currentTarget); - }} - className="outline-none focus:bg-white/5 rounded px-2 py-1 - transition-colors text-white/80" - dangerouslySetInnerHTML={{ __html: scene.shot }} - /> -
- - {/* Frame Description */} -
- -
setIsEditing(true)} - onBlur={(e) => { - setIsEditing(false); - handleContentChange('frame', e.currentTarget.textContent || '', e.currentTarget); - }} - className="outline-none focus:bg-white/5 rounded px-2 py-1 - transition-colors text-white/80" - dangerouslySetInnerHTML={{ __html: scene.frame }} - /> -
- - {/* Atmosphere */} -
- -
setIsEditing(true)} - onBlur={(e) => { - setIsEditing(false); - handleContentChange('atmosphere', e.currentTarget.textContent || '', e.currentTarget); - }} - className="outline-none focus:bg-white/5 rounded px-2 py-1 - transition-colors text-white/80" - dangerouslySetInnerHTML={{ __html: scene.atmosphere }} - /> -
-
-
- - {/* Floating Action Bar */} - - {/* Delete Button */} - { - e.stopPropagation(); - setShowDeleteConfirm(true); - }} - > - - {/* Delete Confirmation */} - {showDeleteConfirm && ( -
e.stopPropagation()} - > - Confirm delete? - - -
- )} -
- - {/* Duplicate Button */} - { - e.stopPropagation(); - onDuplicate(); - }} - className="p-2 rounded-lg hover:bg-white/10 transition-colors" - > - - - - {/* Regenerate Button */} - + {/* Delete Confirmation */} + {showDeleteConfirm && ( +
e.stopPropagation()} - className="p-2 rounded-lg hover:bg-white/10 transition-colors" > - - - + Confirm delete? + + +
+ )} +
- {/* Save Indicator */} - - Saved - -
-
- )} - + {/* Duplicate Button */} + { + e.stopPropagation(); + onDuplicate(); + }} + className="p-2 rounded-lg hover:bg-white/10 transition-colors" + > + + + + {/* Regenerate Button */} + e.stopPropagation()} + className="p-2 rounded-lg hover:bg-white/10 transition-colors" + > + + + + + {/* Save Indicator */} + + Saved + + +
); } \ No newline at end of file diff --git a/components/work-flow/api.ts b/components/work-flow/api.ts index f372ac3..930a42c 100644 --- a/components/work-flow/api.ts +++ b/components/work-flow/api.ts @@ -6,12 +6,28 @@ import { } from './constants'; // 当前选择的mock数据 -let selectedMockData = getRandomMockData(); +let selectedMockData: any = null; + +// 加载mock数据的辅助函数 +const loadMockData = async () => { + if (!selectedMockData) { + try { + selectedMockData = await getRandomMockData(); + } catch (error) { + // 如果API失败,使用本地fallback数据 + const { MOCK_DATA } = await import('./constants'); + const randomIndex = Math.floor(Math.random() * MOCK_DATA.length); + selectedMockData = MOCK_DATA[randomIndex]; + console.log('使用本地fallback数据:', selectedMockData); + } + } + return selectedMockData; +}; // 模拟接口请求 获取任务详情 export const getTaskDetail = async (taskId: string): Promise => { - // 每次获取任务详情时重新随机选择数据 - selectedMockData = getRandomMockData(); + // 确保已经加载了数据 + await loadMockData(); const data: TaskObject = { projectId: selectedMockData.detail.projectId, @@ -32,6 +48,9 @@ export const getTaskSketch = async ( taskId: string, onProgress: (sketch: SketchItem, index: number) => void ): Promise => { + // 确保已经加载了数据 + await loadMockData(); + const sketchData = selectedMockData.sketch; const totalSketches = sketchData.length; @@ -71,6 +90,9 @@ export const getTaskVideo = async ( sketchCount: number, onProgress: (video: VideoItem, index: number) => void ): Promise => { + // 确保已经加载了数据 + await loadMockData(); + const videoData = selectedMockData.video; const totalVideos = videoData.length;