返回 AiToEarn
task.tsx
1 /*
2 * @Author: nevin
3 * @Date: 2025-02-10 22:20:15
4 * @LastEditTime: 2025-02-28 21:36:53
5 * @LastEditors: nevin
6 * @Description: 任务页面
7 */
8 import { useState, useEffect, useRef } from 'react';
9 import {
10 HistoryOutlined,
11 WalletOutlined,
12 CommentOutlined,
13 UserOutlined,
14 ClockCircleOutlined,
15 CheckCircleOutlined,
16 } from '@ant-design/icons';
17 import {
18 Card,
19 List,
20 Typography,
21 Button,
22 Space,
23 Tag,
24 Spin,
25 Modal,
26 Descriptions,
27 message,
28 Progress,
29 Image,
30 notification,
31 Row,
32 Col,
33 Carousel,
34 } from 'antd';
35 import { useInView } from 'react-intersection-observer';
36 import styles from './task.module.scss';
37
38 // 导入现有的任务组件
39 import MineTask from './mineTask';
40 // import TaskInfo from './components/TaskInfo';
41 import TaskInfo from './components/popInfo';
42 // 移除 InteractionTask 导入
43 // import InteractionTask from './interactionTask';
44
45 import { useNavigate } from 'react-router-dom';
46 import { taskApi } from '@/api/task';
47 import { TaskType, TaskVideo, TaskTypeName } from '@@/types/task';
48 import dayjs from 'dayjs';
49 import { TaskInfoRef } from './components/popInfo';
50 import ChooseAccountModule from '@/views/publish/components/ChooseAccountModule/ChooseAccountModule';
51 import { PubType } from '@@/publish/PublishEnum';
52 import { icpCreateInteractionOneKey } from '@/icp/replyother';
53 import { onInteractionProgress } from '../../icp/receiveMsg';
54 import {
55 icpCreatePubRecord,
56 icpCreateImgTextPubRecord,
57 icpPubImgText,
58 } from '@/icp/publish';
59 import { usePubStroe } from '@/store/pubStroe';
60 import { useCommontStore } from '@/store/commont';
61
62 // 导入平台图标
63 import KwaiIcon from '../../assets/svgs/account/ks.svg';
64 import WxSphIcon from '../../assets/svgs/account/wx-sph.svg';
65 import XhsIcon from '../../assets/svgs/account/xhs.svg';
66 import DouyinIcon from '../../assets/svgs/account/douyin.svg';
67 import logo from '@/assets/logo.png';
68 import { useImagePageStore } from '../publish/children/imagePage/useImagePageStore';
69 import { useShallow } from 'zustand/react/shallow';
70
71 const { Title, Text } = Typography;
72
73 const FILE_BASE_URL = import.meta.env.VITE_APP_FILE_HOST;
74
75 // 平台配置
76 const platformConfig = {
77 KWAI: {
78 name: '快手',
79 icon: KwaiIcon,
80 color: '#FF4D4F',
81 },
82 wxSph: {
83 name: '微信视频号',
84 icon: WxSphIcon,
85 color: '#07C160',
86 },
87 xhs: {
88 name: '小红书',
89 icon: XhsIcon,
90 color: '#FF2442',
91 },
92 douyin: {
93 name: '抖音',
94 icon: DouyinIcon,
95 color: '#000000',
96 },
97 };
98
99 // 任务类型定义
100 interface Task {
101 id: string;
102 title: string;
103 price: number;
104 originalPrice?: number;
105 discount?: number;
106 image: string;
107 likes: number;
108 views: number;
109 level: string;
110 }
111
112 export default function Task() {
113 const navigate = useNavigate();
114 // 当前选中的任务类型
115 const [activeTab, setActiveTab] = useState('interaction');
116
117 // 互动任务相关状态
118 const [loading, setLoading] = useState(false);
119 const [taskList, setTaskList] = useState<any[]>([]);
120 const [pageInfo, setPageInfo] = useState({
121 page: 1,
122 pageSize: 20,
123 totalCount: 0,
124 });
125 const [hasMore, setHasMore] = useState(true);
126 const [isOne, setIsOne] = useState(false);
127 const selectedTaskRef = useRef<any>(null);
128 const [selectedTask, setSelectedTask] = useState<any>(null);
129 const [modalVisible, setModalVisible] = useState(false);
130 const [chooseAccountOpen, setChooseAccountOpen] = useState(false);
131 const [accountListChoose, setAccountListChoose] = useState<any[]>([]);
132 const [downloading, setDownloading] = useState(false);
133 const Ref_TaskInfo = useRef<TaskInfoRef>(null);
134 const [pubProgressModuleOpen, setPubProgressModuleOpen] = useState(false);
135 const [htmlContent, setHtmlContent] = useState<string>('');
136 const [htmlModalVisible, setHtmlModalVisible] = useState(false);
137
138 // 使用 react-intersection-observer 监听底部元素
139 const { ref: loadMoreRef, inView } = useInView({
140 threshold: 0.1,
141 triggerOnce: false,
142 rootMargin: '100px 0px',
143 });
144
145 // 当底部元素进入视图时加载更多数据
146 useEffect(() => {
147 if (inView && hasMore && !loading && activeTab === 'interaction') {
148 loadMore();
149 }
150 }, [inView, hasMore, loading, activeTab]);
151
152 // 加载更多数据
153 const loadMore = async () => {
154 console.log('loadMore方法被调用');
155 if (loading || !hasMore) {
156 console.log('loadMore被阻止: loading=', loading, 'hasMore=', hasMore);
157 return;
158 }
159
160 setLoading(true);
161 try {
162 const nextPage = pageInfo.page + 1;
163 console.log('加载下一页:', nextPage);
164 setPageInfo((prev) => ({ ...prev, page: nextPage }));
165 await getTaskList(true);
166 } catch (error) {
167 console.error('加载更多失败:', error);
168 } finally {
169 setLoading(false);
170 }
171 };
172
173 // 获取任务列表
174 async function getTaskList(isLoadMore = false) {
175 setLoading(true);
176 try {
177 const res = await taskApi.getTaskList<any>(pageInfo);
178
179 if (isLoadMore) {
180 setTaskList((prev) => [...prev, ...res.items]);
181 } else {
182 setTaskList(res.items);
183 }
184
185 setPageInfo((prev) => ({
186 ...prev,
187 totalCount: (res as any).meta.totalItems,
188 }));
189
190 // 恢复设置 hasMore 的代码
191 const totalCount = (res as any).meta.totalItems || 0;
192 const currentPage = (res as any).meta.currentPage;
193 const pageSize = pageInfo.pageSize;
194 const hasMoreItems = currentPage * pageSize < totalCount;
195
196 console.log('计算hasMore:', {
197 totalCount,
198 currentPage,
199 pageSize,
200 hasMoreItems,
201 });
202
203 setHasMore(hasMoreItems);
204 } catch (error) {
205 console.error('获取任务列表失败', error);
206 } finally {
207 setLoading(false);
208 }
209 }
210
211 // 初始加载数据
212 useEffect(() => {
213 // if (activeTab === 'interaction') {
214 // setPageInfo({
215 // page: 1,
216 // pageSize: 2,
217 // totalCount: 0,
218 // });
219 // getTaskList();
220 // }
221 }, [activeTab]);
222
223 // 任务进度监听
224 useEffect(() => {
225 const unload = onInteractionProgress((args) => {
226 if (args.status === 1) {
227 taskDone();
228 notification.open({
229 message: '互动任务完成',
230 });
231 }
232 });
233 return () => {
234 unload();
235 };
236 }, []);
237
238 const formatDate = (date: string) => {
239 return dayjs(date).format('YYYY-MM-DD HH:mm');
240 };
241
242 const getPlatformTags = (accountTypes: string[]) => {
243 if (!accountTypes || accountTypes.length === 0) return null;
244
245 return accountTypes.map((type) => {
246 const platform = platformConfig[type as keyof typeof platformConfig];
247 if (!platform) return null;
248
249 return (
250 <div
251 key={type}
252 className={styles.platformIconWrapper}
253 style={{ backgroundColor: platform.color }}
254 >
255 <img
256 src={platform.icon}
257 className={styles.platformIcon}
258 alt={platform.name}
259 />
260 </div>
261 );
262 });
263 };
264
265 const { setCommonPubParams, setImages } = useImagePageStore(
266 useShallow((state) => ({
267 setCommonPubParams: state.setCommonPubParams,
268 setImages: state.setImages,
269 })),
270 );
271 // TODO 完善跳转逻辑
272 const handleJoinTask = (task: any) => {
273 // if (task.isAccepted) {
274 // setActiveTab('mine')
275 // return
276 // }
277
278 // setCommonPubParams({
279 // title: "标题1",
280 // describe: "描述1",
281 // topics: ["话题1","话题2"],
282 // });
283 // navigate('/publish/image');
284 // return;
285 // console.log('task@:', task);
286 setSelectedTask(task);
287
288 // 根据任务类型选择不同的处理逻辑
289 if (task.type === TaskType.ARTICLE || task.type === TaskType.INTERACTION) {
290 // 文章任务和互动任务使用模态框
291 setModalVisible(true);
292 } else {
293 // 其他任务使用 TaskInfo 组件
294 Ref_TaskInfo.current?.init(task);
295 }
296 };
297
298 const handleCompleteTask = async () => {
299 if (!selectedTask) return;
300 setModalVisible(false);
301
302 // 根据任务类型选择不同的处理逻辑
303 if (selectedTask.type === TaskType.ARTICLE) {
304 // 文章任务使用 逻辑
305 setChooseAccountOpen(true);
306 } else {
307 // 其他任务使用原有的互动任务逻辑
308 setChooseAccountOpen(true);
309 }
310 };
311
312 // 在组件内添加一个新的状态来存储任务记录
313 const [taskRecord, setTaskRecord] = useState<{
314 _id: string;
315 createTime: string;
316 isFirstTimeSubmission: boolean;
317 status: string;
318 taskId: string;
319 } | null>(null);
320
321 /**
322 * 接受任务
323 */
324 async function taskApply(params: any) {
325 // console.log('taskApply执行:', selectedTask);
326 const sucai: any = await taskApi.getFristTaskMaterial(selectedTask?._id);
327 console.log('sucai:', sucai);
328 // return;
329 // 00.00 测试
330 if (!selectedTask) return;
331
332 try {
333 // 00.00 测试
334 const res: any = await taskApi.taskApply<TaskVideo>(selectedTask?._id, {
335 account: params.account,
336 accountType: params.accountType,
337 uid: params.uid,
338 taskMaterialId: sucai.id,
339 });
340
341 // const res: any = {
342 // code: 0,
343 // data: {
344 // }
345 // }
346
347 // 存储任务记录信息 00.00
348 // console.log('jieshou :', res);
349 if (res.code == 0 && res.data) {
350 setTaskRecord(res.data);
351 message.success('任务接受成功!');
352
353 // handleCompleteTask();
354
355 // console.log('selectedTask.dataInfo', selectedTask.dataInfo);
356
357 // pubCore(params);
358
359 let imageList = [];
360 for (let index = 0; index < sucai.imageList.length; index++) {
361 let element = sucai.imageList[index];
362 imageList.push({
363 id: '' + index,
364 // 前端临时路径,注意不要存到数据库
365 imgUrl: import.meta.env.VITE_APP_FILE_HOST + element.imageUrl,
366 filename: import.meta.env.VITE_APP_FILE_HOST + element.imageUrl,
367 // 图片在硬盘上的路径
368 imgPath: import.meta.env.VITE_APP_FILE_HOST + element.imageUrl,
369 });
370 }
371 console.log('imageList', imageList);
372
373 if (selectedTask.type == TaskType.ARTICLE) {
374 setCommonPubParams({
375 title: sucai.title || selectedTask.dataInfo?.title,
376 describe: sucai.desc || selectedTask.dataInfo?.desc,
377 topics: selectedTask.dataInfo?.topicList || [],
378 // images: imageList as any[],
379 });
380
381 setImages(imageList as any[]);
382 navigate('/publish/image');
383 }
384
385 // return;
386 } else {
387 message.error(res.msg || '接受任务失败,请稍后再试?');
388 }
389 } catch (error) {
390 message.error('接受任务失败,请稍后再试');
391 }
392 }
393
394 async function isoneFunc(params: any) {
395 if (params) {
396 await setIsOne(true);
397 } else {
398 await setIsOne(false);
399 }
400 setChooseAccountOpen(true);
401 }
402
403 async function taskApplyoney(params: any) {
404 console.log('------ taskApplyoney', selectedTask);
405 if (!selectedTask) return;
406
407 try {
408 const res: any = await taskApi.taskApply<TaskVideo>(selectedTask?._id, {
409 account: params.account,
410 accountType: params.accountType,
411 uid: params.uid,
412 });
413 // 存储任务记录信息 00.00
414 // console.log('jieshou :', res);
415 if (res.code == 0 && res.data) {
416 setTaskRecord(res.data);
417 setModalVisible(false);
418 message.success('任务接受成功!');
419 } else {
420 message.error(res.msg || '接受任务失败,请稍后再试?');
421 }
422 } catch (error) {
423 message.error('接受任务失败,请稍后再试');
424 }
425 }
426
427 useEffect(() => {
428 selectedTaskRef.current = selectedTask;
429 }, [selectedTask]);
430
431 /**
432 * 完成任务
433 */
434 async function taskDone(url?: string, taskRecordId?: string) {
435 console.log('taskDone执行:', selectedTaskRef.current);
436 if (!selectedTaskRef.current) return;
437 const selectedTask = selectedTaskRef.current;
438 if (!selectedTask) {
439 console.error(
440 '任务信息不完整,无法完成任务',
441 selectedTask,
442 '11:',
443 taskRecord,
444 );
445 return;
446 }
447
448 try {
449 // 使用任务记录的 ID 而不是任务 ID
450 console.log('taskRecordId', taskRecordId);
451 const res = await taskApi.taskDone(taskRecordId || taskRecord!._id, {
452 submissionUrl: url || selectedTask.title,
453 screenshotUrls: [selectedTask.dataInfo?.imageList?.[0] || ''],
454 qrCodeScanResult: selectedTask.title,
455 });
456 message.success('任务发布成功!');
457 getTaskList();
458 } catch (error) {
459 message.error('完成任务失败,请稍后再试');
460 }
461 }
462
463 // 文章任务的发布核心逻辑
464 const pubCore = async (account: any) => {
465 const sucai: any = await taskApi.getFristTaskMaterial(selectedTask?._id);
466 console.log('sucai:', sucai);
467 if (!selectedTask) return;
468
469 const taskApplyRes: any = await taskApi.taskApply<TaskVideo>(
470 selectedTask?._id,
471 {
472 account: account.account,
473 accountType: account.type,
474 uid: account.uid,
475 taskMaterialId: sucai.id,
476 },
477 );
478 // 存储任务记录信息 00.00
479 console.log('taskApplyRes', taskApplyRes);
480 if (taskApplyRes.code == 0 && taskApplyRes.data) {
481 console.log('taskApplyRes.data', taskApplyRes.data);
482 setTaskRecord(taskApplyRes.data);
483
484 message.success('任务接受成功!');
485 } else {
486 message.error(taskApplyRes.msg || '接受任务失败,请稍后再试?');
487 return false;
488 }
489
490 setPubProgressModuleOpen(true);
491 setLoading(true);
492 const err = () => {
493 setLoading(false);
494 message.error('网络繁忙,请稍后重试!');
495 };
496
497 // 00.00 测试
498 // console.log('1', selectedTask);
499 // return;
500
501 // topics: selectedTask.dataInfo?.topicList || [],
502
503 // 创建一级记录
504 const recordRes = await icpCreatePubRecord({
505 title: sucai.title || selectedTask.dataInfo?.title,
506 desc: sucai.desc || selectedTask.dataInfo?.desc,
507 type: PubType.ImageText,
508 coverPath: FILE_BASE_URL + (sucai.coverUrl || ''),
509 });
510 if (!recordRes) return err();
511
512 let pubList = [];
513 console.log('sucai.imageList', sucai.imageList);
514 if (sucai.imageList.length) {
515 pubList = sucai.imageList.map((v: any) => {
516 console.log('v', v);
517 return FILE_BASE_URL + v.imageUrl;
518 });
519 }
520
521 console.log('pubList', pubList);
522 console.log('accountListChoose', accountListChoose);
523
524 const allAccount = accountListChoose?.length
525 ? accountListChoose
526 : [account];
527 console.log('allAccount', allAccount);
528
529 for (const account of allAccount) {
530 // 创建二级记录
531 await icpCreateImgTextPubRecord({
532 title: sucai.title || selectedTask.dataInfo?.title,
533 desc: sucai.desc || selectedTask.dataInfo?.desc,
534 type: account.type,
535 topics: selectedTask.dataInfo?.topicList || [],
536 accountId: account.id,
537 pubRecordId: recordRes.id,
538 publishTime: new Date(),
539 coverPath: FILE_BASE_URL + (sucai.coverUrl || ''),
540 imagesPath: pubList,
541 });
542 }
543
544 const okRes = await icpPubImgText(recordRes.id);
545
546 console.log('okRes', okRes);
547
548 if (okRes.length > 0) {
549 for (let itemT of okRes) {
550 let thisling = itemT.previewVideoLink || itemT.dataId;
551 console.log('itemT.previewVideoLink', itemT.previewVideoLink)
552 taskDone(thisling, taskApplyRes.data.id);
553 }
554 }
555
556 setLoading(false);
557 setPubProgressModuleOpen(false);
558 setModalVisible(false);
559 usePubStroe.getState().clearImgTextPubSave();
560 const successList = okRes.filter((v) => v.code === 1);
561 useCommontStore.getState().notification!.open({
562 message: '发布结果',
563 description: (
564 <>
565 一共发布 {okRes.length} 条数据,成功 {successList.length} 条,失败{' '}
566 {okRes.length - successList.length} 条
567 </>
568 ),
569 showProgress: true,
570 actions: [
571 <Button
572 key="view"
573 type="primary"
574 size="small"
575 onClick={() => {
576 navigate('/publish/pubRecord');
577 }}
578 >
579 查看发布记录
580 </Button>,
581 ],
582 key: Date.now(),
583 });
584 };
585
586 const handleInteraction = async (account: any) => {
587 console.log('account', account.id);
588 console.log('selectedTask', selectedTask.dataInfo);
589 console.log('selectedTask.description', selectedTask.description);
590 console.log('selectedTask.accountTypes', account.type);
591
592 const option: any = {
593 platform: account.type,
594 };
595
596 if (selectedTask.dataInfo?.commentContent) {
597 option.commentContent = selectedTask.dataInfo?.commentContent;
598 }
599
600 try {
601 setLoading(true);
602 const res: any = await icpCreateInteractionOneKey(
603 account.id,
604 [
605 {
606 dataId: selectedTask.dataInfo?.worksId,
607 readCount: 0,
608 likeCount: 0,
609 collectCount: 0,
610 forwardCount: 0,
611 commentCount: 0, // 评论数量
612 income: 0,
613 title: selectedTask.dataInfo?.title || '',
614 desc: selectedTask.dataInfo?.title || '',
615 authorId: selectedTask.dataInfo?.authorId || '',
616 author: {
617 id: selectedTask.dataInfo?.authorId || '',
618 },
619 option: {
620 xsec_token: 'ABQgeOn-14sjhmCALp9dEISLZrOOyDdGZwKtr2umjsWeo=',
621 },
622 },
623 ],
624 option,
625 );
626
627 console.log('handleInteraction', 'res', res);
628
629 // if (res.code === 1) {
630 // message.success('互动任务完成成功');
631 // // 更新任务状态
632 // setTaskList(prev => prev.map(task =>
633 // task._id === selectedTask._id ? { ...task, isAccepted: true } : task
634 // ));
635 // } else {
636 // message.error('互动任务完成失败');
637 // }
638 } catch (error) {
639 console.error('互动任务失败', error);
640 message.error('互动任务失败,请重试');
641 } finally {
642 setLoading(false);
643 setChooseAccountOpen(false);
644 }
645 };
646
647 // 处理账号选择确认
648 const handleAccountConfirm = async (aList: any[]) => {
649 console.log('账号:', aList);
650 setAccountListChoose(aList);
651 setChooseAccountOpen(false);
652
653 // 根据任务类型选择不同的处理逻辑
654 if (selectedTask?.type === TaskType.ARTICLE) {
655 // 文章任务使用逻辑
656 console.log('文章任务使用逻辑');
657 if (isOne) {
658 for (const account of aList) {
659 taskApplyoney({
660 account: account.account,
661 accountType: account.type,
662 uid: account.uid,
663 });
664 }
665 } else {
666 for (const account of aList) {
667 // taskApply({
668 // account: account.account,
669 // accountType: account.type,
670 // uid: account.uid,
671 // });
672
673 await pubCore(account);
674 }
675 }
676 // 00.00 测试
677 } else {
678 // 其他任务使用原有的互动任务逻辑
679 await handleInteraction(aList[0]);
680 }
681 };
682
683 // 刷新任务列表的函数
684 const refreshTaskList = () => {
685 setPageInfo({
686 pageSize: 20,
687 page: 1,
688 totalCount: 0,
689 });
690 getTaskList();
691 };
692
693 // 渲染对应的任务内容
694 const renderTaskContent = () => {
695 switch (activeTab) {
696 case 'mine':
697 return <MineTask />;
698 case 'interaction':
699 return renderInteractionTask();
700 default:
701 return renderInteractionTask();
702 }
703 };
704
705 // 渲染互动任务内容
706 const renderInteractionTask = () => {
707 return (
708 <div className={styles.taskList}>
709 <ChooseAccountModule
710 open={chooseAccountOpen}
711 onClose={() => !downloading && setChooseAccountOpen(false)}
712 platChooseProps={{
713 choosedAccounts: accountListChoose,
714 pubType: PubType.ImageText,
715 allowPlatSet: new Set(selectedTask?.accountTypes || []) as any,
716 }}
717 onPlatConfirm={handleAccountConfirm}
718 />
719
720 <Spin spinning={loading}>
721 <List
722 grid={{
723 gutter: 8,
724 xs: 1,
725 sm: 2,
726 md: 3,
727 lg: 4,
728 xl: 5,
729 xxl: 6,
730 }}
731 dataSource={taskList}
732 renderItem={(item) => (
733 <List.Item>
734 <Card
735 className={styles.taskCard}
736 variant="outlined"
737 cover={
738 <div className={styles.taskImage}>
739 <Image
740 src={
741 item.imageUrl
742 ? FILE_BASE_URL + item.imageUrl
743 : item.dataInfo?.imageList?.length
744 ? FILE_BASE_URL + item.dataInfo.imageList[0]
745 : logo
746 }
747 alt="logo"
748 preview={false}
749 style={{
750 width: '100%',
751 height: '200px',
752 objectFit: 'contain',
753 }}
754 />
755 <div
756 style={{
757 position: 'absolute',
758 top: '10px',
759 right: '10px',
760 zIndex: 1,
761 }}
762 >
763 <Tag color="blue">
764 {TaskTypeName.get(item.type as TaskType) ||
765 '未知任务'}
766 </Tag>
767 </div>
768 </div>
769 }
770 actions={[
771 // <Space key="recruits">
772 // <UserOutlined />
773 // <Text>
774 // {item.currentRecruits}
775 // {/* /{item.maxRecruits} */}
776 // </Text>
777 // </Space>,
778 // <Space key="time">
779 // <ClockCircleOutlined />
780 // <Text>{item.keepTime}分钟</Text>
781 // </Space>,
782 <Button
783 type="primary"
784 key="join"
785 // disabled={item.isAccepted}
786 onClick={() => handleJoinTask(item)}
787 // onClick={() => testSseFunc(item)}
788 style={{ minWidth: '120px' }}
789 >
790 {/* {item.isAccepted ? '去完成任务' : '参与任务'} */}
791 参与任务
792 </Button>,
793 ]}
794 >
795 <Card.Meta
796 title={
797 <div className={styles.taskTitle}>
798 <Title level={5}>{item.title}</Title>
799 <Space>
800 <Tag
801 color="green"
802 style={{ fontSize: '16px', padding: '1px 18px' }}
803 >
804 ¥{item.reward}
805 </Tag>
806 <Space size={4}>
807 {getPlatformTags(item.accountTypes)}
808 </Space>
809 </Space>
810 </div>
811 }
812 description={
813 <div className={styles.taskInfo}>
814 <div className={styles.taskProgress}>
815 {/* <Progress
816 percent={Math.round(
817 (item.currentRecruits / item.maxRecruits) * 100,
818 )}
819 size="small"
820 showInfo={false}
821 /> */}
822 </div>
823 <div
824 dangerouslySetInnerHTML={{
825 __html: item.description,
826 }}
827 className={styles.taskDescription}
828 />
829 {/* <Text type="secondary">
830 {item.description}
831 </Text> */}
832 {/* <div className={styles.taskDeadline}>
833 <Text type="secondary">
834 截止时间:{formatDate(item.deadline)}
835 </Text>
836 </div> */}
837 </div>
838 }
839 />
840 </Card>
841 </List.Item>
842 )}
843 />
844 </Spin>
845
846 {/* 底部加载更多触发器 */}
847 <div
848 ref={loadMoreRef}
849 className={styles.loadMoreTrigger}
850 style={{ height: '50px', marginTop: '20px' }}
851 >
852 {hasMore ? (
853 <div className={styles.loadMoreContainer}>
854 <Button type="link" loading={loading} onClick={loadMore}>
855 {loading ? '加载中...' : '加载更多'}
856 </Button>
857 </div>
858 ) : (
859 <div className={styles.loadMoreContainer}>
860 <Text style={{ color: '#999' }}>没有更多任务了</Text>
861 </div>
862 )}
863 </div>
864
865 <Modal
866 title="任务详情"
867 open={modalVisible}
868 onCancel={() => setModalVisible(false)}
869 footer={[
870 // <Button key="cancel" onClick={() => isoneFunc(true)}>
871 // 领取
872 // </Button>,
873 <Button
874 key="complete"
875 type="primary"
876 icon={<CheckCircleOutlined />}
877 onClick={() => {
878 isoneFunc(false);
879 }}
880 >
881 认可内容,自愿完成
882 </Button>,
883 ]}
884 width={700}
885 >
886 {selectedTask && (
887 <div className={styles.taskDetail}>
888 <Row gutter={[16, 6]}>
889 <Col span={24}>
890 <div className={styles.taskDetailHeader}>
891 <Title level={4}>{selectedTask.title}</Title>
892 <Space>
893 <Tag color="blue">
894 {TaskTypeName.get(selectedTask.type as TaskType) ||
895 '未知任务'}
896 </Tag>
897 <Tag color="green">赚 ¥{selectedTask.reward}</Tag>
898 </Space>
899 </div>
900 </Col>
901
902 {/* 图片轮播展示 */}
903 {selectedTask.dataInfo?.imageList &&
904 selectedTask.dataInfo.imageList.length > 0 && (
905 <Col span={24}>
906 <div className={styles.bannerContainer}>
907 <Carousel
908 autoplay
909 dots={true}
910 arrows={true}
911 className={styles.taskBanner}
912 dotPosition="bottom"
913 >
914 {selectedTask.dataInfo.imageList.map(
915 (image: string, index: number) => (
916 <div key={index} className={styles.bannerItem}>
917 <Image
918 src={FILE_BASE_URL + image}
919 alt={`任务图片 ${index + 1}`}
920 preview={false}
921 className={styles.bannerImage}
922 />
923 </div>
924 ),
925 )}
926 </Carousel>
927 </div>
928 </Col>
929 )}
930
931 <Col span={24}>
932 {/* <Divider orientation="left">任务信息</Divider> */}
933 <Descriptions column={1} bordered>
934 {selectedTask.dataInfo?.title != '' && (
935 <Descriptions.Item label="发布标题">
936 <div
937 dangerouslySetInnerHTML={{
938 __html: selectedTask.dataInfo?.title,
939 }}
940 className={styles.taskDescription}
941 />
942 </Descriptions.Item>
943 )}
944
945 {selectedTask.dataInfo?.desc && (
946 <Descriptions.Item label="发布描述">
947 <div
948 dangerouslySetInnerHTML={{
949 __html:
950 selectedTask.dataInfo?.desc ||
951 '' +
952 (selectedTask.dataInfo?.topicList?.length > 0
953 ? '<span style="color: #999; font-size: 12px; margin-left: 8px;">#' +
954 selectedTask.dataInfo.topicList.join(' #') +
955 '</span>'
956 : ''),
957 }}
958 className={styles.taskDescription}
959 />
960 </Descriptions.Item>
961 )}
962
963 {selectedTask.type !== TaskType.ARTICLE && (
964 <Descriptions.Item label="评论内容">
965 {selectedTask.dataInfo?.commentContent || 'AI智能评论'}
966 </Descriptions.Item>
967 )}
968
969 {selectedTask.dataInfo?.worksId && (
970 <Descriptions.Item label="作品ID">
971 {selectedTask.dataInfo?.worksId || ''}
972 </Descriptions.Item>
973 )}
974
975 {/* <Descriptions.Item label="任务时长">
976 {selectedTask.keepTime}分钟
977 </Descriptions.Item> */}
978
979 {/* <Descriptions.Item label="起止时间">
980 {formatDate(selectedTask.createTime)} - {formatDate(selectedTask.deadline)}
981 </Descriptions.Item> */}
982
983 {/* <Descriptions.Item label="截止时间">
984 {formatDate(selectedTask.deadline)}
985 </Descriptions.Item> */}
986 {/* <Descriptions.Item label="参与人数">
987 <Progress
988 percent={Math.round(
989 (selectedTask.currentRecruits /
990 selectedTask.maxRecruits) *
991 100,
992 )}
993 size="small"
994 format={() =>
995 `${selectedTask.currentRecruits}/${selectedTask.maxRecruits}`
996 }
997 />
998 </Descriptions.Item> */}
999 <Descriptions.Item label="支持平台">
1000 <Space size={4}>
1001 {getPlatformTags(selectedTask.accountTypes)}
1002 </Space>
1003 </Descriptions.Item>
1004 </Descriptions>
1005 </Col>
1006 </Row>
1007 </div>
1008 )}
1009 </Modal>
1010
1011 {/* 添加 TaskInfo 组件 */}
1012 <TaskInfo ref={Ref_TaskInfo} onTaskApplied={refreshTaskList} />
1013 </div>
1014 );
1015 };
1016
1017 // 添加 SSE 处理方法
1018 const testSseFunc = async (item: any) => {
1019 try {
1020 setHtmlContent(''); // 清空之前的内容
1021 setHtmlModalVisible(true); // 显示模态框
1022 const response = await fetch(
1023 import.meta.env.VITE_APP_URL + '/tools/ai/article/html/sse',
1024 {
1025 method: 'POST',
1026 headers: {
1027 'Content-Type': 'application/json',
1028 },
1029 body: JSON.stringify({
1030 content: '生成一个卡通人物介绍页 带有图片 小红书图文流光卡片样式',
1031 }),
1032 },
1033 );
1034
1035 if (!response.ok) {
1036 throw new Error(`HTTP error! status: ${response.status}`);
1037 }
1038
1039 const reader = response.body?.getReader();
1040 if (!reader) {
1041 throw new Error('无法获取响应流');
1042 }
1043
1044 let htmlString = '';
1045 let isCollectingHtml = false;
1046 let buffer = '';
1047
1048 // 处理响应流
1049 while (true) {
1050 const { done, value } = await reader.read();
1051 if (done) {
1052 console.log('流读取完成');
1053 setHtmlContent(htmlString); // 设置最终的 HTML 内容
1054 break;
1055 }
1056
1057 // 将 Uint8Array 转换为文本
1058 const text = new TextDecoder().decode(value);
1059 buffer += text;
1060
1061 // 处理缓冲区中的完整行
1062 const lines = buffer.split('\n');
1063 buffer = lines.pop() || ''; // 保留最后一个不完整的行
1064
1065 for (const line of lines) {
1066 if (!line.trim()) continue;
1067
1068 // 检查是否是 data 行
1069 if (line.startsWith('data:')) {
1070 const data = line.slice(5).trim();
1071
1072 // 检查是否包含 ``` 标记
1073 if (data.includes('```')) {
1074 isCollectingHtml = !isCollectingHtml;
1075 continue;
1076 }
1077
1078 // 如果正在收集 HTML,则添加到结果中
1079 if (isCollectingHtml) {
1080 htmlString += data;
1081 }
1082 }
1083 }
1084 }
1085 } catch (error) {
1086 console.error('请求失败:', error);
1087 message.error('连接失败,请稍后重试');
1088 setHtmlModalVisible(false); // 发生错误时关闭模态框
1089 }
1090 };
1091
1092 return (
1093 <div className={styles.taskPageContainer}>
1094 {/* 顶部导航栏 */}
1095 <div className={styles.taskHeader}>
1096 <div className={styles.taskHeaderLeft}>
1097 {/* <div
1098 className={`${styles.taskButton} ${activeTab === 'car' ? styles.activeTaskButton : ''}`}
1099 onClick={() => setActiveTab('car')}
1100 >
1101 <ShoppingCartOutlined />
1102 <span>挂车市场任务</span>
1103 </div>
1104 <div
1105 className={`${styles.taskButton} ${activeTab === 'pop' ? styles.activeTaskButton : ''}`}
1106 onClick={() => setActiveTab('pop')}
1107 >
1108 <ShareAltOutlined />
1109 <span>推广任务</span>
1110 </div>
1111 <div
1112 className={`${styles.taskButton} ${activeTab === 'video' ? styles.activeTaskButton : ''}`}
1113 onClick={() => setActiveTab('video')}
1114 >
1115 <VideoCameraOutlined />
1116 <span>视频任务</span>
1117 </div> */}
1118 {/* <div
1119 className={`${styles.taskButton} ${activeTab === 'article' ? styles.activeTaskButton : ''}`}
1120 onClick={() => setActiveTab('article')}
1121 >
1122 <FileTextOutlined />
1123 <span>文章任务</span>
1124 </div> */}
1125 <div
1126 className={`${styles.taskButton} ${activeTab === 'interaction' ? styles.activeTaskButton : ''}`}
1127 onClick={() => setActiveTab('interaction')}
1128 >
1129 <CommentOutlined />
1130 <span>任务市场</span>
1131 </div>
1132 <div
1133 className={`${styles.taskButton} ${activeTab === 'mine' ? styles.activeTaskButton : ''}`}
1134 onClick={() => setActiveTab('mine')}
1135 >
1136 <HistoryOutlined />
1137 <span>已参与过任务</span>
1138 </div>
1139 </div>
1140 <div className={styles.taskHeaderRight}>
1141 <div
1142 className={styles.withdrawText}
1143 onClick={() => navigate('/finance')}
1144 >
1145 <WalletOutlined />
1146 <span>钱包</span>
1147 </div>
1148 </div>
1149 </div>
1150
1151 {/* 任务内容 */}
1152 <div className={styles.taskContent}>{renderTaskContent()}</div>
1153
1154 {/* HTML 预览模态框 */}
1155 <Modal
1156 title="HTML 预览"
1157 open={htmlModalVisible}
1158 onCancel={() => setHtmlModalVisible(false)}
1159 width="80%"
1160 footer={[
1161 <Button key="close" onClick={() => setHtmlModalVisible(false)}>
1162 关闭
1163 </Button>,
1164 ]}
1165 bodyStyle={{
1166 height: '70vh',
1167 overflow: 'auto',
1168 padding: '20px',
1169 }}
1170 >
1171 {htmlContent ? (
1172 <div
1173 className={styles.htmlPreview}
1174 dangerouslySetInnerHTML={{ __html: htmlContent }}
1175 />
1176 ) : (
1177 <div style={{ textAlign: 'center', padding: '20px' }}>
1178 <Spin tip="正在生成内容..." />
1179 </div>
1180 )}
1181 </Modal>
1182 </div>
1183 );
1184 }
1185
1185 lines Plain Text