|
@@ -1,10 +1,11 @@
|
|
import React, { useEffect, useState } from 'react';
|
|
import React, { useEffect, useState } from 'react';
|
|
-import { Modal, Form, Select, Button, Row, Col, Card, Typography, message } from 'antd';
|
|
|
|
-import { CloseOutlined, PlusOutlined, EditOutlined, PlayCircleOutlined, CaretRightFilled } from '@ant-design/icons';
|
|
|
|
|
|
+import { Modal, Form, Select, Button, Card, Typography, message } from 'antd';
|
|
|
|
+import { CloseOutlined, PlusOutlined, EditOutlined, CaretRightFilled } from '@ant-design/icons';
|
|
import VideoSelectModal from '../videoSelectModal';
|
|
import VideoSelectModal from '../videoSelectModal';
|
|
import EditTitleCoverModal from '../editTitleCoverModal';
|
|
import EditTitleCoverModal from '../editTitleCoverModal';
|
|
import { VideoItem } from '../types'; // Import from common types
|
|
import { VideoItem } from '../types'; // Import from common types
|
|
-import { PlanData } from '../..';
|
|
|
|
|
|
+import { GzhPlanType } from '../../hooks/useGzhPlanList';
|
|
|
|
+import { useAccountOptions } from '../../hooks/useAccountOptions';
|
|
|
|
|
|
const { Option } = Select;
|
|
const { Option } = Select;
|
|
const { Text } = Typography;
|
|
const { Text } = Typography;
|
|
@@ -12,21 +13,24 @@ const { Text } = Typography;
|
|
interface AddPunlishPlanModalProps {
|
|
interface AddPunlishPlanModalProps {
|
|
visible: boolean;
|
|
visible: boolean;
|
|
onCancel: () => void;
|
|
onCancel: () => void;
|
|
- onOk: (values: any) => void; // Pass form values on OK
|
|
|
|
|
|
+ onOk: (values: GzhPlanType) => void; // Pass form values on OK
|
|
actionType: 'add' | 'edit';
|
|
actionType: 'add' | 'edit';
|
|
- editPlanData?: PlanData;
|
|
|
|
|
|
+ editPlanData?: GzhPlanType;
|
|
|
|
+ isSubmiting?: boolean;
|
|
}
|
|
}
|
|
|
|
|
|
-const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, onCancel, onOk, actionType, editPlanData }) => {
|
|
|
|
|
|
+const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, isSubmiting, onCancel, onOk, actionType, editPlanData }) => {
|
|
const [form] = Form.useForm();
|
|
const [form] = Form.useForm();
|
|
const [selectedVideos, setSelectedVideos] = useState<VideoItem[]>([]);
|
|
const [selectedVideos, setSelectedVideos] = useState<VideoItem[]>([]);
|
|
const [isVideoSelectVisible, setIsVideoSelectVisible] = useState(false);
|
|
const [isVideoSelectVisible, setIsVideoSelectVisible] = useState(false);
|
|
const [playingVideo, setPlayingVideo] = useState<VideoItem | null>(null); // State for video player modal
|
|
const [playingVideo, setPlayingVideo] = useState<VideoItem | null>(null); // State for video player modal
|
|
const [editingVideo, setEditingVideo] = useState<VideoItem | null>(null); // State for editing modal
|
|
const [editingVideo, setEditingVideo] = useState<VideoItem | null>(null); // State for editing modal
|
|
|
|
+ const { accountOptions } = useAccountOptions();
|
|
|
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
if (actionType === 'edit') {
|
|
if (actionType === 'edit') {
|
|
form.setFieldsValue(editPlanData);
|
|
form.setFieldsValue(editPlanData);
|
|
|
|
+ setSelectedVideos(editPlanData?.videoList || []);
|
|
}
|
|
}
|
|
}, [actionType, editPlanData]);
|
|
}, [actionType, editPlanData]);
|
|
|
|
|
|
@@ -43,9 +47,9 @@ const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, onCa
|
|
const videosToSubmit = selectedVideos.map(v => ({
|
|
const videosToSubmit = selectedVideos.map(v => ({
|
|
...v,
|
|
...v,
|
|
title: v.customTitle || v.title,
|
|
title: v.customTitle || v.title,
|
|
- thumbnail: v.customThumbnail || v.thumbnail,
|
|
|
|
|
|
+ cover: v.customCover || v.cover,
|
|
}));
|
|
}));
|
|
- onOk({ ...values, videos: videosToSubmit });
|
|
|
|
|
|
+ onOk({ ...values, scene: 0, videoList: videosToSubmit });
|
|
})
|
|
})
|
|
.catch((info) => {
|
|
.catch((info) => {
|
|
console.log('Validate Failed:', info);
|
|
console.log('Validate Failed:', info);
|
|
@@ -53,7 +57,7 @@ const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, onCa
|
|
};
|
|
};
|
|
|
|
|
|
const removeVideo = (idToRemove: number) => {
|
|
const removeVideo = (idToRemove: number) => {
|
|
- setSelectedVideos(currentVideos => currentVideos.filter(video => video.id !== idToRemove));
|
|
|
|
|
|
+ setSelectedVideos(currentVideos => currentVideos.filter(video => video.videoId !== idToRemove));
|
|
};
|
|
};
|
|
|
|
|
|
const openVideoSelector = () => {
|
|
const openVideoSelector = () => {
|
|
@@ -64,13 +68,13 @@ const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, onCa
|
|
// Merge existing custom data with newly selected videos
|
|
// Merge existing custom data with newly selected videos
|
|
const currentCustomData = new Map<number, Partial<VideoItem>>();
|
|
const currentCustomData = new Map<number, Partial<VideoItem>>();
|
|
selectedVideos.forEach(v => {
|
|
selectedVideos.forEach(v => {
|
|
- if (v.customTitle || v.customThumbnail) {
|
|
|
|
- currentCustomData.set(v.id, { customTitle: v.customTitle, customThumbnail: v.customThumbnail });
|
|
|
|
|
|
+ if (v.cover) {
|
|
|
|
+ currentCustomData.set(v.videoId, { title: v.title, cover: v.cover });
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
const mergedVideos = newlySelectedVideos.map(newVideo => {
|
|
const mergedVideos = newlySelectedVideos.map(newVideo => {
|
|
- const customData = currentCustomData.get(newVideo.id);
|
|
|
|
|
|
+ const customData = currentCustomData.get(newVideo.videoId);
|
|
return { ...newVideo, ...customData };
|
|
return { ...newVideo, ...customData };
|
|
});
|
|
});
|
|
|
|
|
|
@@ -95,9 +99,10 @@ const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, onCa
|
|
};
|
|
};
|
|
|
|
|
|
const handleEditOk = (updatedData: Partial<VideoItem>) => {
|
|
const handleEditOk = (updatedData: Partial<VideoItem>) => {
|
|
|
|
+ console.log('updatedData', updatedData);
|
|
setSelectedVideos(currentVideos =>
|
|
setSelectedVideos(currentVideos =>
|
|
currentVideos.map(v =>
|
|
currentVideos.map(v =>
|
|
- v.id === editingVideo?.id ? { ...v, ...updatedData } : v
|
|
|
|
|
|
+ v.videoId === editingVideo?.videoId ? { ...v, ...updatedData } : v
|
|
)
|
|
)
|
|
);
|
|
);
|
|
setEditingVideo(null); // Close modal
|
|
setEditingVideo(null); // Close modal
|
|
@@ -112,22 +117,28 @@ const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, onCa
|
|
<Modal
|
|
<Modal
|
|
title="创建发布计划"
|
|
title="创建发布计划"
|
|
open={visible}
|
|
open={visible}
|
|
|
|
+ destroyOnClose
|
|
onCancel={onCancel}
|
|
onCancel={onCancel}
|
|
width={900} // Adjust width as needed
|
|
width={900} // Adjust width as needed
|
|
footer={[
|
|
footer={[
|
|
<Button key="back" onClick={onCancel}>
|
|
<Button key="back" onClick={onCancel}>
|
|
取消
|
|
取消
|
|
</Button>,
|
|
</Button>,
|
|
- <Button key="submit" type="primary" onClick={handleOk}>
|
|
|
|
|
|
+ <Button key="submit" type="primary" loading={isSubmiting} onClick={handleOk}>
|
|
确定
|
|
确定
|
|
</Button>,
|
|
</Button>,
|
|
]}
|
|
]}
|
|
zIndex={10}
|
|
zIndex={10}
|
|
- destroyOnClose
|
|
|
|
>
|
|
>
|
|
<Form form={form} layout="vertical">
|
|
<Form form={form} layout="vertical">
|
|
<Form.Item
|
|
<Form.Item
|
|
- name="publisher"
|
|
|
|
|
|
+ name="id"
|
|
|
|
+ label="计划ID"
|
|
|
|
+ hidden
|
|
|
|
+ >
|
|
|
|
+ </Form.Item>
|
|
|
|
+ <Form.Item
|
|
|
|
+ name="publishStage"
|
|
label="发布方"
|
|
label="发布方"
|
|
labelCol={{ span: 4 }}
|
|
labelCol={{ span: 4 }}
|
|
labelAlign='left'
|
|
labelAlign='left'
|
|
@@ -135,38 +146,45 @@ const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, onCa
|
|
rules={[{ required: true, message: '请选择发布方' }]}
|
|
rules={[{ required: true, message: '请选择发布方' }]}
|
|
>
|
|
>
|
|
<Select placeholder="选择发布方" allowClear className='!w-50'>
|
|
<Select placeholder="选择发布方" allowClear className='!w-50'>
|
|
- <Option value="platform">平台发布</Option>
|
|
|
|
- <Option value="user">用户发布</Option>
|
|
|
|
|
|
+ <Option value={0}>平台发布</Option>
|
|
|
|
+ <Option value={1}>用户发布</Option>
|
|
</Select>
|
|
</Select>
|
|
</Form.Item>
|
|
</Form.Item>
|
|
<Form.Item
|
|
<Form.Item
|
|
- name="officialAccount"
|
|
|
|
|
|
+ name="accountId"
|
|
label="公众号名称"
|
|
label="公众号名称"
|
|
labelCol={{ span: 4 }}
|
|
labelCol={{ span: 4 }}
|
|
labelAlign='left'
|
|
labelAlign='left'
|
|
layout="horizontal"
|
|
layout="horizontal"
|
|
rules={[{ required: true, message: '请选择公众号' }]}
|
|
rules={[{ required: true, message: '请选择公众号' }]}
|
|
>
|
|
>
|
|
- <Select placeholder="选择公众号" allowClear className='!w-50'>
|
|
|
|
- <Option value="account1">小慧爱厨房</Option>
|
|
|
|
- <Option value="account2">小阳看天下</Option>
|
|
|
|
- {/* Add more options as needed */}
|
|
|
|
|
|
+ <Select
|
|
|
|
+ placeholder="选择公众号"
|
|
|
|
+ allowClear
|
|
|
|
+ className='!w-50'
|
|
|
|
+ options={accountOptions.map(option => ({ label: option.name, value: option.id }))}>
|
|
</Select>
|
|
</Select>
|
|
</Form.Item>
|
|
</Form.Item>
|
|
<Form.Item
|
|
<Form.Item
|
|
|
|
+ name="scene"
|
|
label="发布场景"
|
|
label="发布场景"
|
|
layout="horizontal"
|
|
layout="horizontal"
|
|
labelCol={{ span: 4 }}
|
|
labelCol={{ span: 4 }}
|
|
labelAlign='left'
|
|
labelAlign='left'
|
|
>
|
|
>
|
|
- <Text>关注回复</Text>
|
|
|
|
|
|
+ <Select
|
|
|
|
+ placeholder="发布场景"
|
|
|
|
+ className='!w-50'
|
|
|
|
+ defaultValue={0}
|
|
|
|
+ options={[{ label: '关注回复', value: 0 }]}>
|
|
|
|
+ </Select>
|
|
</Form.Item>
|
|
</Form.Item>
|
|
|
|
|
|
<Form.Item label="发布内容" required>
|
|
<Form.Item label="发布内容" required>
|
|
<div className="flex flex-wrap gap-4">
|
|
<div className="flex flex-wrap gap-4">
|
|
{selectedVideos.map((video) => (
|
|
{selectedVideos.map((video) => (
|
|
<Card
|
|
<Card
|
|
- key={video.id}
|
|
|
|
|
|
+ key={video.videoId}
|
|
className="w-[240px] relative group"
|
|
className="w-[240px] relative group"
|
|
>
|
|
>
|
|
<Button
|
|
<Button
|
|
@@ -174,25 +192,26 @@ const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, onCa
|
|
icon={<CloseOutlined />}
|
|
icon={<CloseOutlined />}
|
|
className="!absolute top-1 right-1 z-10 bg-gray-400 bg-opacity-50 border-none text-white hidden group-hover:inline-flex justify-center items-center"
|
|
className="!absolute top-1 right-1 z-10 bg-gray-400 bg-opacity-50 border-none text-white hidden group-hover:inline-flex justify-center items-center"
|
|
size="small"
|
|
size="small"
|
|
- onClick={() => removeVideo(video.id)}
|
|
|
|
|
|
+ onClick={() => removeVideo(video.videoId)}
|
|
/>
|
|
/>
|
|
<div className="p-0">
|
|
<div className="p-0">
|
|
- <Text type="secondary" className="text-xs">{video.source}</Text>
|
|
|
|
- <Text className="block mt-1 mb-2 leading-tight line-clamp-2" title={video.customTitle || video.title}>{video.customTitle || video.title}</Text>
|
|
|
|
|
|
+ <Text type="secondary" className="text-xs">票圈 | 3亿人喜欢的视频平台</Text>
|
|
|
|
+ <Text className="block mt-1 mb-2 leading-tight line-clamp-2" title={video.title}>{video.title}</Text>
|
|
</div>
|
|
</div>
|
|
- <div className="relative h-[120px] bg-gray-200 cursor-pointer group/thumb"
|
|
|
|
- onClick={(e) => {
|
|
|
|
- e.stopPropagation(); // Prevent card selection if clicking thumbnail/play
|
|
|
|
- playVideo(video);
|
|
|
|
- }}
|
|
|
|
|
|
+ <div
|
|
|
|
+ className="relative h-[120px] bg-gray-200 cursor-pointer group/thumb"
|
|
|
|
+ onClick={(e) => {
|
|
|
|
+ e.stopPropagation(); // Prevent card selection if clicking thumbnail/play
|
|
|
|
+ playVideo(video);
|
|
|
|
+ }}
|
|
>
|
|
>
|
|
- <img src={video.customThumbnail || video.thumbnail} alt={video.customTitle || video.title} className="w-full h-full object-cover" />
|
|
|
|
|
|
+ <img src={video.cover} referrerPolicy="no-referrer" alt={video.title} className="w-full h-full object-cover" />
|
|
<div className="absolute inset-0 flex justify-center items-center cursor-pointer">
|
|
<div className="absolute inset-0 flex justify-center items-center cursor-pointer">
|
|
<CaretRightFilled className="!text-white text-4xl bg-black/20 rounded-full p-1 pl-2" />
|
|
<CaretRightFilled className="!text-white text-4xl bg-black/20 rounded-full p-1 pl-2" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="p-3">
|
|
<div className="p-3">
|
|
- <Text type="secondary" className="text-xs">传播效率: {video.spreadEfficiency.toFixed(2)}</Text>
|
|
|
|
|
|
+ <Text type="secondary" className="text-xs">传播效率: {video.score?.toFixed(2)}</Text>
|
|
<Button
|
|
<Button
|
|
icon={<EditOutlined />}
|
|
icon={<EditOutlined />}
|
|
className="w-full mt-2"
|
|
className="w-full mt-2"
|
|
@@ -224,14 +243,14 @@ const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, onCa
|
|
visible={isVideoSelectVisible}
|
|
visible={isVideoSelectVisible}
|
|
onClose={handleVideoSelectionCancel}
|
|
onClose={handleVideoSelectionCancel}
|
|
onOk={handleVideoSelectionOk}
|
|
onOk={handleVideoSelectionOk}
|
|
- initialSelectedIds={selectedVideos.map(v => v.id)} // Pass current selection IDs
|
|
|
|
|
|
+ initialSelectedIds={selectedVideos.map(v => v.videoId)} // Pass current selection IDs
|
|
/>
|
|
/>
|
|
|
|
|
|
{/* Video Player Modal */}
|
|
{/* Video Player Modal */}
|
|
<Modal
|
|
<Modal
|
|
open={!!playingVideo}
|
|
open={!!playingVideo}
|
|
onCancel={closeVideoPlayer}
|
|
onCancel={closeVideoPlayer}
|
|
- title={playingVideo?.customTitle || playingVideo?.title}
|
|
|
|
|
|
+ title={playingVideo?.title}
|
|
footer={null}
|
|
footer={null}
|
|
destroyOnClose // Unmount video element when closed
|
|
destroyOnClose // Unmount video element when closed
|
|
width={720} // Adjust as needed
|
|
width={720} // Adjust as needed
|
|
@@ -243,7 +262,7 @@ const AddPunlishPlanModal: React.FC<AddPunlishPlanModalProps> = ({ visible, onCa
|
|
controls
|
|
controls
|
|
autoPlay
|
|
autoPlay
|
|
className="w-full h-auto max-h-[80vh] block"
|
|
className="w-full h-auto max-h-[80vh] block"
|
|
- src={playingVideo.videoUrl}
|
|
|
|
|
|
+ src={playingVideo.video}
|
|
>
|
|
>
|
|
Your browser does not support the video tag.
|
|
Your browser does not support the video tag.
|
|
</video>
|
|
</video>
|