loading动画以及错误展示

This commit is contained in:
北枳 2025-10-13 20:19:49 +08:00
parent d16970c45b
commit 568cb65cf3
9 changed files with 518 additions and 31 deletions

View File

@ -1,5 +1,6 @@
'use client' 'use client'
import './globals.css'; import './globals.css';
import '@/iconfont/iconfont.css';
import { createContext, useContext, useEffect, useState } from 'react'; import { createContext, useContext, useEffect, useState } from 'react';
import { Providers } from '@/components/providers'; import { Providers } from '@/components/providers';
import { ConfigProvider, theme } from 'antd'; import { ConfigProvider, theme } from 'antd';

View File

@ -14,6 +14,7 @@ import { Drawer } from 'antd';
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
import error_image from '@/public/assets/error.webp'; import error_image from '@/public/assets/error.webp';
import { showDownloadOptionsModal } from './download-options-modal'; import { showDownloadOptionsModal } from './download-options-modal';
import RenderLoading from './RenderLoading';
interface H5MediaViewerProps { interface H5MediaViewerProps {
/** 任务对象,包含各阶段数据 */ /** 任务对象,包含各阶段数据 */
@ -150,9 +151,10 @@ export function H5MediaViewer({
return [...roles, ...scenes].map(item => ({ return [...roles, ...scenes].map(item => ({
url: item?.url as string | undefined, url: item?.url as string | undefined,
status: item?.status as number | undefined, status: item?.status as number | undefined,
type: item?.type as string | undefined,
})); }));
} }
return [] as Array<{ url?: string; status?: number }>; return [] as Array<{ url?: string; status?: number; type?: string }>;
}, [stage, taskObject.roles?.data, taskObject.scenes?.data]); }, [stage, taskObject.roles?.data, taskObject.scenes?.data]);
// 占位,避免未使用警告 // 占位,避免未使用警告
@ -266,18 +268,19 @@ export function H5MediaViewer({
<div className="w-full aspect-auto min-h-[200px] flex items-center justify-center bg-black/10 relative" data-alt="video-status" style={{ <div className="w-full aspect-auto min-h-[200px] flex items-center justify-center bg-black/10 relative" data-alt="video-status" style={{
height: videoWrapperHeight, height: videoWrapperHeight,
}}> }}>
{status === 0 && ( {(status === 0 || status === 2) && (
<span className="text-blue-500 text-base">Generating...</span> <RenderLoading
recordSize="120px"
loadingText={status === 0 ? 'Generating video...' : 'Generate failed'}
isFailed={status === 2}
/>
)} )}
{status === 2 && ( {/* {status === 2 && (
<div className="flex flex-col items-center justify-center gap-1"> <div className="flex flex-col items-center justify-center gap-1">
<img src={error_image.src} alt="error" className="w-12 h-12" /> <img src={error_image.src} alt="error" className="w-12 h-12" />
<span className="text-[#fa485f] text-base">Generate failed</span> <span className="text-[#fa485f] text-base">Generate failed</span>
</div> </div>
)} )} */}
{status !== 0 && status !== 2 && (
<span className="text-white/70 text-base">Pending</span>
)}
</div> </div>
)} )}
</div> </div>
@ -315,15 +318,19 @@ export function H5MediaViewer({
<div className="w-full aspect-auto min-h-[200px] flex items-center justify-center bg-black/10 relative" data-alt="image-status" style={{ <div className="w-full aspect-auto min-h-[200px] flex items-center justify-center bg-black/10 relative" data-alt="image-status" style={{
height: imageWrapperHeight, height: imageWrapperHeight,
}}> }}>
{status === 0 && ( {(status === 0 || status === 2) && (
<span className="text-blue-500 text-base">Generating...</span> <RenderLoading
recordSize="120px"
loadingText={status === 0 ? (item.type === 'role' ? 'Generating role...' : 'Generating scene...') : 'Generate failed'}
isFailed={status === 2}
/>
)} )}
{status === 2 && ( {/* {status === 2 && (
<div className="flex flex-col items-center justify-center gap-1"> <div className="flex flex-col items-center justify-center gap-1">
<img src={error_image.src} alt="error" className="w-12 h-12" /> <img src={error_image.src} alt="error" className="w-12 h-12" />
<span className="text-[#fa485f] text-base">Generate failed</span> <span className="text-[#fa485f] text-base">Generate failed</span>
</div> </div>
)} )} */}
</div> </div>
)} )}
</div> </div>
@ -544,8 +551,9 @@ export function H5MediaViewer({
</div> </div>
)} )}
<style jsx global>{` <style jsx global>{`
[data-alt='carousel-wrapper'] .slick-slide { display: flex !important;justify-content: center; } [data-alt='carousel-wrapper'] .slick-slide { width: 100%;display: flex !important;justify-content: center; }
.slick-slider { height: 100% !important; } .slick-slider { height: 100% !important; }
.slick-current > div { width: 100% !important; }
.ant-carousel { height: 100% !important; } .ant-carousel { height: 100% !important; }
.slick-list { width: 100%;height: 100% !important;max-height: 100%; } .slick-list { width: 100%;height: 100% !important;max-height: 100%; }
.slick-track { display: flex !important; align-items: center;height: 100% !important; } .slick-track { display: flex !important; align-items: center;height: 100% !important; }

View File

@ -0,0 +1,448 @@
import React from 'react';
/** Props for RenderLoading component */
interface RenderLoadingProps {
/** Size of the vinyl record */
recordSize?: string;
/** Apple silver light color for metallic effect */
appleSilverLight?: string;
/** Apple silver main color */
appleSilverMain?: string;
/** Apple silver dark color for shadows */
appleSilverDark?: string;
/** Core gradient start color (cyan) */
coreGradientStart?: string;
/** Core gradient middle color (purple) */
coreGradientMid?: string;
/** Core gradient end color (magenta) */
coreGradientEnd?: string;
/** Core glow color effect */
coreGlowColor?: string;
/** Scanner light color */
scannerColor?: string;
/** Record background color */
recordBg?: string;
/** Overall background color */
backgroundColor?: string;
/** Loading text */
loadingText?: string;
/** Failed state trigger */
isFailed?: boolean;
}
/**
* Vinyl record loading animation component with pulsing core and tonearm
* @param props - Component configuration props
* @returns React component with animated vinyl record loader
*/
const RenderLoading: React.FC<RenderLoadingProps> = ({
recordSize = '250px',
appleSilverLight = '#E5E5EA',
appleSilverMain = '#D1D1D6',
appleSilverDark = '#8A8A8E',
coreGradientStart = '#00E4E4',
coreGradientMid = '#8A2BE2',
coreGradientEnd = '#FF00FF',
coreGlowColor = 'rgba(138, 43, 226, 0.9)',
scannerColor = 'rgba(0, 228, 228, 0.35)',
recordBg = '#1A1A1A',
backgroundColor = '#000',
loadingText = 'Generating...',
isFailed = false,
}) => {
/** Dead metal color for failed state */
const deadMetalColor = '#3A3A3A';
const styles = `
@keyframes rotateRecord {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes rotateScanner {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes placeTonearm {
from { transform: rotate(-30deg); }
to { transform: rotate(0deg); }
}
@keyframes bobTonearm {
0%, 100% {
transform: rotate(0deg) translateY(0);
}
25% {
transform: rotate(0.5deg) translateY(2px);
}
75% {
transform: rotate(-0.5deg) translateY(-2px);
}
}
@keyframes retractTonearm {
from { transform: rotate(0deg); }
to { transform: rotate(-30deg); }
}
@keyframes breakTonearm {
0% {
transform: rotate(0deg);
animation-timing-function: cubic-bezier(0.3, 0, 0.8, 0.7); /* 加速下坠 */
}
60% {
transform: rotate(-55deg); /* 过冲到最大角度 */
animation-timing-function: cubic-bezier(0.1, 0.3, 0.4, 1.5); /* 回弹 */
}
100% {
transform: rotate(-90deg); /* 稳定在最终角度 */
}
}
@keyframes pulse {
0%, 100% {
transform: scale(1);
box-shadow: 0 0 15px 6px ${coreGlowColor},
inset 0 0 4px 1px rgba(255,255,255,0.4);
opacity: 0.9;
}
50% {
transform: scale(1.4);
box-shadow: 0 0 35px 15px ${coreGlowColor},
inset 0 0 8px 3px rgba(255,255,255,0.7);
opacity: 1;
}
}
`;
return (
<>
<style>{styles}</style>
<div
data-alt="vinyl-loading-container"
style={{
margin: 0,
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
gap: '20px',
justifyContent: 'center',
alignItems: 'center',
backgroundColor,
fontFamily: 'sans-serif',
overflow: 'hidden',
}}
>
<div
data-alt="loading-wrapper"
style={{
position: 'relative',
width: recordSize,
height: recordSize,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
{/* Vinyl Record Container */}
<div
data-alt="vinyl-record"
style={{
width: '100%',
height: '100%',
position: 'relative',
animation: isFailed ? 'none' : 'rotateRecord 4s linear infinite',
transformStyle: 'preserve-3d',
}}
>
{/* Record Half - Top */}
<div
data-alt="record-half-top"
style={{
position: 'absolute',
width: '100%',
height: '100%',
backgroundColor: recordBg,
borderRadius: '50%',
boxShadow: `0 15px 35px rgba(0, 0, 0, 0.7),
inset 0 0 20px rgba(0,0,0,1),
inset 0 0 2px 1px rgba(255, 255, 255, 0.1)`,
overflow: 'hidden',
clipPath: isFailed
? 'polygon(0 0, 100% 0, 100% 50%, 85% 51%, 70% 49%, 55% 52%, 40% 50%, 25% 51.5%, 10% 49.5%, 0 50%)'
: 'polygon(0 0, 100% 0, 100% 50%, 0 50%)',
transition: 'transform 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55), clip-path 0.3s ease',
transform: isFailed ? 'translateY(-10px) rotate(-4deg)' : 'none',
}}
>
{/* Record grooves effect */}
<div
data-alt="record-grooves-top"
style={{
content: '""',
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
borderRadius: '50%',
backgroundImage: `repeating-radial-gradient(
circle at center,
rgba(255, 255, 255, 0.1) 0,
rgba(255, 255, 255, 0.15) 1px,
transparent 1px,
transparent 7px
)`,
mixBlendMode: 'overlay',
zIndex: 1,
}}
/>
{/* Scanner light */}
<div
data-alt="scanner-light-top"
style={{
position: 'absolute',
width: '100%',
height: '100%',
borderRadius: '50%',
background: `conic-gradient(
from 90deg,
transparent 0%,
${scannerColor} 10%,
transparent 25%
)`,
animation: isFailed ? 'none' : 'rotateScanner 3s linear infinite',
zIndex: 2,
}}
/>
{/* Center label */}
<div
data-alt="center-label-top"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
margin: 'auto',
width: '38%',
height: '38%',
background: '#111',
borderRadius: '50%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
boxShadow: 'inset 0 0 20px rgba(0, 0, 0, 1)',
zIndex: 3,
}}
>
{/* Pulsing core */}
<div
data-alt="pulsing-core-top"
style={{
width: '12%',
height: '12%',
background: isFailed
? appleSilverDark
: `radial-gradient(circle at center, ${coreGradientStart} 0%, ${coreGradientMid} 50%, ${coreGradientEnd} 100%)`,
borderRadius: '50%',
animation: isFailed ? 'none' : 'pulse 1.5s ease-in-out infinite',
boxShadow: isFailed ? 'none' : undefined,
transition: 'background 0.3s, box-shadow 0.3s',
}}
/>
</div>
</div>
{/* Record Half - Bottom */}
<div
data-alt="record-half-bottom"
style={{
position: 'absolute',
width: '100%',
height: '100%',
backgroundColor: recordBg,
borderRadius: '50%',
boxShadow: `0 15px 35px rgba(0, 0, 0, 0.7),
inset 0 0 20px rgba(0,0,0,1),
inset 0 0 2px 1px rgba(255, 255, 255, 0.1)`,
overflow: 'hidden',
clipPath: isFailed
? 'polygon(0 50%, 10% 49.5%, 25% 51.5%, 40% 50%, 55% 52%, 70% 49%, 85% 51%, 100% 50%, 100% 100%, 0 100%)'
: 'polygon(0 50%, 100% 50%, 100% 100%, 0 100%)',
transition: 'transform 0.5s cubic-bezier(0.68, -0.55, 0.27, 1.55), clip-path 0.3s ease',
transform: isFailed ? 'translateY(10px) rotate(4deg)' : 'none',
}}
>
{/* Record grooves effect */}
<div
data-alt="record-grooves-bottom"
style={{
content: '""',
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
borderRadius: '50%',
backgroundImage: `repeating-radial-gradient(
circle at center,
rgba(255, 255, 255, 0.1) 0,
rgba(255, 255, 255, 0.15) 1px,
transparent 1px,
transparent 7px
)`,
mixBlendMode: 'overlay',
zIndex: 1,
}}
/>
{/* Scanner light */}
<div
data-alt="scanner-light-bottom"
style={{
position: 'absolute',
width: '100%',
height: '100%',
borderRadius: '50%',
background: `conic-gradient(
from 90deg,
transparent 0%,
${scannerColor} 10%,
transparent 25%
)`,
animation: isFailed ? 'none' : 'rotateScanner 3s linear infinite',
zIndex: 2,
}}
/>
{/* Center label */}
<div
data-alt="center-label-bottom"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
margin: 'auto',
width: '38%',
height: '38%',
background: '#111',
borderRadius: '50%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
boxShadow: 'inset 0 0 20px rgba(0, 0, 0, 1)',
zIndex: 3,
}}
>
{/* Pulsing core */}
<div
data-alt="pulsing-core-bottom"
style={{
width: '12%',
height: '12%',
background: isFailed
? appleSilverDark
: `radial-gradient(circle at center, ${coreGradientStart} 0%, ${coreGradientMid} 50%, ${coreGradientEnd} 100%)`,
borderRadius: '50%',
animation: isFailed ? 'none' : 'pulse 1.5s ease-in-out infinite',
boxShadow: isFailed ? 'none' : undefined,
transition: 'background 0.3s, box-shadow 0.3s',
}}
/>
</div>
</div>
</div>
{/* Tonearm */}
<div
data-alt="tonearm"
style={{
position: 'absolute',
width: `calc(${recordSize} * 0.5)`,
height: `calc(${recordSize} * 0.1)`,
right: `calc(${recordSize} * -0.25)`,
top: '35%',
transformOrigin: `calc(100% - ${recordSize} * 0.06) center`,
zIndex: 10,
animation: isFailed
? 'breakTonearm 1.2s forwards'
: 'placeTonearm 1.5s ease-in-out forwards, bobTonearm 1s ease-in-out 1.5s infinite',
}}
>
{/* Arm */}
<div
data-alt="tonearm-arm"
style={{
width: '88%',
height: '30%',
background: isFailed
? deadMetalColor
: `linear-gradient(90deg,
${appleSilverDark} 0%,
${appleSilverLight} 50%,
${appleSilverDark} 100%)`,
position: 'absolute',
left: 0,
top: '50%',
transform: 'translateY(-50%)',
borderRadius: '10px',
transition: 'background 0.3s ease',
}}
/>
{/* Head */}
<div
data-alt="tonearm-head"
style={{
width: '20%',
height: '60%',
background: isFailed ? deadMetalColor : appleSilverMain,
position: 'absolute',
left: 0,
top: '50%',
transform: 'translateY(-50%)',
borderRadius: '4px',
boxShadow: 'inset 0 0 4px rgba(0,0,0,0.4)',
transition: 'background 0.3s ease',
}}
/>
{/* Pivot */}
<div
data-alt="tonearm-pivot"
style={{
width: `calc(${recordSize} * 0.12)`,
height: `calc(${recordSize} * 0.12)`,
background: isFailed
? deadMetalColor
: `radial-gradient(circle at 65% 35%, ${appleSilverLight}, ${appleSilverMain} 60%, ${appleSilverDark} 100%)`,
borderRadius: '50%',
position: 'absolute',
right: 0,
top: '50%',
transform: 'translateY(-50%)',
boxShadow: isFailed
? '2px 2px 5px rgba(0,0,0,0.4), inset 1px 1px 1px #222'
: `2px 2px 5px rgba(0,0,0,0.4), inset 1px 1px 1px ${appleSilverLight}`,
transition: 'background 0.3s ease, box-shadow 0.3s ease',
}}
/>
</div>
</div>
{/* 加载/失败文案 */}
<div data-alt="loading-text" className={`text-center ${isFailed ? 'text-[#d2c6c6]' : 'text-white'}`}>
{loadingText}
</div>
</div>
</>
);
};
export default RenderLoading;

