新增: 分享页规则; H5适配

This commit is contained in:
moux1024 2025-09-24 21:25:22 +08:00
parent 8dbdee5b6c
commit b98c34c39f
5 changed files with 166 additions and 47 deletions

View File

@ -197,36 +197,7 @@ export default function SharePage(): JSX.Element {
<p data-alt="subtitle" className="mt-1 text-sm text-white/60">Invite friends to join and earn rewards.</p> <p data-alt="subtitle" className="mt-1 text-sm text-white/60">Invite friends to join and earn rewards.</p>
</div> </div>
</header> </header>
{/* Section 1: My Invitation Link */}
{/* Section 1: Invite Flow */}
<section data-alt="invite-flow" className="mb-8 rounded-lg border border-white/20 bg-black p-6 shadow-sm">
<h2 data-alt="section-title" className="text-lg font-medium text-white">Invitation Flow</h2>
<ol data-alt="steps" className="mt-4 grid gap-4 sm:grid-cols-3">
<li data-alt="step" className="rounded-md border border-white/20 p-4">
<div data-alt="step-header" className="flex items-center justify-between">
<span className="text-sm font-medium text-custom-blue/50">Step 1</span>
<span className="rounded bg-white/10 px-2 py-0.5 text-xs text-white bg-custom-purple/50">Share</span>
</div>
<p data-alt="step-desc" className="mt-2 text-sm text-white/70">Copy your invitation link and share it with friends.</p>
</li>
<li data-alt="step" className="rounded-md border border-white/20 p-4">
<div data-alt="step-header" className="flex items-center justify-between">
<span className="text-sm font-medium text-custom-blue/50">Step 2</span>
<span className="rounded bg-white/10 px-2 py-0.5 text-xs text-white bg-custom-purple/50">Register</span>
</div>
<p data-alt="step-desc" className="mt-2 text-sm text-white/70">Friends click the link and register directly.</p>
</li>
<li data-alt="step" className="rounded-md border border-white/20 p-4">
<div data-alt="step-header" className="flex items-center justify-between">
<span className="text-sm font-medium text-custom-blue/50">Step 3</span>
<span className="rounded bg-white/10 px-2 py-0.5 text-xs text-white bg-custom-purple/50">Reward</span>
</div>
<p data-alt="step-desc" className="mt-2 text-sm text-white/70">You both receive rewards after your friend activates their account.</p>
</li>
</ol>
</section>
{/* Section 2: My Invitation Link */}
<section data-alt="my-invite-link" className="mb-8 rounded-lg border border-white/20 bg-black p-6 shadow-sm"> <section data-alt="my-invite-link" className="mb-8 rounded-lg border border-white/20 bg-black p-6 shadow-sm">
<div data-alt="link-panel" className="mt-4 grid gap-6 sm:grid-cols-4"> <div data-alt="link-panel" className="mt-4 grid gap-6 sm:grid-cols-4">
<div data-alt="link-box" className="sm:col-span-2"> <div data-alt="link-box" className="sm:col-span-2">
@ -277,6 +248,74 @@ export default function SharePage(): JSX.Element {
</div> </div>
</section> </section>
{/* Section 2: Invite Flow - Two Columns (Left: Steps, Right: Rules) */}
<section data-alt="invite-flow" className="mb-8 rounded-lg border border-white/20 bg-black p-6 shadow-sm">
<div data-alt="two-col-wrapper" className="mt-4 grid grid-cols-1 gap-6 md:grid-cols-[30%_1fr]">
{/* Left: Steps */}
<div data-alt="steps-col" className="space-y-4">
<h2 data-alt="section-title" className="text-lg font-medium text-white">Invitation Flow</h2>
<ol data-alt="steps" className="space-y-4">
<li data-alt="step" className="rounded-md p-4">
<div data-alt="step-header" className="flex items-center justify-between">
<span className="text-sm font-medium text-custom-blue/50">Step 1</span>
<span className="rounded bg-white/10 px-2 py-0.5 text-xs text-white bg-custom-purple/50">Share</span>
</div>
<p data-alt="step-desc" className="mt-2 text-sm text-white/70">Copy your invitation link and share it with friends.</p>
</li>
<li data-alt="step" className="rounded-md p-4">
<div data-alt="step-header" className="flex items-center justify-between">
<span className="text-sm font-medium text-custom-blue/50">Step 2</span>
<span className="rounded bg-white/10 px-2 py-0.5 text-xs text-white bg-custom-purple/50">Register</span>
</div>
<p data-alt="step-desc" className="mt-2 text-sm text-white/70">Friends click the link and register directly.</p>
</li>
<li data-alt="step" className="rounded-md p-4">
<div data-alt="step-header" className="flex items-center justify-between">
<span className="text-sm font-medium text-custom-blue/50">Step 3</span>
<span className="rounded bg-white/10 px-2 py-0.5 text-xs text-white bg-custom-purple/50">Reward</span>
</div>
<p data-alt="step-desc" className="mt-2 text-sm text-white/70">You both receive rewards after your friend activates their account.</p>
</li>
</ol>
</div>
{/* Right: Rules */}
<div data-alt="rules-col" className="rounded-md">
<h2 data-alt="section-title" className="text-lg font-medium text-white mb-4">MovieFlow Credits Rewards Program</h2>
<div className='p-4 space-y-4'>
<p className="text-sm">Welcome to MovieFlow! Our Credits Program is designed to reward your growth and contributions. Credits can be redeemed for premium templates, effects, and membership time.</p>
<div className="content">
<h2 className="text-medium font-medium text-white">How to Earn Credits?</h2>
<div className="reward-section welcome">
<h3 className="text-medium font-medium text-white">Welcome Bonus</h3>
<p className='text-sm'>All <strong>new users</strong> receive a bonus of <span className="credit-amount">500 credits</span> upon successful registration!</p>
</div>
<div className="reward-section invite">
<h3 className="text-medium font-medium text-white">Invite & Earn</h3>
<p className='text-sm'>Invite friends to join using your unique referral link. Both you and your friend will get <span className="credit-amount">500 credits</span> once they successfully sign up.</p>
<div className="highlight">
<p className='text-sm'>If your invited friend completes their first purchase, you will receive a <strong>bonus equal to 20% of the credits</strong> they earn from that purchase.</p>
</div>
</div>
<div className="reward-section login">
<h3 className="text-medium font-medium text-white">Daily Login</h3>
<p className='text-sm'>Starting the day after registration, log in daily to claim <span className="credit-amount">100 credits</span>.</p>
<p className='text-sm'>This reward can be claimed for <strong>7 consecutive days</strong>.</p>
<div className="note">
<p className='text-sm'><strong>Please note:</strong> Daily login credits will <strong>reset</strong> automatically on the 8th day, so remember to use them in time!</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
{/* Section 3: Invite Records */} {/* Section 3: Invite Records */}
<section data-alt="invite-records" className="rounded-lg border border-white/20 bg-black p-6 shadow-sm"> <section data-alt="invite-records" className="rounded-lg border border-white/20 bg-black p-6 shadow-sm">
<div data-alt="section-header" className="mb-4 flex items-center justify-between"> <div data-alt="section-header" className="mb-4 flex items-center justify-between">

View File

@ -116,7 +116,7 @@ export default function SmartChatBox({
const textBlock = last.blocks.find(b => (b as any).type === 'text') as any; const textBlock = last.blocks.find(b => (b as any).type === 'text') as any;
const text = textBlock?.text || ''; const text = textBlock?.text || '';
if (text && onNewMessage) { if (text && onNewMessage) {
const snippet = text.slice(0, 20); const snippet = text.slice(0, 40);
onNewMessage(snippet); onNewMessage(snippet);
} }
} }
@ -199,7 +199,7 @@ export default function SmartChatBox({
)} )}
{/* Messages grouped by date */} {/* Messages grouped by date */}
<div className="space-y-3"> <div className="space-y-3 pb-28">
{groupedMessages.map((group) => ( {groupedMessages.map((group) => (
<React.Fragment key={group.date}> <React.Fragment key={group.date}>
<DateDivider timestamp={group.date} /> <DateDivider timestamp={group.date} />

View File

@ -624,7 +624,7 @@ Please process this video editing request.`;
{isMobile ? ( {isMobile ? (
<div className="relative"> <div className="relative">
{(!isSmartChatBoxOpen && chatTip) && ( {(!isSmartChatBoxOpen && chatTip) && (
<div className="absolute -top-8 right-0 bg-black/80 text-white text-xs px-2 py-1 rounded-md whitespace-nowrap"> <div className="absolute -top-8 right-0 bg-black/80 text-white text-xs px-2 py-1 rounded-md whitespace-nowrap bg-custom-blue/30">
{chatTip} {chatTip}
</div> </div>
)} )}
@ -640,7 +640,7 @@ Please process this video editing request.`;
setChatTip(null); setChatTip(null);
setHasUnread(false); setHasUnread(false);
}} }}
className="backdrop-blur-lg bg-custom-purple/80 border-transparent hover:opacity-90" className="backdrop-blur-lg bg-custom-purple/80 border-transparent hover:bg-custom-purple/80"
/> />
</div> </div>
) : ( ) : (
@ -698,8 +698,8 @@ Please process this video editing request.`;
if (!isSmartChatBoxOpen && snippet) { if (!isSmartChatBoxOpen && snippet) {
setChatTip(snippet); setChatTip(snippet);
setHasUnread(true); setHasUnread(true);
// 3秒后自动消失 // 5秒后自动消失
setTimeout(() => setChatTip(null), 3000); setTimeout(() => setChatTip(null), 5000);
} }
}} }}
onClearPreview={() => { onClearPreview={() => {

View File

@ -3,7 +3,7 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'; import React, { useEffect, useMemo, useRef, useState } from 'react';
import { Carousel } from 'antd'; import { Carousel } from 'antd';
import type { CarouselRef } from 'antd/es/carousel'; import type { CarouselRef } from 'antd/es/carousel';
import { Play, Pause, Scissors, MessageCircleMore, Download, ArrowDownWideNarrow, RotateCcw, Navigation } from 'lucide-react'; import { Play, Pause, FeatherIcon, MessageCircleMore, Download, ArrowDownWideNarrow, RotateCcw, Navigation } from 'lucide-react';
import { TaskObject } from '@/api/DTO/movieEdit'; import { TaskObject } from '@/api/DTO/movieEdit';
import { ScriptRenderer } from '@/components/script-renderer/ScriptRenderer'; import { ScriptRenderer } from '@/components/script-renderer/ScriptRenderer';
import ScriptLoading from './script-loading'; import ScriptLoading from './script-loading';
@ -395,15 +395,15 @@ export function H5MediaViewer({
{stage === 'final_video' && videoUrls.length > 0 && renderVideoSlides()} {stage === 'final_video' && videoUrls.length > 0 && renderVideoSlides()}
{stage === 'video' && videoUrls.length > 0 && renderVideoSlides()} {stage === 'video' && videoUrls.length > 0 && renderVideoSlides()}
{(stage === 'scene' || stage === 'character') && imageUrls.length > 0 && renderImageSlides()} {(stage === 'scene' || stage === 'character') && imageUrls.length > 0 && renderImageSlides()}
{/* 全局固定操作区(右角)视频暂停时展示 */} {/* 全局固定操作区(右角)视频暂停时展示 */}
{(stage === 'video' || stage === 'final_video') && !isPlaying && ( {(stage === 'video' || stage === 'final_video') && !isPlaying && (
<div data-alt="global-video-actions" className="absolute top-2 right-6 z-[60] flex items-center gap-2"> <div data-alt="global-video-actions" className="absolute bottom-0 right-4 z-[60] flex flex-col items-center gap-2">
{stage === 'video' && ( {stage === 'video' && (
<> <>
<GlassIconButton <GlassIconButton
data-alt="edit-with-chat-button" data-alt="edit-with-chat-button"
className="w-8 h-8 bg-gradient-to-br from-blue-500/80 to-blue-600/80 backdrop-blur-xl border border-blue-400/30 rounded-full flex items-center justify-center hover:from-blue-400/80 hover:to-blue-500/80 transition-all" className="w-8 h-8 bg-custom-purple backdrop-blur-xl rounded-full flex items-center justify-center transition-all"
icon={MessageCircleMore} icon={FeatherIcon}
size="sm" size="sm"
aria-label="edit-with-chat" aria-label="edit-with-chat"
onClick={() => { onClick={() => {
@ -416,7 +416,7 @@ export function H5MediaViewer({
/> />
<GlassIconButton <GlassIconButton
data-alt="download-all-button" data-alt="download-all-button"
className="w-8 h-8 bg-gradient-to-br from-purple-500/80 to-purple-600/80 backdrop-blur-xl border border-purple-400/30 rounded-full flex items-center justify-center hover:from-purple-400/80 hover:to-purple-500/80 transition-all" className="w-8 h-8 bg-gradient-to-br from-purple-500/80 to-purple-600/80 backdrop-blur-xl rounded-full flex items-center justify-center hover:from-purple-400/80 hover:to-purple-500/80 transition-all"
icon={ArrowDownWideNarrow} icon={ArrowDownWideNarrow}
size="sm" size="sm"
aria-label="download-all" aria-label="download-all"
@ -430,7 +430,7 @@ export function H5MediaViewer({
return status === 1 ? ( return status === 1 ? (
<GlassIconButton <GlassIconButton
data-alt="download-current-button" data-alt="download-current-button"
className="w-8 h-8 bg-gradient-to-br from-amber-500/80 to-yellow-600/80 backdrop-blur-xl border border-amber-400/30 rounded-full flex items-center justify-center hover:from-amber-400/80 hover:to-yellow-500/80 transition-all" className="w-8 h-8 bg-gradient-to-br from-purple-600/80 to-purple-700/80 backdrop-blur-xl rounded-full flex items-center justify-center transition-all"
icon={Download} icon={Download}
size="sm" size="sm"
aria-label="download-current" aria-label="download-current"

View File

@ -2,7 +2,7 @@
import React, { useEffect, useMemo, useState } from 'react' import React, { useEffect, useMemo, useState } from 'react'
import { TaskObject } from '@/api/DTO/movieEdit' import { TaskObject } from '@/api/DTO/movieEdit'
import { motion } from 'framer-motion' import { motion, AnimatePresence } from 'framer-motion'
import { Heart, Camera, Film, Scissors, type LucideIcon } from 'lucide-react' import { Heart, Camera, Film, Scissors, type LucideIcon } from 'lucide-react'
interface H5TaskInfoProps { interface H5TaskInfoProps {
@ -94,6 +94,34 @@ const H5TaskInfo: React.FC<H5TaskInfoProps> = ({
return null return null
}, [selectedView, taskObject, displayCurrent, total]) }, [selectedView, taskObject, displayCurrent, total])
/** 阶段图标H5 精简版) */
const StageIcon = useMemo(() => {
const Icon = stageIconMap[currentStage].icon
return (
<motion.div
data-alt="stage-icon"
className="relative"
initial={{ opacity: 0, x: -8, scale: 0.9 }}
animate={{ opacity: 1, x: 0, scale: 1 }}
transition={{ duration: 0.25 }}
>
<motion.div
className="rounded-full p-1"
animate={{
rotate: [0, 360],
scale: [1, 1.1, 1],
transition: {
rotate: { duration: 3, repeat: Infinity, ease: 'linear' },
scale: { duration: 1.6, repeat: Infinity, ease: 'easeInOut' }
}
}}
>
<Icon className="w-4 h-4" style={{ color: stageColor }} />
</motion.div>
</motion.div>
)
}, [currentStage, stageColor])
return ( return (
<div <div
data-alt="h5-header" data-alt="h5-header"
@ -101,9 +129,10 @@ const H5TaskInfo: React.FC<H5TaskInfoProps> = ({
> >
<div <div
data-alt="h5-header-bar" data-alt="h5-header-bar"
className="flex items-start justify-between" className="flex items-start gap-3"
> >
<div data-alt="title-area" className="flex flex-col min-w-0 bg-gradient-to-b from-slate-900/80 via-slate-900/40 to-transparent backdrop-blur-sm rounded-lg py-4"> {/* 左侧标题区域 */}
<div data-alt="title-area" className="flex-1 min-w-0 bg-gradient-to-b from-slate-900/80 via-slate-900/40 to-transparent backdrop-blur-sm rounded-lg py-4 px-4">
<h1 <h1
data-alt="title" data-alt="title"
className="text-white text-lg font-bold" className="text-white text-lg font-bold"
@ -116,7 +145,58 @@ const H5TaskInfo: React.FC<H5TaskInfoProps> = ({
{subtitle} {subtitle}
</span> </span>
)} )}
</div>
{/* 右侧状态区域 */}
<div data-alt="status-area" className="flex-shrink-0 bg-gradient-to-b from-slate-900/80 via-slate-900/40 to-transparent backdrop-blur-sm rounded-lg py-4 px-4 max-w-[200px]">
<AnimatePresence mode="popLayout">
{currentLoadingText && currentLoadingText !== 'Task completed' && (
<motion.div
key={currentLoadingText}
data-alt="status-line"
className="flex flex-col gap-2"
initial={{ opacity: 0, x: 10 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 10 }}
transition={{ duration: 0.25 }}
>
<div className="flex items-center justify-center">
{StageIcon}
</div>
<div className="relative text-center">
{/* 背景流光 */}
<motion.div
className="absolute inset-0 text-transparent bg-clip-text bg-gradient-to-r from-blue-400 via-cyan-400 to-purple-400 blur-[1px]"
animate={{
backgroundPosition: ['0% 50%', '100% 50%', '0% 50%'],
transition: { duration: 2, repeat: Infinity, ease: 'linear' }
}}
style={{ backgroundSize: '200% 200%' }}
>
<span className="text-xs leading-tight break-words">{currentLoadingText}</span>
</motion.div>
{/* 主文字轻微律动 */}
<motion.div
className="relative z-10"
animate={{ scale: [1, 1.02, 1] }}
transition={{ duration: 1.5, repeat: Infinity, ease: 'easeInOut' }}
>
</motion.div>
{/* 底部装饰线 */}
<motion.div
className="absolute -bottom-0.5 left-1/2 transform -translate-x-1/2 h-0.5 w-8"
style={{
background: `linear-gradient(to right, ${stageColor}, rgb(34 211 238), rgb(168 85 247))`
}}
animate={{ width: ['0%', '100%', '0%'] }}
transition={{ duration: 2, repeat: Infinity, ease: 'easeInOut' }}
/>
</div>
</motion.div>
)}
</AnimatePresence>
</div> </div>
</div> </div>
</div> </div>