| 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 |