拆镜复刻

This commit is contained in:
孙佳艺
2026-06-10 15:30:48 +08:00
parent 735ef8d9fd
commit 383ac946af
6 changed files with 1056 additions and 511 deletions
+20 -1
View File
@@ -1 +1,20 @@
{}
{
"src/pages/GenerateConver.tsx": {
"description": "ai创建"
},
"src/pages/GeneratedRecord.tsx": {
"description": "生成历史"
},
"src/pages/GeneratePage.tsx": {
"description": "生成记录"
},
"src/pages/ProjectsPage.tsx": {
"description": "我的项目"
},
"src/pages/RemoveLens.tsx": {
"description": "拆镜复刻"
},
"src/pages/InitialReplication.tsx": {
"description": "爆款开头复刻"
}
}
+2
View File
@@ -14,6 +14,7 @@ import InitialInfo from './pages/InitialInfo';
import RemoveLens from './pages/RemoveLens';
import GeneratedRecord from './pages/GeneratedRecord';
import AuthorizationPage from './pages/AuthorizationPage';
import RemoveInfo from './pages/RemoveInfo';
@@ -96,6 +97,7 @@ const App = () => {
<Route path="initial" element={<InitialReplication />} />
<Route path="initial/:creatID/initialinfo" element={<InitialInfo />} />
<Route path="removelens" element={<RemoveLens />} />
<Route path="removelens/:creatID/removeinfo" element={<RemoveInfo />} />
<Route path="generated" element={<GeneratedRecord />} />
<Route path="authorization" element={<AuthorizationPage />} />
+1 -1
View File
@@ -1069,7 +1069,7 @@ const GeneratedRecord: React.FC = () => {
style={{ width: '100%', borderRadius: 8, marginTop: 20, color: '#4c49cc' }}
>
</Button>
</div>
+1 -1
View File
@@ -61,7 +61,7 @@ function InitialInfo() {
return (
<React.Fragment>
<div style={{ height: '94vh', background: '#f8fafc' }}>
<div style={{ height: 'calc(94vh)', padding: 24, }}>
<div style={{ height: 'calc(94vh)', }}>
<div style={{ height: '100%', display: 'flex', justifyContent: 'space-between', gap: '2%' }}>
<div style={{ width: '70%', background: '#fff', borderRadius: 12, overflowY: 'auto' }}>
<div style={{ borderBottom: '1px solid #e2e8f0', width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '14px', boxSizing: 'border-box' }}>
+419
View File
@@ -0,0 +1,419 @@
import React, { useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Button, Table, Tag, Drawer, Input, Upload, message } from 'antd';
import { ArrowLeftOutlined, PlayCircleOutlined, XOutlined, PlusOutlined, UploadOutlined } from '@ant-design/icons';
import type { UploadFile } from 'antd';
const { TextArea } = Input;
function RemoveInfo() {
const { creatID } = useParams<{ creatID: string }>();
const navigate = useNavigate();
const [drawerVisible, setDrawerVisible] = useState(false);
const [currentSegment, setCurrentSegment] = useState<number | null>(null);
const [productName, setProductName] = useState('');
const [productSellingPoint, setProductSellingPoint] = useState('');
const [productImage, setProductImage] = useState('');
const [detailImage, setDetailImage] = useState('');
const handleGenerate = (segmentId: number) => {
setCurrentSegment(segmentId);
setDrawerVisible(true);
};
const handleCloseDrawer = () => {
setDrawerVisible(false);
setCurrentSegment(null);
setProductName('');
setProductSellingPoint('');
setProductImage('');
setDetailImage('');
};
const handleProductImageChange: any = (info: any) => {
if (info.fileList.length > 0) {
const file = info.fileList[0];
if (file.originFileObj) {
const reader = new FileReader();
reader.onload = (e) => {
setProductImage(e.target?.result as string);
};
reader.readAsDataURL(file.originFileObj);
}
} else {
setProductImage('');
}
};
const handleDetailImageChange: any = (info: any) => {
if (info.fileList.length > 0) {
const file = info.fileList[0];
if (file.originFileObj) {
const reader = new FileReader();
reader.onload = (e) => {
setDetailImage(e.target?.result as string);
};
reader.readAsDataURL(file.originFileObj);
}
} else {
setDetailImage('');
}
};
const handleManualGenerate = () => {
// 必填校验
if (!productImage) {
message.warning('请上传产品图');
return;
}
if (!productName.trim()) {
message.warning('请输入产品名称');
return;
}
if (!productSellingPoint.trim()) {
message.warning('请输入产品卖点');
return;
}
// 输出内容
console.log('手动生成 - 片段', currentSegment);
console.log('产品图:', productImage);
console.log('细节图:', detailImage);
console.log('产品名称:', productName);
console.log('产品卖点:', productSellingPoint);
message.success(`手动生成成功!片段: ${currentSegment}`);
};
const mockData = {
productName: '返回',
uploadTime: '2026-06-09 09:01:16',
sellingPoints: ['一键匹配', '连麦聊天'],
audience: '123123',
audienceAnalysis: '123123',
videoUrl: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=320&h=180&fit=crop',
segments: [
{
key: '1',
id: 1,
timeRange: '00:00 - 00:03',
thumbnail: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=120&h=80&fit=crop',
content: '11111',
lines: 'qqqqqqqqqqq',
contentStrategy: '展示礼盒'
},
{
key: '2',
id: 2,
timeRange: '00:03 - 00:06',
thumbnail: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=120&h=80&fit=crop',
content: '1231231231',
lines: 'qqqqqqqqqqq',
contentStrategy: '开箱展示'
},
{
key: '3',
id: 3,
timeRange: '00:06 - 00:09',
thumbnail: 'https://images.unsplash.com/photo-1522202176988-66273c2fd55f?w=120&h=80&fit=crop',
content: '123123',
lines: 'qqqqqqqqqqq',
contentStrategy: '取出产品'
},
]
};
const columns = [
{
title: '片段',
width: 100,
render: (text: any, record: any) => (
<div>
<div style={{ fontSize: 16, fontWeight: 600, color: '#333' }}>{record.id}</div>
<div style={{ fontSize: 12, color: '#999' }}>{record.timeRange}</div>
</div>
)
},
{
title: '片段视频',
width: 120,
render: (text: any, record: any) => (
<div style={{ position: 'relative', width: 120, height: 80, borderRadius: 6, overflow: 'hidden' }}>
<img
src={record.thumbnail}
alt={`片段${record.id}`}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
<div style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 28,
height: 28,
background: 'rgba(0,0,0,0.6)',
borderRadius: '50%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<PlayCircleOutlined style={{ fontSize: 16, color: '#fff' }} />
</div>
</div>
)
},
{
title: '画面内容',
width: 250,
render: (text: any, record: any) => (
<div style={{ fontSize: 14, color: '#333', lineHeight: 1.6 }}>
{record.content}
</div>
)
},
{
title: '台词',
width: 250,
render: (text: any, record: any) => (
<div style={{ fontSize: 14, color: '#333', lineHeight: 1.6 }}>
{record.lines}
</div>
)
},
{
title: '内容策略',
width: 120,
align: 'left' as const,
render: (text: any, record: any) => (
<div style={{ fontSize: 14, color: '#333', fontWeight: 500 }}>
{record.contentStrategy || '-'}
</div>
)
},
{
title: '素材',
width: 140,
align: 'center' as const,
render: (text: any, record: any) => (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}>
<div style={{ fontSize: 12, color: '#999', padding: '12px 24px', border: '1px dashed #ddd', borderRadius: 4 }}>
</div>
<Button
type="text"
onClick={() => handleGenerate(record.id)}
style={{ color: '#656efa', fontSize: 12, padding: 0, display: 'flex', alignItems: 'center', gap: 4 }}
>
</Button>
</div>
)
}
];
return (
<React.Fragment>
<div style={{ minHeight: '94vh', background: '#f5f5f5' }}>
<div style={{ background: '#fff', padding: '16px 24px 0 24px', boxShadow: '0 2px 8px rgba(0,0,0,0.06)' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<Button
type="text"
icon={<ArrowLeftOutlined />}
onClick={() => navigate(-1)}
style={{ fontSize: 16, color: '#666' }}
/>
<span style={{ fontSize: 18, fontWeight: 600 }}>{mockData.productName}</span>
</div>
</div>
<div style={{ }}>
<div style={{ background: '#fff', borderRadius: 12, padding: '10px 20px 20px 20px', marginBottom: 20 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 30, marginBottom: 12 }}>
<div style={{ position: 'relative', width: 280, height: 160, borderRadius: 8, overflow: 'hidden', flexShrink: 0 }}>
<img
src={mockData.videoUrl}
alt="视频缩略图"
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
<div style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 48,
height: 48,
background: 'rgba(0,0,0,0.6)',
borderRadius: '50%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<PlayCircleOutlined style={{ fontSize: 28, color: '#fff' }} />
</div>
</div>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }}>
<div>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<h2 style={{ fontSize: 20, fontWeight: 600, marginBottom: 16 }}></h2>
<div style={{ textAlign: 'right' }}>
<span style={{ color: '#999', fontSize: 12 }}>: {mockData.uploadTime}</span>
</div>
</div>
<div style={{ display: 'flex', marginBottom: 12 }}>
<span style={{ color: '#999', fontSize: 14, marginRight: 12 }}>:</span>
<span style={{ color: '#333', fontWeight: 500 }}>{mockData.productName}</span>
</div>
<div style={{ display: 'flex', marginBottom: 12 }}>
<span style={{ color: '#999', fontSize: 14, marginRight: 12 }}>:</span>
<div style={{ display: 'flex', gap: 8 }}>
{mockData.sellingPoints.map((point, index) => (
<Tag key={index} color="purple" style={{ fontSize: 12 }}>
{point}
</Tag>
))}
</div>
</div>
<div style={{ display: 'flex', marginBottom: 12 }}>
<span style={{ color: '#999', fontSize: 14, marginRight: 12 }}>:</span>
<span style={{ color: '#333' }}>{mockData.audience}</span>
</div>
<div style={{ display: 'flex' }}>
<span style={{ color: '#999', fontSize: 14, marginRight: 12 }}>:</span>
<span style={{ color: '#333' }}>{mockData.audienceAnalysis}</span>
</div>
</div>
</div>
</div>
</div>
<div style={{ background: '#fff', borderRadius: 12, overflow: 'hidden' }}>
<Table
columns={columns}
dataSource={mockData.segments}
pagination={false}
bordered={false}
rowKey="key"
scroll={{ y: 520 }}
/>
</div>
</div>
</div>
<Drawer
title={
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
<span>
<span style={{ color: '#6366f1' }}></span>
<span style={{ color: '#6366f1' }}>-{currentSegment}</span>
</span>
<Button
type="text"
icon={<XOutlined />}
onClick={handleCloseDrawer}
style={{ padding: 0 }}
/>
</div>
}
placement="right"
closable={false}
onClose={handleCloseDrawer}
open={drawerVisible}
width={480}
bodyStyle={{ padding: '24px' }}
>
<div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
<div>
<label style={{ fontWeight: 400, color: '#333', marginBottom: 12, display: 'block' }}>
<span style={{ color: '#ff4d4f' }}>*</span>
</label>
<div style={{ display: 'flex', gap: 16 }}>
<Upload
listType="picture-card"
onChange={handleProductImageChange}
maxCount={1}
accept="image/*"
style={{ width: 140, height: 140 }}
>
{!productImage && (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}>
<PlusOutlined style={{ fontSize: 24, color: '#999' }} />
<span style={{ fontSize: 12, color: '#999' }}> *</span>
</div>
)}
</Upload>
<Upload
listType="picture-card"
onChange={handleDetailImageChange}
maxCount={1}
accept="image/*"
style={{ width: 140, height: 140 }}
>
{!detailImage && (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}>
<PlusOutlined style={{ fontSize: 24, color: '#999' }} />
<span style={{ fontSize: 12, color: '#999' }}></span>
</div>
)}
</Upload>
</div>
</div>
<div>
<label style={{ fontWeight: 500, color: '#333', marginBottom: 12, display: 'block' }}>
</label>
<Input
value={productName}
onChange={(e) => setProductName(e.target.value)}
placeholder="请输入产品名称"
style={{ height: 48, borderRadius: 8 }}
maxLength={10}
showCount
/>
</div>
<div>
<label style={{ fontWeight: 500, color: '#333', marginBottom: 12, display: 'block' }}>
</label>
<TextArea
value={productSellingPoint}
onChange={(e) => setProductSellingPoint(e.target.value)}
placeholder="请输入产品卖点"
style={{ borderRadius: 8 }}
maxLength={100}
showCount
rows={3}
/>
</div>
<div style={{ display: 'flex', gap: 16, marginTop: 24 }}>
<Button
type="default"
onClick={handleManualGenerate}
style={{
flex: 1,
height: 48,
borderRadius: 8,
borderColor: '#6366f1',
color: '#6366f1',
fontWeight: 500
}}
>
</Button>
</div>
</div>
</Drawer>
</React.Fragment>
);
}
export default RemoveInfo;
File diff suppressed because it is too large Load Diff