返回 AiToEarn
videoTask.tsx
根目录 / project / aitoearn-electron / src / views / task / videoTask.tsx
1 /*
2 * @Author: nevin
3 * @Date: 2025-02-10 22:20:15
4 * @LastEditTime: 2025-03-02 00:17:11
5 * @LastEditors: nevin
6 * @Description: 视频任务
7 */
8 import { Button, Card, Tag, Spin, message, Tooltip } from 'antd';
9 import { useState, useEffect, useRef } from 'react';
10 import { Task, TaskType, TaskVideo } from '@@/types/task';
11 import { taskApi } from '@/api/task';
12 import { TaskInfoRef } from './components/popInfo';
13 import TaskInfo from './components/videoInfo';
14 import styles from './videoTask.module.scss';
15 import {
16 ClockCircleOutlined,
17 TeamOutlined,
18 RightOutlined,
19 PlayCircleOutlined,
20 CopyOutlined,
21 } from '@ant-design/icons';
22 import dayjs from 'dayjs';
23 import VideoPlayer from '@/components/VideoPlayer';
24
25 // 导入平台图标
26 import KwaiIcon from '../../assets/svgs/account/ks.svg';
27 import WxSphIcon from '../../assets/svgs/account/wx-sph.svg';
28 import XhsIcon from '../../assets/svgs/account/xhs.svg';
29 import DouyinIcon from '../../assets/svgs/account/douyin.svg';
30
31 const FILE_BASE_URL = import.meta.env.VITE_APP_FILE_HOST;
32
33 // 平台类型映射
34 const PLATFORM_MAP = {
35 KWAI: { name: '快手', color: '#FF5000', icon: KwaiIcon },
36 wxSph: { name: '视频号', color: '#FA9A32', icon: WxSphIcon },
37 xhs: { name: '小红书', color: '#fe2c55', icon: XhsIcon },
38 douyin: { name: '抖音', color: '#000000', icon: DouyinIcon },
39 };
40
41 export default function Page() {
42 const [taskList, setTaskList] = useState<Task<TaskVideo>[]>([]);
43 const [pageInfo, setPageInfo] = useState({
44 pageSize: 10,
45 pageNo: 1,
46 totalCount: 0,
47 });
48 const [loading, setLoading] = useState(true);
49 const [hasMore, setHasMore] = useState(true);
50
51 const Ref_TaskInfo = useRef<TaskInfoRef>(null);
52
53 // 添加视频播放状态
54 const [videoPlayback, setVideoPlayback] = useState<{
55 visible: boolean;
56 url: string;
57 title: string;
58 }>({
59 visible: false,
60 url: '',
61 title: '',
62 });
63
64 async function getTaskList(isLoadMore = false) {
65 setLoading(true);
66 try {
67 const res = await taskApi.getTaskList<TaskVideo>({
68 ...pageInfo,
69 type: TaskType.VIDEO,
70 });
71
72 if (isLoadMore) {
73 setTaskList((prev) => [...prev, ...res.items]);
74 } else {
75 setTaskList(res.items);
76 }
77
78 setPageInfo((prev) => ({
79 ...prev,
80 totalCount: (res as any).totalCount,
81 }));
82
83 // 检查是否还有更多数据
84 setHasMore(pageInfo.pageNo * pageInfo.pageSize < (res as any).totalCount);
85 } catch (error) {
86 console.error('获取任务列表失败', error);
87 } finally {
88 setLoading(false);
89 }
90 }
91
92 useEffect(() => {
93 getTaskList();
94 }, []);
95
96 // 加载更多数据
97 const loadMore = () => {
98 setPageInfo((prev) => ({
99 ...prev,
100 pageNo: prev.pageNo + 1,
101 }));
102 getTaskList(true);
103 };
104
105 // 格式化日期
106 const formatDate = (dateString?: string) => {
107 if (!dateString) return '2025/03/17';
108 return dayjs(dateString).format('YYYY/MM/DD');
109 };
110
111 // 复制任务ID
112 const copyTaskId = (id: string, e: React.MouseEvent) => {
113 e.stopPropagation();
114 navigator.clipboard
115 .writeText(id)
116 .then(() => {
117 message.success('任务ID已复制到剪贴板');
118 })
119 .catch(() => {
120 message.error('复制失败,请手动复制');
121 });
122 };
123
124 // 渲染平台图标
125 const renderPlatformTags = (accountTypes?: string[]) => {
126 if (!accountTypes || accountTypes.length === 0) {
127 return <Tag color="#f50">全平台</Tag>;
128 }
129
130 return (
131 <div className={styles.platformIcons}>
132 {accountTypes.map((type) => {
133 const platform = (PLATFORM_MAP as any)[type];
134 if (!platform) return null;
135
136 return (
137 <Tooltip key={type} title={platform.name}>
138 <div
139 className={styles.platformIconWrapper}
140 style={{ backgroundColor: platform.color }}
141 >
142 <img
143 src={platform.icon}
144 className={styles.platformIcon}
145 alt={platform.name}
146 />
147 </div>
148 </Tooltip>
149 );
150 })}
151 </div>
152 );
153 };
154
155 // 打开视频播放器
156 const openVideoPlayer = (task: Task<TaskVideo>, e: React.MouseEvent) => {
157 e.stopPropagation();
158 if (task.dataInfo?.videoUrl) {
159 setVideoPlayback({
160 visible: true,
161 url: `${FILE_BASE_URL}${task.dataInfo.videoUrl}`,
162 title: task.title || '视频播放',
163 });
164 } else {
165 message.info('该任务暂无视频');
166 }
167 };
168
169 // 关闭视频播放器
170 const closeVideoPlayer = () => {
171 setVideoPlayback((prev) => ({ ...prev, visible: false }));
172 };
173
174 // 刷新任务列表的函数
175 const refreshTaskList = () => {
176 setPageInfo({
177 pageSize: 10,
178 pageNo: 1,
179 totalCount: 0,
180 });
181 getTaskList();
182 };
183
184 return (
185 <div className={styles.videoTaskContainer}>
186 <TaskInfo ref={Ref_TaskInfo} onTaskApplied={refreshTaskList} />
187
188 {/* 添加视频播放组件 */}
189 <VideoPlayer
190 videoUrl={videoPlayback.url}
191 visible={videoPlayback.visible}
192 onClose={closeVideoPlayer}
193 title={videoPlayback.title}
194 />
195
196 <div className={styles.taskList}>
197 {taskList.map((task) => (
198 <Card
199 key={task._id}
200 className={styles.taskCard}
201 styles={{ body: { padding: 0 } }}
202 >
203 <div className={styles.taskCardContent}>
204 <div
205 className={styles.taskImageContainer}
206 onClick={(e) => openVideoPlayer(task, e)}
207 >
208 <img
209 src={`${FILE_BASE_URL}${task.imageUrl}`}
210 alt={task.title}
211 className={styles.taskImage}
212 />
213 <div className={styles.videoOverlay}>
214 <PlayCircleOutlined className={styles.playIcon} />
215 </div>
216 </div>
217
218 <div className={styles.taskInfo}>
219 <div className={styles.taskHeader}>
220 <h3 className={styles.taskTitle}>
221 {task.title}
222 <span className={styles.taskId}>
223 ID: {task.id}
224 <Tooltip title="复制任务ID">
225 <CopyOutlined
226 className={styles.copyIcon}
227 onClick={(e) => copyTaskId(task._id, e)}
228 />
229 </Tooltip>
230 </span>
231 </h3>
232 <Tag color="#a66ae4" className={styles.taskTag}>
233 {(task.dataInfo as any)?.type || '视频任务'}
234 </Tag>
235 </div>
236
237 <div
238 className={styles.taskDescription}
239 dangerouslySetInnerHTML={{
240 __html: task.description || '不许删文',
241 }}
242 />
243
244 <div className={styles.taskDetails}>
245 <div className={styles.taskDetail}>
246 <ClockCircleOutlined className={styles.detailIcon} />
247 <span className={styles.detailLabel}>截止时间:</span>
248 <span className={styles.detailValue}>
249 {formatDate(task.deadline)}
250 </span>
251 </div>
252
253 <div className={styles.taskDetail}>
254 <TeamOutlined className={styles.detailIcon} />
255 <span className={styles.detailLabel}>招募人数:</span>
256 <span className={styles.detailValue}>
257 {task.maxRecruits || 100}
258 </span>
259 </div>
260 </div>
261
262 <div className={styles.taskRequirement}>
263 <span className={styles.requirementLabel}>任务要求:</span>
264 <span className={styles.requirementValue}>
265 {task.requirement || '不许删文'}
266 </span>
267 </div>
268
269 <div className={styles.platformContainer}>
270 <span className={styles.platformLabel}>可用平台:</span>
271 {renderPlatformTags(task.accountTypes)}
272 </div>
273 </div>
274
275 <div className={styles.taskAction}>
276 <div className={styles.taskStatus}>
277 <Tag color="#a66ae4">进行中</Tag>
278 </div>
279
280 <div className={styles.taskReward}>
281 <span className={styles.rewardLabel}>每篇可赚</span>
282 <span className={styles.rewardValue}>
283 ¥{task.reward || 5}
284 </span>
285 </div>
286
287 <Button
288 type="primary"
289 className={styles.viewButton}
290 onClick={() => Ref_TaskInfo.current?.init(task)}
291 style={{ backgroundColor: '#a66ae4', borderColor: '#a66ae4' }}
292 >
293 去查看 <RightOutlined />
294 </Button>
295 </div>
296 </div>
297 </Card>
298 ))}
299
300 {loading && (
301 <div className={styles.loadingContainer}>
302 <Spin size="large" />
303 </div>
304 )}
305
306 {!loading && hasMore && (
307 <div className={styles.loadMoreContainer}>
308 <Button onClick={loadMore}>查看更多任务</Button>
309 </div>
310 )}
311 </div>
312 </div>
313 );
314 }
315
315 lines Plain Text