View File

@ -18,8 +18,7 @@ import { VideoEditOverlay } from './video-edit/VideoEditOverlay';
import { EditPoint as EditPointType } from './video-edit/types'; import { EditPoint as EditPointType } from './video-edit/types';
import { isVideoModificationEnabled } from '@/lib/server-config'; import { isVideoModificationEnabled } from '@/lib/server-config';
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
import error_image from '@/public/assets/error.webp'; import RenderLoading from './RenderLoading';
import { AspectRatioValue } from '@/components/ChatInputBox/AspectRatioSelector';
interface MediaViewerProps { interface MediaViewerProps {
taskObject: TaskObject; taskObject: TaskObject;
@ -573,14 +572,16 @@ export const MediaViewer = React.memo(function MediaViewer({
{taskObject.videos.data[currentSketchIndex].video_status !== 1 && ( {taskObject.videos.data[currentSketchIndex].video_status !== 1 && (
<div className="absolute inset-0 overflow-hidden z-20"> <div className="absolute inset-0 overflow-hidden z-20">
{/* 生成中 */} {/* 生成中 */}
{taskObject.videos.data[currentSketchIndex].video_status === 0 && ( {(taskObject.videos.data[currentSketchIndex].video_status === 0 || taskObject.videos.data[currentSketchIndex].video_status === 2) && (
renderLoading() <RenderLoading
loadingText={taskObject.videos.data[currentSketchIndex].video_status === 0 ? 'Generating video...' : 'Violation of security policy. Please modify your prompt and regenerate.'}
isFailed={taskObject.videos.data[currentSketchIndex].video_status === 2}
/>
)} )}
{/* 生成失败 */} {/* 生成失败 */}
{taskObject.videos.data[currentSketchIndex].video_status === 2 && ( {/* {taskObject.videos.data[currentSketchIndex].video_status === 2 && (
<div className="absolute inset-0 bg-[#fcb0ba1a] flex flex-col items-center justify-center"> <div className="absolute inset-0 bg-[#fcb0ba1a] flex flex-col items-center justify-center">
<img src={error_image.src} alt="error" className="w-12 h-12" /> <img src={error_image.src} alt="error" className="w-12 h-12" />
{/* 文案 */}
<div className="text-white text-center"> <div className="text-white text-center">
<div className="text-sm font-medium" role="status" aria-live="polite" aria-busy="true"> <div className="text-sm font-medium" role="status" aria-live="polite" aria-busy="true">
Failed Failed
@ -588,7 +589,7 @@ export const MediaViewer = React.memo(function MediaViewer({
<div className="text-xs text-white/60 mt-1">Violation of security policy. Please modify your prompt and regenerate.</div> <div className="text-xs text-white/60 mt-1">Violation of security policy. Please modify your prompt and regenerate.</div>
</div> </div>
</div> </div>
)} )} */}
</div> </div>
)} )}
@ -778,14 +779,17 @@ export const MediaViewer = React.memo(function MediaViewer({
}} }}
> >
{/* 状态 */} {/* 状态 */}
{currentSketch.status === 0 && ( {(currentSketch.status === 0 || currentSketch.status === 2) && (
renderLoading() <RenderLoading
loadingText={currentSketch.status === 0 ? (currentSketch.type === 'role' ? 'Generating role...' : 'Generating scene...') : 'Generate failed'}
isFailed={currentSketch.status === 2}
/>
)} )}
{currentSketch.status === 2 && ( {/* {currentSketch.status === 2 && (
<div className="absolute inset-0 bg-[#fcb0ba1a] flex items-center justify-center"> <div className="absolute inset-0 bg-[#fcb0ba1a] flex items-center justify-center">
<img src={error_image.src} alt="error" className="w-12 h-12" /> <img src={error_image.src} alt="error" className="w-12 h-12" />
</div> </div>
)} )} */}
{/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */} {/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */}
{currentSketch.status === 1 && ( {currentSketch.status === 1 && (
<AnimatePresence mode="wait"> <AnimatePresence mode="wait">

View File

@ -5,7 +5,6 @@ import { CircleAlert, Film } from 'lucide-react';
import { TaskObject } from '@/api/DTO/movieEdit'; import { TaskObject } from '@/api/DTO/movieEdit';
import { getFirstFrame } from '@/utils/tools'; import { getFirstFrame } from '@/utils/tools';
import { AspectRatioValue } from '@/components/ChatInputBox/AspectRatioSelector'; import { AspectRatioValue } from '@/components/ChatInputBox/AspectRatioSelector';
import error_image from '@/public/assets/error.webp';
interface ThumbnailGridProps { interface ThumbnailGridProps {
isDisabledFocus: boolean; isDisabledFocus: boolean;
@ -52,7 +51,7 @@ export function ThumbnailGrid({
const [scrollLeft, setScrollLeft] = useState(0); const [scrollLeft, setScrollLeft] = useState(0);
const [isFocused, setIsFocused] = useState(false); const [isFocused, setIsFocused] = useState(false);
// 监听当前选中索引变化,自动滚动到对应位置 // 监听当前选中索引变化,自动滚动到对应位置(居中显示)
useEffect(() => { useEffect(() => {
if (thumbnailsRef.current) { if (thumbnailsRef.current) {
const container = thumbnailsRef.current; const container = thumbnailsRef.current;
@ -60,9 +59,13 @@ export function ThumbnailGrid({
if (currentSketchIndex >= 0 && currentSketchIndex < thumbnails.length) { if (currentSketchIndex >= 0 && currentSketchIndex < thumbnails.length) {
const thumbnail = thumbnails[currentSketchIndex] as HTMLElement; const thumbnail = thumbnails[currentSketchIndex] as HTMLElement;
const containerLeft = container.getBoundingClientRect().left; const containerRect = container.getBoundingClientRect();
const thumbnailLeft = thumbnail.getBoundingClientRect().left; const thumbnailRect = thumbnail.getBoundingClientRect();
const scrollPosition = container.scrollLeft + (thumbnailLeft - containerLeft);
// 计算滚动位置:将缩略图居中显示
const containerCenter = containerRect.width / 2;
const thumbnailCenter = thumbnailRect.width / 2;
const scrollPosition = container.scrollLeft + (thumbnailRect.left - containerRect.left) - containerCenter + thumbnailCenter;
container.scrollTo({ container.scrollTo({
left: scrollPosition, left: scrollPosition,
@ -266,7 +269,7 @@ export function ThumbnailGrid({
)} )}
{taskObject.videos.data[index].video_status === 2 && ( {taskObject.videos.data[index].video_status === 2 && (
<div className="absolute inset-0 bg-red-500/10 flex items-center justify-center z-20"> <div className="absolute inset-0 bg-red-500/10 flex items-center justify-center z-20">
<img src={error_image.src} alt="error" className="w-6 h-6" /> <i className="iconfont icon-shipindiushibaojing text-[#773f8ecc] !text-lg" />
</div> </div>
)} )}
@ -340,7 +343,7 @@ export function ThumbnailGrid({
)} )}
{sketch.status === 2 && ( {sketch.status === 2 && (
<div className="absolute inset-0 bg-red-500/10 flex items-center justify-center z-20"> <div className="absolute inset-0 bg-red-500/10 flex items-center justify-center z-20">
<img src={error_image.src} alt="error" className="w-6 h-6" /> <i className="iconfont icon-tudiushi text-[#773f8ecc] !text-lg" />
</div> </div>
)} )}
{/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */} {/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */}

23
iconfont/iconfont.css Normal file
View File

@ -0,0 +1,23 @@
@font-face {
font-family: "iconfont"; /* Project id 5039410 */
src: url('./iconfont.woff2?t=1760340285776') format('woff2'),
url('./iconfont.woff?t=1760340285776') format('woff'),
url('./iconfont.ttf?t=1760340285776') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-shipindiushibaojing:before {
content: "\e618";
}
.icon-tudiushi:before {
content: "\e717";
}

BIN
iconfont/iconfont.ttf Normal file

Binary file not shown.

BIN
iconfont/iconfont.woff Normal file

Binary file not shown.

BIN
iconfont/iconfont.woff2 Normal file

Binary file not shown.