From 14a61b9dec60f46374294ed81f509b9e823c9891 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, 1 Aug 2025 13:30:36 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E8=A7=92=E8=89=B2=E6=9B=BF=E6=8D=A2?= =?UTF-8?q?=E8=81=94=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/ui/FloatingGlassPanel.tsx | 5 +- components/ui/ImageWave.tsx | 21 ++++- components/ui/character-library-selector.tsx | 57 ++++++++++++ components/ui/character-tab-content.tsx | 87 +++++------------- components/ui/person-detection.tsx | 10 ++- components/ui/replace-character-panel.tsx | 4 +- components/ui/replace-panel.tsx | 4 +- components/ui/scene-tab-content.tsx | 2 +- components/ui/shot-editor/CharacterToken.tsx | 13 ++- components/ui/shot-editor/ShotEditor.tsx | 19 +++- components/ui/shot-tab-content.tsx | 92 +++++++++++++------- 11 files changed, 201 insertions(+), 113 deletions(-) create mode 100644 components/ui/character-library-selector.tsx diff --git a/components/ui/FloatingGlassPanel.tsx b/components/ui/FloatingGlassPanel.tsx index fcf8496..0564a75 100644 --- a/components/ui/FloatingGlassPanel.tsx +++ b/components/ui/FloatingGlassPanel.tsx @@ -9,9 +9,10 @@ type FloatingGlassPanelProps = { children: ReactNode; width?: string; r_key?: string | number; + panel_style?: React.CSSProperties; }; -export default function FloatingGlassPanel({ open, onClose, children, width = '320px', r_key }: FloatingGlassPanelProps) { +export default function FloatingGlassPanel({ open, onClose, children, width = '320px', r_key, panel_style }: FloatingGlassPanelProps) { // 定义弹出动画 const bounceAnimation = { scale: [0.95, 1.02, 0.98, 1], @@ -48,7 +49,7 @@ export default function FloatingGlassPanel({ open, onClose, children, width = '3 }} >
{children} diff --git a/components/ui/ImageWave.tsx b/components/ui/ImageWave.tsx index 60feecf..2f4e1d8 100644 --- a/components/ui/ImageWave.tsx +++ b/components/ui/ImageWave.tsx @@ -1,5 +1,7 @@ import React, { useState, useEffect, useCallback, useRef } from 'react'; import styled from 'styled-components'; +import { GlassIconButton } from '@/components/ui/glass-icon-button'; +import { Check } from 'lucide-react'; interface ImageWaveProps { // 图片列表数据 @@ -152,8 +154,6 @@ export const ImageWave: React.FC = ({ setCurrentExpandedItem(null); } else { setCurrentExpandedItem(index); - setCurrentSelectedIndex(index); - onClick?.(index); } }; @@ -188,6 +188,11 @@ export const ImageWave: React.FC = ({ }; }, [autoAnimate]); + const handleSelectImage = (index: number) => { + setCurrentSelectedIndex(index); + onClick?.(index); + }; + return ( @@ -196,11 +201,19 @@ export const ImageWave: React.FC = ({ key={index} width={itemWidth} height={itemHeight} - className={`item ${currentExpandedItem === index ? 'expanded' : ''} ${currentSelectedIndex === index ? 'selected' : ''}`} + className={`group relative item ${currentExpandedItem === index ? 'expanded' : ''} ${currentSelectedIndex === index ? 'selected' : ''}`} style={{ backgroundImage: `url(${image})` }} onClick={() => handleItemClick(index)} tabIndex={0} - /> + > + {/* 添加一个玻璃按钮 勾选当前图片 移入/选中改变图标颜色 */} + handleSelectImage(index)} + className="absolute top-1 right-1 z-[999] cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity duration-300 group-hover:bg-blue-500/50" + /> + ))} diff --git a/components/ui/character-library-selector.tsx b/components/ui/character-library-selector.tsx new file mode 100644 index 0000000..e8e01f9 --- /dev/null +++ b/components/ui/character-library-selector.tsx @@ -0,0 +1,57 @@ +import FloatingGlassPanel from './FloatingGlassPanel'; +import { ImageWave } from '@/components/ui/ImageWave'; + +const imageUrls = [ + 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg', + 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg', + 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-3.jpg', + 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-4.jpg', + 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg', + 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg', + 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-3.jpg', + 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-4.jpg', + 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg', + 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg', + 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-3.jpg', + 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-4.jpg', + 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg', + 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg', + 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-3.jpg', + 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-4.jpg', + 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg', + 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg', +]; + +export function CharacterLibrarySelector({ + isReplaceLibraryOpen, + setIsReplaceLibraryOpen, + onSelect, +}: { + isReplaceLibraryOpen: boolean; + setIsReplaceLibraryOpen: (open: boolean) => void; + onSelect: (index: number) => void; +}) { + return ( + setIsReplaceLibraryOpen(false)} + > + {/* 内容 */} + { + onSelect(index); + }} + /> + + ); +} \ No newline at end of file diff --git a/components/ui/character-tab-content.tsx b/components/ui/character-tab-content.tsx index 6f6be3e..42d7204 100644 --- a/components/ui/character-tab-content.tsx +++ b/components/ui/character-tab-content.tsx @@ -2,14 +2,11 @@ import React, { useState, useRef } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Upload, Library, Play, Pause, RefreshCw, Wand2, Users, Check, ReplaceAll, X } from 'lucide-react'; import { cn } from '@/public/lib/utils'; -import { GlassIconButton } from './glass-icon-button'; -import { ReplaceCharacterModal } from './replace-character-modal'; -import { Slider } from './slider'; import CharacterEditor from './character-editor'; import ImageBlurTransition from './ImageBlurTransition'; import FloatingGlassPanel from './FloatingGlassPanel'; import { ReplaceCharacterPanel, mockShots, mockCharacter } from './replace-character-panel'; -import { ImageWave } from '@/components/ui/ImageWave'; +import { CharacterLibrarySelector } from './character-library-selector'; interface Appearance { hairStyle: string; @@ -58,45 +55,19 @@ interface CharacterTabContentProps { roles: Role[]; } -const imageUrls = [ - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg', - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg', - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-3.jpg', - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-4.jpg', - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg', - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg', - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-3.jpg', - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-4.jpg', - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg', - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg', - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-3.jpg', - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-4.jpg', - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg', - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg', - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-3.jpg', - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-4.jpg', - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg', - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg', -]; - export function CharacterTabContent({ taskSketch, currentRoleIndex, onSketchSelect, roles = [mockRole] }: CharacterTabContentProps) { - const [isReplaceModalOpen, setIsReplaceModalOpen] = useState(false); - const [activeReplaceMethod, setActiveReplaceMethod] = useState('upload'); - const [newTag, setNewTag] = useState(''); const [localRole, setLocalRole] = useState(mockRole); const [currentRole, setCurrentRole] = useState(roles[currentRoleIndex]); const [isReplacePanelOpen, setIsReplacePanelOpen] = useState(false); const [replacePanelKey, setReplacePanelKey] = useState(0); const [ignoreReplace, setIgnoreReplace] = useState(false); const [isReplaceLibraryOpen, setIsReplaceLibraryOpen] = useState(false); - const [replaceLibraryKey, setReplaceLibraryKey] = useState(0); - const textareaRef = useRef(null); const handleReplaceCharacter = (url: string) => { setCurrentRole({ @@ -133,6 +104,13 @@ export function CharacterTabContent({ setCurrentRole(roles[index]); }; + // 从角色库中选择角色 + const handleSelectCharacter = (index: number) => { + console.log('index', index); + setIsReplaceLibraryOpen(false); + handleReplaceCharacter('https://c.huiying.video/images/5740cb7c-6e08-478f-9e7c-bca7f78a2bf6.jpg'); + }; + // 如果没有角色数据,显示占位内容 if (!roles || roles.length === 0) { return ( @@ -260,7 +238,7 @@ export function CharacterTabContent({ {/* 重新生成按钮、替换形象按钮 */}
console.log('Replace')} + onClick={() => handleReplaceCharacter('https://c.huiying.video/images/5740cb7c-6e08-478f-9e7c-bca7f78a2bf6.jpg')} className="flex items-center justify-center gap-2 px-4 py-3 bg-pink-500/10 hover:bg-pink-500/20 text-pink-500 rounded-lg transition-colors" whileHover={{ scale: 1.02 }} @@ -287,7 +265,12 @@ export function CharacterTabContent({ - + handleCloseReplacePanel()} + > {/* 从角色库中选择角色 */} - - {/* 标题 从角色库中选择角色 */} -
Role Library
- {/* 内容 */} - { - console.log('index', index); - }} - /> - {/* 操作按钮 */} -
- - -
-
+
); } \ No newline at end of file diff --git a/components/ui/person-detection.tsx b/components/ui/person-detection.tsx index 7c41fd5..8274d09 100644 --- a/components/ui/person-detection.tsx +++ b/components/ui/person-detection.tsx @@ -51,6 +51,7 @@ type Props = { scanTimeout?: number; isScanFailed?: boolean; // 外部传入的失败状态 onDetectionsChange?: (detections: PersonDetection[]) => void; + onPersonClick?: (person: PersonDetection) => void; }; export const PersonDetectionScene: React.FC = ({ @@ -63,7 +64,8 @@ export const PersonDetectionScene: React.FC = ({ onScanExit, scanTimeout = 10000, isScanFailed = false, - onDetectionsChange + onDetectionsChange, + onPersonClick }) => { const scanControls = useAnimation(); const videoRef = useRef(null); @@ -374,7 +376,9 @@ export const PersonDetectionScene: React.FC = ({ {detections.map((person, index) => { return ( - +
{ + onPersonClick?.(person); + }}> = ({ > {person.name} - +
); })} diff --git a/components/ui/replace-character-panel.tsx b/components/ui/replace-character-panel.tsx index 498a417..211ed01 100644 --- a/components/ui/replace-character-panel.tsx +++ b/components/ui/replace-character-panel.tsx @@ -4,6 +4,7 @@ import { Shot, Character } from '@/app/model/types'; interface ReplaceCharacterPanelProps { shots: Shot[]; character: Character; + showAddToLibrary?: boolean; onClose: () => void; onConfirm: (selectedShots: string[], addToLibrary: boolean) => void; } @@ -47,6 +48,7 @@ export const mockCharacter: Character = { export function ReplaceCharacterPanel({ shots = mockShots, character = mockCharacter, + showAddToLibrary = true, onClose, onConfirm, }: ReplaceCharacterPanelProps) { @@ -55,7 +57,7 @@ export function ReplaceCharacterPanel({ title="替换新形象" shots={shots} item={character} - showAddToLibrary={true} + showAddToLibrary={showAddToLibrary} addToLibraryText="新形象同步添加至角色库" onClose={onClose} onConfirm={onConfirm} diff --git a/components/ui/replace-panel.tsx b/components/ui/replace-panel.tsx index 08181a0..38d4fe9 100644 --- a/components/ui/replace-panel.tsx +++ b/components/ui/replace-panel.tsx @@ -100,14 +100,14 @@ export function ReplacePanel({ {/* 分镜展示区 */}
选择需要替换的分镜:
-
+
{shots.map((shot) => ( console.log('Replace')} + onClick={() => handleReplaceScene('https://c.huiying.video/images/5740cb7c-6e08-478f-9e7c-bca7f78a2bf6.jpg')} className="flex items-center justify-center gap-2 px-4 py-3 bg-pink-500/10 hover:bg-pink-500/20 text-pink-500 rounded-lg transition-colors" whileHover={{ scale: 1.02 }} diff --git a/components/ui/shot-editor/CharacterToken.tsx b/components/ui/shot-editor/CharacterToken.tsx index 4910397..313fbff 100644 --- a/components/ui/shot-editor/CharacterToken.tsx +++ b/components/ui/shot-editor/CharacterToken.tsx @@ -11,13 +11,18 @@ interface CharacterAttributes { age: string; } +// interface CharacterTokenProps extends ReactNodeViewProps { +// onClick?: (attrs: CharacterAttributes) => void +// } + export function CharacterToken(props: ReactNodeViewProps) { const [showCard, setShowCard] = useState(false) const { name, avatar, gender, age } = props.node.attrs as CharacterAttributes const handleClick = () => { console.log('点击角色:', name) - alert(`点击角色:${name}`) + const { editor } = props; + editor?.emit('character-clicked', props.node.attrs); } return ( @@ -81,6 +86,12 @@ export const CharacterTokenExtension = Node.create({ return ['character-token', mergeAttributes(HTMLAttributes)]; }, + // addStorage() { + // return { + // onClickCharacter: null as null | ((character: CharacterAttributes) => void), + // } + // }, + addNodeView() { return ReactNodeViewRenderer(CharacterToken); }, diff --git a/components/ui/shot-editor/ShotEditor.tsx b/components/ui/shot-editor/ShotEditor.tsx index 54a1319..e9ab0c0 100644 --- a/components/ui/shot-editor/ShotEditor.tsx +++ b/components/ui/shot-editor/ShotEditor.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useCallback, useEffect } from 'react'; import { EditorContent, useEditor } from '@tiptap/react'; import StarterKit from '@tiptap/starter-kit'; import { motion } from "framer-motion"; @@ -36,9 +36,10 @@ const initialContent = { interface ShotEditorProps { onAddSegment?: () => void; + onCharacterClick?: (attrs: any) => void; } -const ShotEditor = React.forwardRef<{ addSegment: () => void }, ShotEditorProps>(function ShotEditor({ onAddSegment }, ref) { +const ShotEditor = React.forwardRef<{ addSegment: () => void, onCharacterClick: (attrs: any) => void }, ShotEditorProps>(function ShotEditor({ onAddSegment, onCharacterClick }, ref) { const [segments, setSegments] = useState(initialContent.content); const editor = useEditor({ @@ -60,6 +61,20 @@ const ShotEditor = React.forwardRef<{ addSegment: () => void }, ShotEditorProps> }, }) + useEffect(() => { + const handleCharacterClick = (attrs: any) => { + console.log('SceneEditor 收到角色点击事件:', attrs) + // 你可以这里 setState 打开一个弹窗 / 面板等 + onCharacterClick?.(attrs); + }; + + editor?.on('character-clicked', handleCharacterClick as any); + + return () => { + editor?.off('character-clicked', handleCharacterClick as any); + }; + }, [editor]); + const addSegment = () => { if (!editor) return; diff --git a/components/ui/shot-tab-content.tsx b/components/ui/shot-tab-content.tsx index 6161876..9c9b7cf 100644 --- a/components/ui/shot-tab-content.tsx +++ b/components/ui/shot-tab-content.tsx @@ -10,6 +10,9 @@ import { MediaPropertiesModal } from './media-properties-modal'; import { DramaLineChart } from './drama-line-chart'; import { PersonDetection, PersonDetectionScene } from './person-detection'; import ShotEditor from './shot-editor/ShotEditor'; +import { CharacterLibrarySelector } from './character-library-selector'; +import FloatingGlassPanel from './FloatingGlassPanel'; +import { ReplaceCharacterPanel, mockShots, mockCharacter } from './replace-character-panel'; interface ShotTabContentProps { taskSketch: any[]; @@ -29,16 +32,16 @@ export function ShotTabContent({ const videosRef = useRef(null); const videoPlayerRef = useRef(null); const [isPlaying, setIsPlaying] = React.useState(externalIsPlaying); - const [isMuted, setIsMuted] = React.useState(false); - const [progress, setProgress] = React.useState(0); - const [isReplaceModalOpen, setIsReplaceModalOpen] = React.useState(false); - const [activeReplaceMethod, setActiveReplaceMethod] = React.useState<'upload' | 'library' | 'generate'>('upload'); const [isMediaPropertiesModalOpen, setIsMediaPropertiesModalOpen] = React.useState(false); - const [triggerScan, setTriggerScan] = useState(false); const [detections, setDetections] = useState([]); const [scanState, setScanState] = useState<'idle' | 'scanning' | 'detected'>('idle'); + const [isReplaceLibraryOpen, setIsReplaceLibraryOpen] = useState(false); + const [isReplacePanelOpen, setIsReplacePanelOpen] = useState(false); + + const [shots, setShots] = useState([]); + // 监听外部播放状态变化 useEffect(() => { @@ -87,13 +90,6 @@ export function ShotTabContent({ } }, [isPlaying, currentSketchIndex]); - // 更新进度条 - const handleTimeUpdate = () => { - if (videoPlayerRef.current) { - const progress = (videoPlayerRef.current.currentTime / videoPlayerRef.current.duration) * 100; - setProgress(progress); - } - }; // 处理扫描开始 const handleScan = () => { @@ -130,6 +126,27 @@ export function ShotTabContent({ } }; + // 处理人物点击 打开角色库 + const handlePersonClick = (person: PersonDetection) => { + console.log('person', person); + setIsReplaceLibraryOpen(true); + }; + + // 从角色库中选择角色 + const handleSelectCharacter = (index: number) => { + console.log('index', index); + setIsReplaceLibraryOpen(false); + // 模拟打开替换面板 + setTimeout(() => { + setIsReplacePanelOpen(true); + }, 1000); + }; + + // 确认替换角色 + const handleConfirmReplace = (selectedShots: string[], addToLibrary: boolean) => { + + }; + // 如果没有数据,显示空状态 if (sketches.length === 0) { return ( @@ -176,14 +193,14 @@ export function ShotTabContent({ Shot {index + 1}
{/* 鼠标悬浮/移出 显示/隐藏 删除图标 */} -
+ {/*
-
+
*/} ))}
@@ -251,6 +268,7 @@ export function ShotTabContent({ onScanTimeout={handleScanTimeout} onScanExit={handleScanTimeout} onDetectionsChange={handleDetectionsChange} + onPersonClick={handlePersonClick} /> {/*
); } diff --git a/components/ui/character-tab-content.tsx b/components/ui/character-tab-content.tsx index 42d7204..cf6ac7c 100644 --- a/components/ui/character-tab-content.tsx +++ b/components/ui/character-tab-content.tsx @@ -7,6 +7,7 @@ import ImageBlurTransition from './ImageBlurTransition'; import FloatingGlassPanel from './FloatingGlassPanel'; import { ReplaceCharacterPanel, mockShots, mockCharacter } from './replace-character-panel'; import { CharacterLibrarySelector } from './character-library-selector'; +import HorizontalScroller from './HorizontalScroller'; interface Appearance { hairStyle: string; @@ -67,9 +68,13 @@ export function CharacterTabContent({ const [replacePanelKey, setReplacePanelKey] = useState(0); const [ignoreReplace, setIgnoreReplace] = useState(false); const [isReplaceLibraryOpen, setIsReplaceLibraryOpen] = useState(false); + + const [selectRoleIndex, setSelectRoleIndex] = useState(0); + const [enableAnimation, setEnableAnimation] = useState(true); const handleReplaceCharacter = (url: string) => { + setEnableAnimation(true); setCurrentRole({ ...currentRole, url: url @@ -85,13 +90,14 @@ export function CharacterTabContent({ setIsReplacePanelOpen(false); }; + // 取消替换 const handleCloseReplacePanel = () => { setIsReplacePanelOpen(false); setIgnoreReplace(true); }; const handleChangeRole = (index: number) => { - if (currentRole.url !== roles[currentRoleIndex].url && !ignoreReplace) { + if (currentRole.url !== roles[selectRoleIndex].url && !ignoreReplace) { // 提示 角色已修改,弹出替换角色面板 if (isReplacePanelOpen) { setReplacePanelKey(replacePanelKey + 1); @@ -100,7 +106,11 @@ export function CharacterTabContent({ } return; } - onSketchSelect(index); + // 重置替换规则 + setEnableAnimation(false); + setIgnoreReplace(false); + + setSelectRoleIndex(index); setCurrentRole(roles[index]); }; @@ -130,16 +140,20 @@ export function CharacterTabContent({ animate={{ opacity: 1, y: 0 }} >
-
+ handleChangeRole(i)} + > {roles.map((role, index) => ( handleChangeRole(index)} whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} > @@ -153,13 +167,13 @@ export function CharacterTabContent({
))} -
+
{/* 下部分:角色详情 */} {/* 应用角色按钮 */}
diff --git a/components/ui/scene-tab-content.tsx b/components/ui/scene-tab-content.tsx index 8f87d31..414556a 100644 --- a/components/ui/scene-tab-content.tsx +++ b/components/ui/scene-tab-content.tsx @@ -7,6 +7,7 @@ import { cn } from '@/public/lib/utils'; import SceneEditor from './scene-editor'; import FloatingGlassPanel from './FloatingGlassPanel'; import { ReplaceScenePanel, mockShots } from './replace-scene-panel'; +import HorizontalScroller from './HorizontalScroller'; interface SceneEnvironment { time: { @@ -195,45 +196,44 @@ export function SceneTabContent({ > {/* 分镜缩略图行 */}
-
handleChangeScene(i)} > -
- {sketches.map((sketch, index) => ( - handleChangeScene(index)} - whileHover={{ scale: 1.05 }} - whileTap={{ scale: 0.95 }} - > - {`Sketch -
- Scene {index + 1} -
- {/* 鼠标悬浮/移出 显示/隐藏 删除图标 */} -
- -
-
- ))} -
+ {sketches.map((sketch, index) => ( + + {`Sketch +
+ Scene {index + 1} +
+ {/* 鼠标悬浮/移出 显示/隐藏 删除图标 */} + {/*
+ +
*/} +
+ ))} {/* 新增占位符 */} {/* 添加场景
*/} -
+
{/* 脚本预览行 - 单行滚动 */}
-
handleChangeScene(i)} > {sketches.map((script, index) => { const isActive = currentSketchIndex === index; @@ -269,7 +271,6 @@ export function SceneTabContent({ 'flex-shrink-0 cursor-pointer transition-all duration-300', isActive ? 'text-white' : 'text-white/50 hover:text-white/80' )} - onClick={() => handleChangeScene(index)} initial={false} animate={{ scale: isActive ? 1.02 : 1, @@ -286,7 +287,7 @@ export function SceneTabContent({ ); })} -
+ {/* 渐变遮罩 */}
diff --git a/components/ui/shot-tab-content.tsx b/components/ui/shot-tab-content.tsx index 9c9b7cf..3b78e26 100644 --- a/components/ui/shot-tab-content.tsx +++ b/components/ui/shot-tab-content.tsx @@ -13,6 +13,7 @@ import ShotEditor from './shot-editor/ShotEditor'; import { CharacterLibrarySelector } from './character-library-selector'; import FloatingGlassPanel from './FloatingGlassPanel'; import { ReplaceCharacterPanel, mockShots, mockCharacter } from './replace-character-panel'; +import HorizontalScroller from './HorizontalScroller'; interface ShotTabContentProps { taskSketch: any[]; @@ -27,9 +28,7 @@ export function ShotTabContent({ onSketchSelect, isPlaying: externalIsPlaying = true }: ShotTabContentProps) { - const thumbnailsRef = useRef(null); const editorRef = useRef(null); - const videosRef = useRef(null); const videoPlayerRef = useRef(null); const [isPlaying, setIsPlaying] = React.useState(externalIsPlaying); const [isMediaPropertiesModalOpen, setIsMediaPropertiesModalOpen] = React.useState(false); @@ -51,31 +50,6 @@ export function ShotTabContent({ // 确保 taskSketch 是数组 const sketches = Array.isArray(taskSketch) ? taskSketch : []; - // 自动滚动到选中项 - useEffect(() => { - if (thumbnailsRef.current && videosRef.current) { - const thumbnailContainer = thumbnailsRef.current; - const videoContainer = videosRef.current; - - const thumbnailWidth = thumbnailContainer.children[0]?.clientWidth ?? 0; - const thumbnailGap = 16; // gap-4 = 16px - const thumbnailScrollPosition = (thumbnailWidth + thumbnailGap) * currentSketchIndex; - - const videoElement = videoContainer.children[currentSketchIndex] as HTMLElement; - const videoScrollPosition = videoElement?.offsetLeft ?? 0; - - thumbnailContainer.scrollTo({ - left: thumbnailScrollPosition - thumbnailContainer.clientWidth / 2 + thumbnailWidth / 2, - behavior: 'smooth' - }); - - videoContainer.scrollTo({ - left: videoScrollPosition - videoContainer.clientWidth / 2 + videoElement?.clientWidth / 2, - behavior: 'smooth' - }); - } - }, [currentSketchIndex]); - // 视频播放控制 useEffect(() => { if (videoPlayerRef.current) { @@ -165,9 +139,11 @@ export function ShotTabContent({ > {/* 分镜缩略图行 */}
-
onSketchSelect(i)} > {sketches.map((sketch, index) => ( onSketchSelect(index)} - whileHover={{ scale: 1.05 }} - whileTap={{ scale: 0.95 }} + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} > ))} -
+
{/* 视频描述行 - 单行滚动 */}
-
onSketchSelect(i)} > {sketches.map((video, index) => { const isActive = currentSketchIndex === index; @@ -238,7 +215,7 @@ export function ShotTabContent({ ); })} -
+ {/* 渐变遮罩 */}
From 3ccc6e44a599c301f642f35f8c6761a22274fdaf 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, 1 Aug 2025 20:47:45 +0800 Subject: [PATCH 3/3] =?UTF-8?q?edit=20modal=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/ui/edit-modal.tsx | 4 ++-- components/ui/shot-editor/CharacterToken.tsx | 2 +- components/ui/shot-tab-content.tsx | 11 ++++++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/components/ui/edit-modal.tsx b/components/ui/edit-modal.tsx index be4e2ec..c420bc5 100644 --- a/components/ui/edit-modal.tsx +++ b/components/ui/edit-modal.tsx @@ -235,7 +235,7 @@ export function EditModal({
{/* 底部操作栏 */} - {/*
+
-
*/} +
diff --git a/components/ui/shot-editor/CharacterToken.tsx b/components/ui/shot-editor/CharacterToken.tsx index 313fbff..7eaefff 100644 --- a/components/ui/shot-editor/CharacterToken.tsx +++ b/components/ui/shot-editor/CharacterToken.tsx @@ -43,7 +43,7 @@ export function CharacterToken(props: ReactNodeViewProps) { animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: 4 }} transition={{ duration: 0.2 }} - className="absolute top-full left-0 mt-2 w-64 rounded-lg bg-gray-900 border border-gray-800 shadow-2xl p-4 z-50" + className="absolute top-full left-0 mt-2 w-64 rounded-lg backdrop-blur-md bg-white/10 border border-white/20 p-4 z-50" >
{ + // 切换前 判断数据是否发生变化 + onSketchSelect(index); + }; + // 如果没有数据,显示空状态 if (sketches.length === 0) { return ( @@ -143,7 +149,7 @@ export function ShotTabContent({ itemWidth={128} gap={0} selectedIndex={currentSketchIndex} - onItemClick={(i: number) => onSketchSelect(i)} + onItemClick={(i: number) => handleSelectShot(i)} > {sketches.map((sketch, index) => ( onSketchSelect(i)} + onItemClick={(i: number) => handleSelectShot(i)} > {sketches.map((video, index) => { const isActive = currentSketchIndex === index; @@ -198,7 +204,6 @@ export function ShotTabContent({ 'flex-shrink-0 cursor-pointer transition-all duration-300', isActive ? 'text-white' : 'text-white/50 hover:text-white/80' )} - onClick={() => onSketchSelect(index)} initial={false} animate={{ scale: isActive ? 1.02 : 1,