统一创建入口:模板选择兼容H5、选中模板没有配置文本 不展示输入框

This commit is contained in:
北枳 2025-10-21 15:53:21 +08:00
parent 7ffb8b3297
commit ae807f33e9
3 changed files with 164 additions and 39 deletions

View File

@ -1,7 +1,8 @@
"use client"; "use client";
import { useEffect, useState } from 'react'; import { useEffect, useState, useRef } from 'react';
import { X } from 'lucide-react'; import { X, ChevronDown, ChevronUp } from 'lucide-react';
import { useDeviceType } from '@/hooks/useDeviceType';
interface TemplatePreviewModalProps { interface TemplatePreviewModalProps {
/** Control visibility */ /** Control visibility */
@ -23,6 +24,7 @@ interface TemplatePreviewModalProps {
/** /**
* Fullscreen, Tailwind-based template preview modal with video content. * Fullscreen, Tailwind-based template preview modal with video content.
* Overlays title/description if provided and supports an optional primary action. * Overlays title/description if provided and supports an optional primary action.
* Supports expand/collapse for long descriptions and H5 responsive design.
*/ */
export function TemplatePreviewModal({ export function TemplatePreviewModal({
open, open,
@ -31,27 +33,62 @@ export function TemplatePreviewModal({
title, title,
description, description,
onPrimaryAction, onPrimaryAction,
primaryLabel = 'Try it', primaryLabel = 'Try it Free',
}: TemplatePreviewModalProps) { }: TemplatePreviewModalProps) {
const [isReady, setIsReady] = useState(false); const [isReady, setIsReady] = useState(false);
const [isExpanded, setIsExpanded] = useState(false);
const [showExpandButton, setShowExpandButton] = useState(false);
const descriptionRef = useRef<HTMLDivElement>(null);
const { isMobile } = useDeviceType();
useEffect(() => { useEffect(() => {
if (!open) { if (!open) {
setIsReady(false); setIsReady(false);
setIsExpanded(false);
} }
}, [open]); }, [open]);
useEffect(() => {
if (isReady && description && descriptionRef.current) {
const element = descriptionRef.current;
const isOverflowing = element.scrollHeight > element.clientHeight;
setShowExpandButton(isOverflowing);
}
}, [isReady, description]);
if (!open || !videoUrl) return null; if (!open || !videoUrl) return null;
return ( return (
<>
<style jsx>{`
.description-scroll::-webkit-scrollbar {
width: 4px;
}
.description-scroll::-webkit-scrollbar-track {
background: transparent;
}
.description-scroll::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
}
.description-scroll::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.3);
}
`}</style>
<div <div
data-alt="template-preview-modal" data-alt="template-preview-modal"
className="fixed inset-0 z-50 bg-black/80 flex items-center justify-center p-4" className="fixed inset-0 z-50 bg-black/90 flex items-center justify-center"
onClick={onClose} onClick={onClose}
> >
<div <div
data-alt="template-preview-modal-content" data-alt="template-preview-modal-content"
className="relative w-[70vw] min-h-[40vw] rounded-lg overflow-hidden border border-white/30 bg-black shadow-2xl" className={`
relative rounded-lg overflow-hidden border border-white/30 bg-black shadow-2xl
${isMobile
? 'w-[95vw] max-h-[90vh] mx-2'
: 'w-[70vw] min-h-[40vw]'
}
`}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<video <video
@ -69,50 +106,121 @@ export function TemplatePreviewModal({
data-alt="template-preview-loading" data-alt="template-preview-loading"
className="absolute inset-0 flex items-center justify-center" className="absolute inset-0 flex items-center justify-center"
> >
<div className="h-10 w-10 rounded-full border-2 border-white/30 border-t-white/80 animate-spin" /> <div className={`rounded-full border-2 border-white/30 border-t-white/80 animate-spin ${isMobile ? 'h-8 w-8' : 'h-10 w-10'}`} />
</div> </div>
)} )}
{isReady && (title || description) && ( {isReady && title && (
<div <div
data-alt="template-preview-header" data-alt="template-preview-header"
className="absolute top-0 left-0 right-0 bg-gradient-to-b from-black/90 via-black/80 to-transparent px-6 py-8 text-left" className={`
absolute top-0 left-0 right-0
bg-gradient-to-b from-black/90 via-black/80 to-transparent
text-left
${isMobile ? 'px-3 py-2' : 'px-6 py-8'}
`}
> >
{title && ( <div className={`font-bold text-white/90 line-clamp-2 ${isMobile ? 'text-base' : 'text-2xl'}`}>
<div className="text-bold text-2xl text-white/90 line-clamp-2 mb-2">{title}</div> {title}
)} </div>
</div> </div>
)} )}
{isReady && onPrimaryAction && ( {isReady && (description || onPrimaryAction) && (
<div <div
data-alt="template-preview-footer" data-alt="template-preview-footer"
className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90 via-black/80 to-transparent px-8 py-6 text-center" className={`
absolute bottom-0 left-0 right-0
bg-gradient-to-t from-black/95 via-black/85 to-transparent
text-center
${isMobile ? 'px-3 py-2' : 'px-8 py-6'}
`}
> >
{description && ( {description && (
<div className="text-base text-white/80 line-clamp-2 text-left">{description}</div> <div className={`text-left ${isMobile ? 'mb-2' : 'mb-3'}`}>
)} <div
ref={descriptionRef}
className={`
text-white/80 transition-all duration-300
${isMobile ? 'text-sm' : 'text-base'}
${isExpanded
? `${isMobile ? 'max-h-20' : 'max-h-40'} overflow-y-auto pr-2 description-scroll`
: 'line-clamp-2'
}
`}
style={isExpanded ? {
scrollbarWidth: 'thin',
scrollbarColor: 'rgba(255, 255, 255, 0.2) transparent'
} : undefined}
>
{description}
</div>
{showExpandButton && (
<button <button
type="button" type="button"
className="inline-flex items-center justify-center rounded-full border border-white/30 bg-white/10 mt-3 px-6 py-2 text-white hover:border-white hover:bg-white hover:text-slate-900" onClick={() => setIsExpanded(!isExpanded)}
className={`
inline-flex items-center gap-1 mt-1
text-white/60 hover:text-white/90
transition-colors
${isMobile ? 'text-xs' : 'text-sm'}
`}
data-alt="description-toggle"
>
{isExpanded ? (
<>
<span>Collapse</span>
<ChevronUp className={isMobile ? 'h-3 w-3' : 'h-4 w-4'} />
</>
) : (
<>
<span>Expand</span>
<ChevronDown className={isMobile ? 'h-3 w-3' : 'h-4 w-4'} />
</>
)}
</button>
)}
</div>
)}
{onPrimaryAction && (
<button
type="button"
className={`
inline-flex items-center justify-center
rounded-full border border-white/30 bg-white/10
text-white hover:border-white hover:bg-white hover:text-slate-900
transition-all duration-200
${isMobile ? 'px-5 py-2 text-sm' : 'px-6 py-2.5 text-base'}
`}
onClick={onPrimaryAction} onClick={onPrimaryAction}
data-alt="template-preview-primary" data-alt="template-preview-primary"
> >
{primaryLabel} {primaryLabel}
</button> </button>
)}
</div> </div>
)} )}
<button <button
type="button" type="button"
data-alt="template-preview-modal-close" data-alt="template-preview-modal-close"
className="absolute top-3 right-3 inline-flex items-center justify-center rounded-full border border-white/30 bg-white/10 p-2 text-white hover:border-white hover:bg-white hover:text-slate-900" className={`
absolute inline-flex items-center justify-center
rounded-full border border-white/30 bg-white/10
text-white hover:border-white hover:bg-white hover:text-slate-900
transition-all duration-200
${isMobile
? 'top-2 right-2 p-1.5'
: 'top-3 right-3 p-2'
}
`}
onClick={onClose} onClick={onClose}
> >
<X className="h-4 w-4" /> <X className={isMobile ? 'h-3.5 w-3.5' : 'h-4 w-4'} />
</button> </button>
</div> </div>
</div> </div>
</>
); );
} }

View File

@ -63,6 +63,21 @@ export default function VideoCreationForm() {
void getTemplateStoryList(); void getTemplateStoryList();
}, [getTemplateStoryList]); }, [getTemplateStoryList]);
/** Clear inputText when currentTemplate changes */
useEffect(() => {
if (currentTemplate) {
setInputText('');
}
}, [currentTemplate]);
/** Determine if input textarea should be shown based on selected template's freeInput */
const shouldShowInput = useMemo(() => {
if (!isTemplateSelected) {
return true;
}
return isTemplateSelected.freeInput && isTemplateSelected.freeInput.length > 0;
}, [isTemplateSelected]);
const characterInputRef = useRef<HTMLInputElement>(null); const characterInputRef = useRef<HTMLInputElement>(null);
const sceneInputRef = useRef<HTMLInputElement>(null); const sceneInputRef = useRef<HTMLInputElement>(null);
const propInputRef = useRef<HTMLInputElement>(null); const propInputRef = useRef<HTMLInputElement>(null);
@ -350,6 +365,7 @@ export default function VideoCreationForm() {
)} )}
{/* Text Input Area - Middle */} {/* Text Input Area - Middle */}
{shouldShowInput && (
<div data-alt="text-input-wrapper" className="flex-1 px-4 py-4"> <div data-alt="text-input-wrapper" className="flex-1 px-4 py-4">
<textarea <textarea
data-alt="main-text-input" data-alt="main-text-input"
@ -359,6 +375,7 @@ export default function VideoCreationForm() {
onChange={(e) => setInputText(e.target.value)} onChange={(e) => setInputText(e.target.value)}
/> />
</div> </div>
)}
{/* Control Panel - Bottom */} {/* Control Panel - Bottom */}
<div data-alt="control-panel" className="px-4 py-4 flex items-center justify-between gap-2"> <div data-alt="control-panel" className="px-4 py-4 flex items-center justify-between gap-2">

View File

@ -8,7 +8,7 @@ export default function CreateVideo() {
<div className='p-1 text-center text-2xl font-bold tracking-tight sm:text-3xl'>Transform any idea into a compelling video</div> <div className='p-1 text-center text-2xl font-bold tracking-tight sm:text-3xl'>Transform any idea into a compelling video</div>
<div className='mx-auto w-full max-w-[600px] px-3 py-2 text-center text-xs text-gray-400 sm:px-16 sm:text-sm'>Generate professional videos from simple prompts. Browse community creations for inspiration, or start fresh with your own vision</div> <div className='mx-auto w-full max-w-[600px] px-3 py-2 text-center text-xs text-gray-400 sm:px-16 sm:text-sm'>Generate professional videos from simple prompts. Browse community creations for inspiration, or start fresh with your own vision</div>
<div className='py-2'> <div className='py-2'>
<div className='space-y-4 mx-auto w-full max-w-[900px] px-3 sm:px-16'> <div className='space-y-4 mx-auto w-full max-w-[900px] px-3 sm:px-16 bg-[radial-gradient(ellipse_at_center,rgba(106,244,249,0.28)_0%,rgba(106,244,249,0.14)_35%,transparent_70%)]'>
<VideoCreationForm /> <VideoCreationForm />
</div> </div>
</div> </div>