forked from 77media/video-flow
更新角色库选择组件,新增加载状态处理,优化角色数据展示逻辑。同时,调整角色替换面板以支持加载状态,确保用户体验流畅。
This commit is contained in:
parent
944b465069
commit
5842f177de
@ -57,7 +57,7 @@ export const useEditData = (tabType: string) => {
|
|||||||
toggleShotSelection,
|
toggleShotSelection,
|
||||||
applyRoleToSelectedShots,
|
applyRoleToSelectedShots,
|
||||||
clearShotSelection
|
clearShotSelection
|
||||||
} = useRoleShotServiceHook(projectId);
|
} = useRoleShotServiceHook(projectId, selectedRole);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tabType === 'shot') {
|
if (tabType === 'shot') {
|
||||||
@ -69,7 +69,6 @@ export const useEditData = (tabType: string) => {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
} else if (tabType === 'role') {
|
} else if (tabType === 'role') {
|
||||||
// fetchUserRoleLibrary();
|
|
||||||
fetchRoleList(projectId).then(() => {
|
fetchRoleList(projectId).then(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
@ -105,6 +104,7 @@ export const useEditData = (tabType: string) => {
|
|||||||
optimizeRoleText,
|
optimizeRoleText,
|
||||||
updateRoleText,
|
updateRoleText,
|
||||||
regenerateRole,
|
regenerateRole,
|
||||||
|
fetchUserRoleLibrary,
|
||||||
// role shot
|
// role shot
|
||||||
shotSelectionList,
|
shotSelectionList,
|
||||||
selectedRoleId,
|
selectedRoleId,
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { ImageWave } from '@/components/ui/ImageWave';
|
|||||||
import { RoleEntity } from '@/app/service/domain/Entities';
|
import { RoleEntity } from '@/app/service/domain/Entities';
|
||||||
|
|
||||||
interface CharacterLibrarySelectorProps {
|
interface CharacterLibrarySelectorProps {
|
||||||
|
isLoading: boolean;
|
||||||
isReplaceLibraryOpen: boolean;
|
isReplaceLibraryOpen: boolean;
|
||||||
setIsReplaceLibraryOpen: (open: boolean) => void;
|
setIsReplaceLibraryOpen: (open: boolean) => void;
|
||||||
onSelect: (index: number) => void;
|
onSelect: (index: number) => void;
|
||||||
@ -11,13 +12,28 @@ interface CharacterLibrarySelectorProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function CharacterLibrarySelector({
|
export function CharacterLibrarySelector({
|
||||||
|
isLoading,
|
||||||
isReplaceLibraryOpen,
|
isReplaceLibraryOpen,
|
||||||
setIsReplaceLibraryOpen,
|
setIsReplaceLibraryOpen,
|
||||||
onSelect,
|
onSelect,
|
||||||
userRoleLibrary = []
|
userRoleLibrary = []
|
||||||
}: CharacterLibrarySelectorProps) {
|
}: CharacterLibrarySelectorProps) {
|
||||||
// 将 RoleEntity[] 转换为图片URL数组
|
|
||||||
const imageUrls = userRoleLibrary.map(role => role.imageUrl);
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<FloatingGlassPanel
|
||||||
|
open={isReplaceLibraryOpen}
|
||||||
|
width='90vw'
|
||||||
|
panel_style={{ background: 'unset', border: 'unset', backdropFilter: 'unset', boxShadow: 'none' }}
|
||||||
|
onClose={() => setIsReplaceLibraryOpen(false)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col items-center justify-center w-full h-64 text-white/50">
|
||||||
|
<div className="w-12 h-12 mb-4 animate-spin rounded-full border-b-2 border-blue-600" />
|
||||||
|
<p>Loading...</p>
|
||||||
|
</div>
|
||||||
|
</FloatingGlassPanel>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 如果没有数据,显示空状态
|
// 如果没有数据,显示空状态
|
||||||
if (userRoleLibrary.length === 0) {
|
if (userRoleLibrary.length === 0) {
|
||||||
@ -44,7 +60,7 @@ export function CharacterLibrarySelector({
|
|||||||
>
|
>
|
||||||
{/* 内容 */}
|
{/* 内容 */}
|
||||||
<ImageWave
|
<ImageWave
|
||||||
images={imageUrls}
|
images={userRoleLibrary.map(role => role.imageUrl)}
|
||||||
containerWidth="90vw"
|
containerWidth="90vw"
|
||||||
containerHeight="calc(var(--index) * 15)"
|
containerHeight="calc(var(--index) * 15)"
|
||||||
itemWidth="calc(var(--index) * 2)"
|
itemWidth="calc(var(--index) * 2)"
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { CharacterLibrarySelector } from './character-library-selector';
|
|||||||
import HorizontalScroller from './HorizontalScroller';
|
import HorizontalScroller from './HorizontalScroller';
|
||||||
import { useEditData } from '@/components/pages/work-flow/use-edit-data';
|
import { useEditData } from '@/components/pages/work-flow/use-edit-data';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
|
import { RoleEntity } from '@/app/service/domain/Entities';
|
||||||
|
|
||||||
interface Appearance {
|
interface Appearance {
|
||||||
hairStyle: string;
|
hairStyle: string;
|
||||||
@ -75,6 +76,8 @@ export function CharacterTabContent({
|
|||||||
const characterEditorRef = useRef<any>(null);
|
const characterEditorRef = useRef<any>(null);
|
||||||
const [isInitialized, setIsInitialized] = useState(false);
|
const [isInitialized, setIsInitialized] = useState(false);
|
||||||
const [isRegenerate, setIsRegenerate] = useState(false);
|
const [isRegenerate, setIsRegenerate] = useState(false);
|
||||||
|
const [isLoadingShots, setIsLoadingShots] = useState(false);
|
||||||
|
const [isLoadingLibrary, setIsLoadingLibrary] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
loading,
|
loading,
|
||||||
@ -85,16 +88,11 @@ export function CharacterTabContent({
|
|||||||
optimizeRoleText,
|
optimizeRoleText,
|
||||||
updateRoleText,
|
updateRoleText,
|
||||||
regenerateRole,
|
regenerateRole,
|
||||||
|
fetchUserRoleLibrary,
|
||||||
// role shot
|
// role shot
|
||||||
shotSelectionList,
|
shotSelectionList,
|
||||||
selectedRoleId,
|
|
||||||
isAllVideoSegmentSelected,
|
|
||||||
selectedVideoSegmentCount,
|
|
||||||
fetchRoleShots,
|
fetchRoleShots,
|
||||||
toggleSelectAllShots,
|
applyRoleToSelectedShots
|
||||||
toggleShotSelection,
|
|
||||||
applyRoleToSelectedShots,
|
|
||||||
clearShotSelection
|
|
||||||
} = useEditData('role');
|
} = useEditData('role');
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const episodeId = searchParams.get('episodeId');
|
const episodeId = searchParams.get('episodeId');
|
||||||
@ -113,16 +111,22 @@ export function CharacterTabContent({
|
|||||||
console.log('获取选中项数据', selectedRole);
|
console.log('获取选中项数据', selectedRole);
|
||||||
}, [selectedRole]);
|
}, [selectedRole]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('获取角色库数据', userRoleLibrary);
|
||||||
|
}, [userRoleLibrary]);
|
||||||
|
|
||||||
const handleSmartPolish = (text: string) => {
|
const handleSmartPolish = (text: string) => {
|
||||||
// 然后调用优化角色文本
|
// 然后调用优化角色文本
|
||||||
optimizeRoleText(text);
|
optimizeRoleText(text);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStartReplaceCharacter = () => {
|
const handleStartReplaceCharacter = async () => {
|
||||||
// 获取当前角色对应的视频片段
|
setIsLoadingShots(true);
|
||||||
fetchRoleShots(selectedRole?.id || '');
|
|
||||||
// 打开替换角色面板
|
|
||||||
setIsReplacePanelOpen(true);
|
setIsReplacePanelOpen(true);
|
||||||
|
// 获取当前角色对应的视频片段
|
||||||
|
await fetchRoleShots(selectedRole?.name || '');
|
||||||
|
// 打开替换角色面板
|
||||||
|
setIsLoadingShots(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleConfirmGotoReplace = () => {
|
const handleConfirmGotoReplace = () => {
|
||||||
@ -146,6 +150,7 @@ export function CharacterTabContent({
|
|||||||
// 处理替换确认逻辑
|
// 处理替换确认逻辑
|
||||||
console.log('Selected shots:', selectedShots);
|
console.log('Selected shots:', selectedShots);
|
||||||
console.log('Add to library:', addToLibrary);
|
console.log('Add to library:', addToLibrary);
|
||||||
|
applyRoleToSelectedShots(selectedRole || {} as RoleEntity);
|
||||||
setIsReplacePanelOpen(false);
|
setIsReplacePanelOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -185,9 +190,12 @@ export function CharacterTabContent({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOpenReplaceLibrary = () => {
|
const handleOpenReplaceLibrary = async () => {
|
||||||
|
setIsLoadingLibrary(true);
|
||||||
setIsReplaceLibraryOpen(true);
|
setIsReplaceLibraryOpen(true);
|
||||||
setShowAddToLibrary(true);
|
setShowAddToLibrary(true);
|
||||||
|
await fetchUserRoleLibrary();
|
||||||
|
setIsLoadingLibrary(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRegenerate = async () => {
|
const handleRegenerate = async () => {
|
||||||
@ -200,6 +208,7 @@ export function CharacterTabContent({
|
|||||||
// 然后调用重新生成角色
|
// 然后调用重新生成角色
|
||||||
await regenerateRole();
|
await regenerateRole();
|
||||||
setIsRegenerate(false);
|
setIsRegenerate(false);
|
||||||
|
handleStartReplaceCharacter();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUploadClick = () => {
|
const handleUploadClick = () => {
|
||||||
@ -384,8 +393,9 @@ export function CharacterTabContent({
|
|||||||
onClose={() => handleCloseReplacePanel()}
|
onClose={() => handleCloseReplacePanel()}
|
||||||
>
|
>
|
||||||
<ReplaceCharacterPanel
|
<ReplaceCharacterPanel
|
||||||
|
isLoading={isLoadingShots}
|
||||||
shots={shotSelectionList}
|
shots={shotSelectionList}
|
||||||
character={selectedRole}
|
role={selectedRole}
|
||||||
showAddToLibrary={showAddToLibrary}
|
showAddToLibrary={showAddToLibrary}
|
||||||
onClose={() => handleCloseReplacePanel()}
|
onClose={() => handleCloseReplacePanel()}
|
||||||
onConfirm={handleConfirmReplace}
|
onConfirm={handleConfirmReplace}
|
||||||
@ -394,6 +404,7 @@ export function CharacterTabContent({
|
|||||||
|
|
||||||
{/* 从角色库中选择角色 */}
|
{/* 从角色库中选择角色 */}
|
||||||
<CharacterLibrarySelector
|
<CharacterLibrarySelector
|
||||||
|
isLoading={isLoadingLibrary}
|
||||||
isReplaceLibraryOpen={isReplaceLibraryOpen}
|
isReplaceLibraryOpen={isReplaceLibraryOpen}
|
||||||
setIsReplaceLibraryOpen={setIsReplaceLibraryOpen}
|
setIsReplaceLibraryOpen={setIsReplaceLibraryOpen}
|
||||||
onSelect={handleSelectCharacter}
|
onSelect={handleSelectCharacter}
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import { ReplacePanel } from './replace-panel';
|
import { ReplacePanel } from './replace-panel';
|
||||||
import { Shot, Character } from '@/app/model/types';
|
import { Shot, Character } from '@/app/model/types';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
interface ReplaceCharacterPanelProps {
|
interface ReplaceCharacterPanelProps {
|
||||||
|
isLoading: boolean;
|
||||||
shots: any[];
|
shots: any[];
|
||||||
character: Character;
|
role: any;
|
||||||
showAddToLibrary?: boolean;
|
showAddToLibrary?: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onConfirm: (selectedShots: string[], addToLibrary: boolean) => void;
|
onConfirm: (selectedShots: string[], addToLibrary: boolean) => void;
|
||||||
@ -40,21 +42,24 @@ export const mockShots: Shot[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export function ReplaceCharacterPanel({
|
export function ReplaceCharacterPanel({
|
||||||
shots = [],
|
isLoading,
|
||||||
character,
|
shots,
|
||||||
|
role,
|
||||||
showAddToLibrary = true,
|
showAddToLibrary = true,
|
||||||
onClose,
|
onClose,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
}: ReplaceCharacterPanelProps) {
|
}: ReplaceCharacterPanelProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReplacePanel
|
<ReplacePanel
|
||||||
title="替换新形象"
|
title="替换新形象"
|
||||||
shots={shots}
|
shots={shots}
|
||||||
item={character}
|
item={role}
|
||||||
showAddToLibrary={showAddToLibrary}
|
showAddToLibrary={showAddToLibrary}
|
||||||
addToLibraryText="新形象同步添加至角色库"
|
addToLibraryText="新形象同步添加至角色库"
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onConfirm={onConfirm}
|
onConfirm={onConfirm}
|
||||||
|
isLoading={isLoading}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -4,6 +4,7 @@ import { Check, X, CircleAlert, ArrowLeft, ArrowRight } from 'lucide-react';
|
|||||||
import { cn } from '@/public/lib/utils';
|
import { cn } from '@/public/lib/utils';
|
||||||
|
|
||||||
interface ReplacePanelProps {
|
interface ReplacePanelProps {
|
||||||
|
isLoading: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
shots: any[];
|
shots: any[];
|
||||||
item: any;
|
item: any;
|
||||||
@ -14,6 +15,7 @@ interface ReplacePanelProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ReplacePanel({
|
export function ReplacePanel({
|
||||||
|
isLoading,
|
||||||
title,
|
title,
|
||||||
shots,
|
shots,
|
||||||
item,
|
item,
|
||||||
@ -56,11 +58,11 @@ export function ReplacePanel({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleShotToggle = (shotId: string) => {
|
const handleShotToggle = (shotId: string) => {
|
||||||
setSelectedShots(prev =>
|
// setSelectedShots(prev =>
|
||||||
prev.includes(shotId)
|
// prev.includes(shotId)
|
||||||
? prev.filter(id => id !== shotId)
|
// ? prev.filter(id => id !== shotId)
|
||||||
: [...prev, shotId]
|
// : [...prev, shotId]
|
||||||
);
|
// );
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectAllShots = (checked: boolean) => {
|
const handleSelectAllShots = (checked: boolean) => {
|
||||||
@ -104,6 +106,15 @@ export function ReplacePanel({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center w-full max-w-5xl text-white/50">
|
||||||
|
<div className="w-12 h-12 mb-4 animate-spin rounded-full border-b-2 border-blue-600" />
|
||||||
|
<p>Loading...</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2 w-full max-w-5xl">
|
<div className="space-y-2 w-full max-w-5xl">
|
||||||
{/* 标题 */}
|
{/* 标题 */}
|
||||||
@ -115,7 +126,7 @@ export function ReplacePanel({
|
|||||||
<CircleAlert className="w-4 h-4" />
|
<CircleAlert className="w-4 h-4" />
|
||||||
该内容出现在 <span className="text-blue-500">{shots.length}</span> 个分镜中,替换后将影响如下分镜
|
该内容出现在 <span className="text-blue-500">{shots.length}</span> 个分镜中,替换后将影响如下分镜
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
{/* <div className="flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={selectedShots.length === shots.length}
|
checked={selectedShots.length === shots.length}
|
||||||
@ -123,12 +134,12 @@ export function ReplacePanel({
|
|||||||
className="w-4 h-4 rounded border-white/20"
|
className="w-4 h-4 rounded border-white/20"
|
||||||
/>
|
/>
|
||||||
<label className="text-white/80">全选</label>
|
<label className="text-white/80">全选</label>
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 分镜展示区 */}
|
{/* 分镜展示区 */}
|
||||||
<div className="space-y-2 relative">
|
<div className="space-y-2 relative">
|
||||||
<div className="text-white/80 text-sm">选择需要替换的分镜:</div>
|
{/* <div className="text-white/80 text-sm">选择需要替换的分镜:</div> */}
|
||||||
<div className="relative flex gap-4 overflow-x-auto pb-4 hide-scrollbar h-64" ref={shotsRef}>
|
<div className="relative flex gap-4 overflow-x-auto pb-4 hide-scrollbar h-64" ref={shotsRef}>
|
||||||
{shots.map((shot) => (
|
{shots.map((shot) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user