“首页样式优化”

This commit is contained in:
sjy
2026-07-10 10:20:59 +08:00
parent 4f0cd6baf6
commit d99062ca98
5 changed files with 230 additions and 119 deletions
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -28,7 +28,7 @@
}
})();
</script>
<script type="module" crossorigin src="/assets/index-epOb7vts.js"></script>
<script type="module" crossorigin src="/assets/index-I0gVfD2c.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-JhRVnnL-.css">
</head>
<body>
@@ -142,11 +142,11 @@ const PrivatePortraitAssetPicker: React.FC<PrivatePortraitAssetPickerProps> = ({
}, [open, libraryType]);
useEffect(() => {
if (!open) return;
if (!projectId) return;
setTimeout(() => {
loadAssets();
}, 0);
}, [open, projectId, assetType, libraryType]);
}, [projectId, assetType]);
const toggle = (asset: PrivatePortraitSelectableAsset) => {
if (selectedIds.includes(asset.id)) {
+160 -98
View File
@@ -15,6 +15,7 @@ import {
HeartOutlined,
ShareAltOutlined,
StarOutlined,
CopyOutlined,
} from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import { getmedit ,getHomeCaseHeader,getHomeCaseButton} from '../api';
@@ -22,6 +23,147 @@ import { getmedit ,getHomeCaseHeader,getHomeCaseButton} from '../api';
interface CaseAssetCardProps {
asset: any;
index: number;
onPreview: (asset: any) => void;
}
const CaseAssetCard: React.FC<CaseAssetCardProps> = ({ asset, index, onPreview }) => {
const [isHovered, setIsHovered] = useState(false);
return (
<div
className="project-card"
onClick={() => onPreview(asset)}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{
borderRadius: 12,
overflow: 'hidden',
cursor: 'pointer',
flexShrink: 0,
width: "18%",
position: 'relative',
}}
>
<div style={{ position: 'relative', aspectRatio: '9/16', }}>
{asset.mediaType === 'video' ? (
<>
<video
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${asset.url}`}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
muted
playsInline
/>
<div style={{
position: 'absolute',
inset: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}>
<div style={{
width: 40,
height: 40,
borderRadius: '50%',
background: 'rgba(0,0,0,0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}>
<PlayCircleOutlined style={{ fontSize: 24, color: '#fff' }} />
</div>
</div>
</>
) : (
<img
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${asset.url}`}
alt={asset.title || `素材 ${index + 1}`}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
)}
{/* <div
style={{
position: 'absolute',
inset: 0,
background: 'rgba(0,0,0,0.6)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
opacity: isHovered ? 1 : 0,
transition: 'opacity 0.4s ease',
}}
>
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 10,
padding: '24px 36px',
background: 'linear-gradient(135deg, rgba(6,182,212,0.2), rgba(99,102,241,0.2))',
borderRadius: 16,
backdropFilter: 'blur(20px)',
border: '1px solid rgba(6,182,212,0.3)',
boxShadow: isHovered ? '0 0 30px rgba(6,182,212,0.4), 0 0 60px rgba(99,102,241,0.2)' : 'none',
transform: isHovered ? 'scale(1) translateY(0)' : 'scale(0.95) translateY(10px)',
transition: 'transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.4s ease',
}}
>
<div
style={{
position: 'relative',
width: 56,
height: 56,
borderRadius: '50%',
background: 'linear-gradient(135deg, #06b6d4, #6366f1)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
animation: isHovered ? 'pulse-glow 2s ease-in-out infinite' : 'none',
}}
>
<CopyOutlined style={{ fontSize: 28, color: '#fff' }} />
<div
style={{
position: 'absolute',
inset: '-4px',
borderRadius: '50%',
background: 'linear-gradient(135deg, #06b6d4, #6366f1)',
opacity: 0.3,
animation: isHovered ? 'ripple 2s ease-out infinite' : 'none',
}}
/>
</div>
<span
style={{
fontSize: 14,
color: '#fff',
fontWeight: 600,
letterSpacing: 1,
textShadow: '0 0 10px rgba(6,182,212,0.5)',
}}
>
点击预览
</span>
<style>{`
@keyframes pulse-glow {
0%, 100% { box-shadow: 0 0 20px rgba(6,182,212,0.5); }
50% { box-shadow: 0 0 40px rgba(99,102,241,0.6), 0 0 60px rgba(6,182,212,0.3); }
}
@keyframes ripple {
0% { transform: scale(1); opacity: 0.6; }
100% { transform: scale(1.8); opacity: 0; }
}
`}</style>
</div>
</div> */}
</div>
</div>
);
};
// 把 ISO 时间格式化成 MM-DD HH:mm(与图片一致)
function formatShortDate(iso?: string | null): string {
if (!iso) return '-';
@@ -135,10 +277,17 @@ const HomePage: React.FC = () => {
const aiEntries = [
{
icon: <FileTextOutlined style={{ fontSize: 24 }} />,
title: '项目创建',
title: '行业智造',
description: '新建项目、设置图文视频参数、核对信息并生成素材',
action: '立即创作',
path: '/projects',
},
{
icon: <RobotOutlined style={{ fontSize: 24 }} />,
title: 'AI成片',
description: '输入想法、剧本或上传参考,智能生成视频/图片',
action: '立即生成',
path: '/conversation',
},
{
icon: <FileTextOutlined style={{ fontSize: 24 }} />,
@@ -154,13 +303,7 @@ const HomePage: React.FC = () => {
action: '开始拆镜',
path: '/removelens',
},
{
icon: <RobotOutlined style={{ fontSize: 24 }} />,
title: 'AI成片',
description: '输入想法、剧本或上传参考,智能生成视频/图片',
action: '立即生成',
path: '/conversation',
},
];
@@ -842,93 +985,12 @@ const HomePage: React.FC = () => {
<div></div>
</div>
) : caseAssets.map((asset: any, index: number) => (
<div
<CaseAssetCard
key={asset.id || index}
className="project-card"
onClick={() => setPreviewAsset(asset)}
style={{
borderRadius: 12,
overflow: 'hidden',
cursor: 'pointer',
// background: '#0f172a',
flexShrink: 0,
width: "18%",
}}
>
<div style={{ position: 'relative', aspectRatio: '9/16', }}>
{asset.mediaType === 'video' ? (
<>
<video
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${asset.url}`}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
muted
playsInline
/>
<div style={{
position: 'absolute',
inset: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}>
<div style={{
width: 40,
height: 40,
borderRadius: '50%',
background: 'rgba(0,0,0,0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}>
<PlayCircleOutlined style={{ fontSize: 24, color: '#fff' }} />
</div>
</div>
</>
) : (
<img
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${asset.url}`}
alt={asset.title || `素材 ${index + 1}`}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
)}
{/* {asset.mediaType === 'video' && (
<div style={{
position: 'absolute',
top: 8,
right: 8,
fontSize: 10,
color: '#fff',
background: 'rgba(0,0,0,0.6)',
padding: '2px 6px',
borderRadius: 4,
}}>
AI生成
</div>
)} */}
</div>
{/* <div style={{ padding: '8px 10px' }}>
<div style={{
fontSize: 11,
color: '#94a3b8',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
marginBottom: 4,
}}>
{asset.title || `素材 ${index + 1}`}
</div>
<div style={{
display: 'flex',
alignItems: 'center',
gap: 4,
fontSize: 10,
color: '#94a3b8',
}}>
<HeartOutlined style={{ fontSize: 12 }} />
<span>{asset.likes || Math.floor(Math.random() * 1000)}</span>
</div>
</div> */}
</div>
asset={asset}
index={index}
onPreview={setPreviewAsset}
/>
))}
</div>
</>
@@ -948,24 +1010,24 @@ const HomePage: React.FC = () => {
className="preview-modal"
style={{ borderRadius: 16, overflow: 'hidden' }}
>
<div style={{ display: 'flex', height: 580 }}>
<div style={{ display: 'flex', height: 580 ,paddingTop: 30}}>
{/* 左侧:素材预览 */}
<div style={{ flex: 1, position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
<div style={{ width: 280, aspectRatio: '9/16', borderRadius: 12, overflow: 'hidden', background: '#0f172a' }}>
<div style={{ maxWidth: '100%', maxHeight: 500, borderRadius: 12, overflow: 'hidden', background: '#0f172a' }}>
{previewAsset?.mediaType === 'video' ? (
<video
ref={previewVideoRef}
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${previewAsset.url}`}
controls
autoPlay
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
style={{ maxWidth: '100%', maxHeight: 500, objectFit: 'contain' }}
webkit-playsinline="true"
/>
) : (
<img
src={`${import.meta.env.VITE_API_BASE || "http://localhost:8000"}${previewAsset?.url}`}
alt={previewAsset?.title}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
style={{ maxWidth: '100%', maxHeight: 500, objectFit: 'contain' }}
/>
)}
{previewAsset?.mediaType === 'video' && (
+54 -5
View File
@@ -86,12 +86,14 @@ function RemoveInfo() {
const pollRes = await Removelist(creatID);
const pollItems = pollRes.items || [];
// 只更新未完成的项目
// 只更新未完成的项目,并检查是否需要自动分析
setTableData((prevData) =>
prevData.map((prevItem) => {
const pollItem = pollItems.find((p: any) => p.id === prevItem.id);
if (pollItem) {
// 只有当原项目未完成时才更新
const prevSplitStatus = prevItem.split_status || prevItem.splitStatus;
const currSplitStatus = pollItem.split_status || pollItem.splitStatus;
if (
prevItem.moduleProjectCurrentStepCode !== 'video_generate' ||
(prevItem.moduleProjectStatus !== 'completed' && prevItem.moduleProjectStatus !== 'failed')
@@ -173,11 +175,50 @@ function RemoveInfo() {
try {
await retrySplit(segmentId);
message.success('重新切割已提交');
fetchSegments();
await fetchSegments();
if (!pollingIntervalRef.current && creatID) {
pollingIntervalRef.current = window.setInterval(async () => {
try {
const pollRes = await Removelist(creatID);
const pollItems = pollRes.items || [];
setTableData((prevData) =>
prevData.map((prevItem) => {
const pollItem = pollItems.find((p: any) => p.id === prevItem.id);
if (pollItem) {
if (
prevItem.moduleProjectCurrentStepCode !== 'video_generate' ||
(prevItem.moduleProjectStatus !== 'completed' && prevItem.moduleProjectStatus !== 'failed')
) {
return pollItem;
}
}
return prevItem;
})
);
const allCompleted = pollItems.every(
(item: any) =>
item.moduleProjectCurrentStepCode === 'video_generate' ||
item.moduleProjectStatus === 'completed' ||
item.moduleProjectStatus === 'failed'
);
if (allCompleted) {
if (pollingIntervalRef.current) {
clearInterval(pollingIntervalRef.current);
pollingIntervalRef.current = null;
}
}
} catch {
if (pollingIntervalRef.current) {
clearInterval(pollingIntervalRef.current);
pollingIntervalRef.current = null;
}
}
}, 3000);
}
} catch (error: any) {
message.error(error?.message || '重新切割失败');
}
}, [fetchSegments]);
}, [creatID, fetchSegments]);
const handleDeleteSegment = useCallback(async (segmentId: string) => {
try {
@@ -443,9 +484,17 @@ function RemoveInfo() {
width: 500,
align: 'left' as const,
render: (_: any, record: any) => {
const splitStatus = record.split_status || record.splitStatus;
if (splitStatus === 'processing') {
return (
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<Spin size="small" />
<span style={{ fontSize: 14, color: '#f59e0b' }}></span>
</div>
);
}
// AI建议的片段直接显示内容,不经过分析状态判断
if (record.source_mode === 'ai_suggestion' || record.sourceMode === 'ai_suggestion') {
return <div style={{ fontSize: 14, color: '#333', lineHeight: 1.6 }}>{record.segmentContent || record.lastError || '-'}</div>;
}