“首页样式优化”
This commit is contained in:
+13
-13
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -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)) {
|
||||
|
||||
@@ -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' && (
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user