返回 AiToEarn
replyother.tsx
根目录 / project / aitoearn-electron / src / views / replyother / replyother.tsx
1 import {
2 icpGetCommentListByOther,
3 icpGetSecondCommentListByOther,
4 WorkData,
5 CommentData,
6 getCommentSearchNotes,
7 ipcGetInteractionRecordList,
8 ipcGetAutoRunOfInteractionInfo,
9 icpDianzanDyOther,
10 icpShoucangDyOther,
11 } from '@/icp/replyother';
12 import {
13 Avatar,
14 Button,
15 Card,
16 Col,
17 Row,
18 message,
19 Modal,
20 List,
21 Space,
22 Typography,
23 Divider,
24 Empty,
25 Form,
26 Input,
27 Slider,
28 Radio,
29 Tooltip,
30 Spin,
31 Checkbox,
32 Tabs,
33 Table,
34 } from 'antd';
35 import { useCallback, useRef, useState, useEffect } from 'react';
36 import AccountSidebar from '../account/components/AccountSidebar/AccountSidebar';
37 import styles from './reply.module.scss';
38 import ReplyWorks, { ReplyWorksRef } from './components/replyWorks';
39 import ReplyComment, { ReplyCommentRef } from './components/replyComment';
40 import AddAutoRun, { AddAutoRunRef } from './components/addAutoRun';
41 import {
42 LikeOutlined,
43 StarOutlined,
44 CloseOutlined,
45 CommentOutlined,
46 UnorderedListOutlined,
47 SettingOutlined,
48 RobotOutlined,
49 UserOutlined,
50 SendOutlined,
51 DownOutlined,
52 QuestionCircleOutlined,
53 SyncOutlined,
54 CheckSquareOutlined,
55 CopyOutlined,
56 } from '@ant-design/icons';
57 import Masonry from 'react-masonry-css';
58 import { AccountModel } from '../../../electron/db/models/account';
59 import WebView from '../../components/WebView';
60 // @ts-ignore
61 import { useInView } from 'react-intersection-observer';
62 import { icpCreatorList } from '@/icp/reply';
63 import { icpCreateInteractionOneKey } from '@/icp/replyother';
64 import { useUserStore } from '@/store/user';
65 import { taskApi } from '@/api/task';
66 import {PlatType} from "@@/AccountEnum";
67
68 export default function Page() {
69 const userStore = useUserStore();
70
71 const [wordList, setWordList] = useState<WorkData[]>([]);
72 const [postFirstId, setPostFirstId] = useState<string>('');
73 const [activeAccountId, setActiveAccountId] = useState<number>(-1);
74 const [activeAccountType, setActiveAccountType] = useState<string>('');
75 const [activeAccount, setActiveAccount] = useState<AccountModel>();
76 const Ref_ReplyWorks = useRef<ReplyWorksRef>(null);
77 const Ref_AddAutoRun = useRef<AddAutoRunRef>(null);
78 const Ref_ReplyComment = useRef<ReplyCommentRef>(null);
79 const [postList, setPostList] = useState<any[]>([]);
80 const [commentModalVisible, setCommentModalVisible] = useState(false);
81 const [currentPost, setCurrentPost] = useState<any>(null);
82 const [currentComments, setCurrentComments] = useState<any[]>([]);
83 const { Text, Title } = Typography;
84 const [webviewModalVisible, setWebviewModalVisible] = useState(false);
85 const [currentUrl, setCurrentUrl] = useState('');
86 const [isWebviewLoading, setIsWebviewLoading] = useState(true);
87 const [pageInfo, setPageInfo] = useState<{
88 count: number;
89 hasMore: boolean;
90 pcursor?: any;
91 }>({
92 count: 0,
93 hasMore: true,
94 pcursor: 1,
95 });
96
97 // 创建 webview 的引用
98 const webviewRef = useRef<any>(null);
99
100 // 在组件挂载后添加事件监听器
101 useEffect(() => {
102 const webviewElement = webviewRef.current;
103 if (webviewElement && webviewModalVisible) {
104 // 添加事件监听器
105 const handleLoad = () => {
106 setIsWebviewLoading(false);
107 };
108
109 webviewElement.addEventListener('did-finish-load', handleLoad);
110
111 // 清理函数
112 return () => {
113 if (webviewElement) {
114 webviewElement.removeEventListener('did-finish-load', handleLoad);
115 }
116 };
117 }
118 }, [webviewModalVisible, webviewRef.current]);
119
120 // 添加状态记录
121 const [likedPosts, setLikedPosts] = useState<Record<string, boolean>>({});
122 const [collectedPosts, setCollectedPosts] = useState<Record<string, boolean>>(
123 {},
124 );
125
126 // 添加互动记录相关状态
127 const [interactionRecords, setInteractionRecords] = useState<any[]>([]);
128 const [interactionPageInfo, setInteractionPageInfo] = useState({
129 page_size: 10,
130 page_no: 1,
131 total: 0,
132 });
133
134 // 添加进程中互动相关状态
135 const [runningInteractions, setRunningInteractions] = useState<
136 {
137 createTime: number; // 1744200185113;
138 message: string; // '进行中';
139 status: number; // 0;
140 title: string; // '互动任务';
141 updateTime: number; // 1744200185113;
142 }[]
143 >([]);
144
145 // 获取进程中互动信息
146 const getRunningInteractions = async () => {
147 try {
148 // console.log('------ 开始获取进程中互动信息');
149 const res = await ipcGetAutoRunOfInteractionInfo();
150 // console.log('------ 获取进程中互动信息结果:', res);
151 if (res) {
152 setRunningInteractions([{ ...res }]);
153 } else {
154 setRunningInteractions([]);
155 }
156 } catch (error) {
157 // console.error('------ 获取进程中互动信息失败:', error);
158 message.error('获取进程中互动信息失败');
159 }
160 };
161
162 // 定期获取进程中互动信息
163 useEffect(() => {
164 if (activeAccountId !== -1) {
165 getRunningInteractions();
166 const timer = setInterval(() => {
167 getRunningInteractions();
168 }, 5000); // 每5秒更新一次
169 return () => clearInterval(timer);
170 }
171 }, [activeAccountId]);
172
173 // 获取互动记录
174 const getInteractionRecords = async () => {
175 try {
176 console.log(
177 '------ 开始获取互动记录',
178 activeAccountId,
179 'activeAccountType:',
180 activeAccountType,
181 );
182 const res = await ipcGetInteractionRecordList(
183 {
184 page_size: interactionPageInfo.page_size,
185 page_no: interactionPageInfo.page_no,
186 },
187 {
188 accountId: activeAccountId,
189 type: activeAccountType as any,
190 },
191 );
192 console.log('------ 获取互动记录结果:', res);
193 const data: any = res;
194 setInteractionRecords(data.list || []);
195 setInteractionPageInfo((prev) => ({
196 ...prev,
197 total: data.total || 0,
198 }));
199 } catch (error) {
200 console.error('------ 获取互动记录失败:', error);
201 message.error('获取互动记录失败');
202 }
203 };
204
205 // 监听账户变化,重新获取记录
206 useEffect(() => {
207 if (activeAccountId !== -1) {
208 getInteractionRecords();
209 }
210 }, [activeAccountId, activeAccountType]);
211
212 // 添加任务表单相关状态
213 const [taskForm] = Form.useForm();
214 const [commentType, setCommentType] = useState<'ai' | 'custom' | 'copy' >('custom');
215 const [customComments, setCustomComments] = useState<string[]>([
216 '很棒!',
217 '喜欢这个',
218 '支持一下',
219 '不错哦',
220 ]);
221
222 // 添加任务弹窗状态
223 const [taskModalVisible, setTaskModalVisible] = useState(false);
224
225 // 添加加载状态
226 const [isLoadingMore, setIsLoadingMore] = useState(false);
227
228 // 使用 react-intersection-observer 创建一个观察器
229 const { ref: loadMoreRef, inView } = useInView({
230 threshold: 0.5, // 当元素50%可见时触发
231 triggerOnce: false, // 允许多次触发
232 });
233
234 // 监听 inView 变化,当元素可见时加载更多
235 useEffect(() => {
236 if (inView && pageInfo.hasMore && !isLoadingMore) {
237 loadMorePosts();
238 }
239 }, [inView, pageInfo.hasMore, isLoadingMore]);
240
241 // 加载更多帖子
242 const loadMorePosts = async () => {
243 // console.log('------ loadMorePosts == 1');
244 if (
245 !pageInfo.hasMore ||
246 isLoadingMore ||
247 !searchKeyword ||
248 searchKeyword == ''
249 )
250 return;
251 // console.log('------ loadMorePosts == 2');
252 if (activeAccountType !== 'xhs') {
253 if (!postFirstId || postFirstId == '') return;
254 }
255 // console.log('------ loadMorePosts == 3');
256 setIsLoadingMore(true);
257 try {
258 setTimeout(async () => {
259 await getSearchListFunc(activeAccountId, searchKeyword, false);
260 }, 0);
261 } finally {
262 setIsLoadingMore(false);
263 }
264 };
265
266 // 添加选择模式状态
267 const [isSelectMode, setIsSelectMode] = useState(false);
268 const [selectedPosts, setSelectedPosts] = useState<string[]>([]);
269
270 // 处理选择模式切换
271 const handleSelectModeToggle = () => {
272 console.log('isSelectMode', isSelectMode);
273 // if (!isSelectMode) {
274 setSelectedPosts([]); // 清空已选择的帖子
275 // }
276 setIsSelectMode(!isSelectMode);
277 };
278
279 // 处理帖子选择
280 const handlePostSelect = (postId: string) => {
281 setSelectedPosts((prev) => {
282 if (prev.includes(postId)) {
283 return prev.filter((id) => id !== postId);
284 } else {
285 return [...prev, postId];
286 }
287 });
288 };
289
290 // 修改提交任务函数
291 const submitTask = async (values: any) => {
292 console.log('任务参数:', values);
293 console.log('选中的帖子:', selectedPosts);
294 message.success('任务已下发');
295 setTaskModalVisible(false);
296 setIsSelectMode(false);
297
298 if (!selectedPosts.length) return;
299
300 // 根据当前激活的标签页获取对应的数据列表
301 const currentDataList = activeTabKey === '4' ? searchTaskResults : postList;
302
303 // 从当前数据列表中提取选中的帖子数据
304 const selectedPostData = selectedPosts
305 .map((postId) => {
306 return currentDataList.find((item) => item.dataId === postId);
307 })
308 .filter(Boolean); // 过滤掉undefined的值
309
310 console.log('------ selectedPostData', selectedPostData);
311
312 // 调用icpCreateInteractionOneKey函数
313 const option: any = {
314 platform: activeAccountType,
315 commentType: values.commentType,
316 ...values,
317 accountId: activeAccountId
318 };
319 if (values.commentType != 'ai') {
320 option.commentContent = customComments.join(',');
321 }
322
323 console.log('------ option', option);
324
325 // return;
326
327 const res = await icpCreateInteractionOneKey(
328 activeAccountId,
329 selectedPostData,
330 option,
331 );
332 console.log('------ res', res);
333
334 message.success('互动任务已下发,前往记录查看');
335
336 setSelectedPosts([]);
337 };
338
339 // 添加自定义评论
340 const addCustomComment = (value: string, type: 'comment') => {
341 if (!value.trim()) return;
342 setCustomComments([...customComments, value.trim()]);
343 taskForm.setFieldValue('newComment', '');
344 };
345
346 // 删除自定义评论
347 const removeCustomComment = (index: number, type: 'comment') => {
348 const newComments = [...customComments];
349 newComments.splice(index, 1);
350 setCustomComments(newComments);
351 };
352
353 // 修改状态结构,使用Map存储二级评论
354 const [secondCommentsMap, setSecondCommentsMap] = useState<
355 Record<string, any[]>
356 >({});
357
358 // 添加搜索关键词状态
359 const [searchKeyword, setSearchKeyword] = useState('哎哟赚');
360 const [searchKeywordSelected, setSearchKeywordSelected] = useState('');
361
362 // 添加小红书搜索任务相关状态
363 const [searchTaskId, setSearchTaskId] = useState<string>('');
364 const [searchTaskStatus, setSearchTaskStatus] = useState<
365 'pending' | 'running' | 'completed' | 'failed'
366 >('pending');
367 const [searchTaskProgress, setSearchTaskProgress] = useState<number>(0);
368 const [searchTaskResults, setSearchTaskResults] = useState<any[]>([]);
369 const [searchTaskList, setSearchTaskList] = useState<any[]>([]);
370 const [selectedTaskId, setSelectedTaskId] = useState<string>('');
371 const [activeTabKey, setActiveTabKey] = useState<string>('1');
372
373 // 获取搜索任务列表
374 const getSearchTaskList = async () => {
375 try {
376 const res = await taskApi.searchNotesList({
377 taskType: 'xhs_comments',
378 userId: userStore.userInfo?.id,
379 });
380 // console.log('333',3333, res)
381 if (res) {
382 // console.log('444',444)
383 setSearchTaskList(res);
384 }
385 } catch (error) {
386 message.error('获取搜索任务列表失败');
387 }
388 };
389
390 // 处理页签切换
391 const handleTabChange = (key: string) => {
392 setActiveTabKey(key);
393 if (key === '4' && activeAccountType === 'xhs') {
394 getSearchTaskList();
395 // 切换到评论搜索选项卡时,默认使用AI模式
396 setSearchKeyword('AI');
397 } else if (key === '2') {
398 // 切换到互动记录标签页时调用 getInteractionRecords
399 getInteractionRecords();
400 }
401 };
402
403 // 查看任务结果
404 const viewTaskResult = async (taskId: string, keywords: string) => {
405 setSelectedTaskId(taskId);
406 setSearchTaskStatus('running');
407 setSearchTaskProgress(0);
408 setSearchKeywordSelected(keywords);
409 try {
410 const result = await taskApi.searchNotesResult({
411 taskType: 'xhs_comments',
412 taskId: taskId,
413 });
414
415 if (result) {
416 // 转换数据格式
417 const formattedResults = result.map((item: any) => ({
418 author: {
419 name: item.author.name,
420 avatar: item.author.avatar || '',
421 id: item.author.id,
422 },
423 profileUrl: item.profileUrl || '',
424 collectCount: item.stats?.collectCount?.toString() || '0',
425 commentCount: item.stats?.commentCount?.toString() || '0',
426 coverUrl: item.cover,
427 category: item.category,
428 data: {
429 id: item.noteId,
430 model_type: 'note',
431 note_card: {},
432 xsec_token: item.set_xsec_token
433 ? item.url?.split('xsec_token=')[1]?.split('&')[0]
434 : '',
435 },
436 dataId: item.noteId,
437 likeCount: item.stats?.likeCount?.toString() || '0',
438 option: {
439 xsec_token: item.set_xsec_token
440 ? item.url?.split('xsec_token=')[1]?.split('&')[0]
441 : '',
442 },
443 title: item.title,
444 content: item.content,
445 url: item.url,
446 aboutsComments: item.aboutsComments || '',
447 }));
448
449 setSearchTaskResults(formattedResults);
450 }
451 } catch (error) {
452 message.error('获取任务结果失败');
453 }
454 };
455
456 // 打开作者主页
457 const openAuthorProfile = (url?: string) => {
458 if (url) {
459 window.open(url, '_blank');
460 }
461 };
462
463 // 提交搜索任务
464 const submitSearchTask = async () => {
465 if (!searchKeyword) {
466 message.error('请输入搜索关键词');
467 return;
468 }
469
470 try {
471 const res = await taskApi.searchNotesTask({
472 keywords: searchKeyword,
473 taskType: 'xhs_comments',
474 userId: userStore.userInfo?.id,
475 maxCounts: 10,
476 });
477
478 if (res && res.taskId) {
479 message.success('搜索任务已提交');
480 // 刷新任务列表
481 getSearchTaskList();
482 }
483 } catch (error) {
484 message.error('提交搜索任务失败');
485 }
486 };
487
488 // 组件加载时获取任务列表
489 useEffect(() => {
490 if (activeAccountType === 'xhs') {
491 getSearchTaskList();
492 }
493 }, [activeAccountType]);
494
495 async function getCreatorList(thisid: any) {
496 setWordList([]);
497 if (activeAccountId === -1) {
498 return;
499 }
500 const thisida = thisid ? thisid : activeAccountId;
501 const res = await icpCreatorList(thisida);
502 console.log('------ icpCreatorList', res);
503 setWordList(res.list);
504 }
505
506 // 搜索列表 - 平台自己搜索
507 async function getSearchListFunc(
508 thisid: number,
509 qe?: any,
510 isfirst?: boolean,
511 ) {
512 if (!pageInfo.hasMore && pageInfo.pcursor !== 1 && !isfirst) {
513 console.log('没有更多数据了,不再发送请求');
514 return;
515 }
516 console.log('activeAccountType', activeAccountType)
517 if (isfirst) {
518 setPostFirstId('');
519 pageInfo.pcursor = 1;
520 }
521 const res = await getCommentSearchNotes(thisid, qe, {
522 ...pageInfo,
523 postFirstId: postFirstId,
524 });
525 console.log('------ getSearchListFunc -- @@:', res);
526 if (isfirst && activeAccountType == 'douyin') {
527 setPostFirstId(res.orgList?.log_pb?.impr_id || '');
528 } else if (isfirst && activeAccountType == 'KWAI') {
529 console.log(
530 '------ getSearchListFunc -- @@:',
531 res.orgList?.searchSessionId,
532 );
533 setPostFirstId(res.orgList?.searchSessionId);
534 }
535 if (res.list?.length) {
536 // 如果是加载更多,则追加到现有列表
537 setPostList((prev) =>
538 pageInfo.pcursor !== 1 ? [...prev, ...res.list] : res.list,
539 );
540
541 // 更新分页信息
542 setPageInfo({
543 count: res.pageInfo.count || 0,
544 hasMore: res.pageInfo.hasMore || false,
545 pcursor: res.pageInfo.pcursor || '',
546 });
547
548 } else {
549 // 如果没有返回数据,设置hasMore为false
550 setPageInfo((prev) => ({
551 ...prev,
552 hasMore: false,
553 }));
554 }
555 }
556
557 /**
558 * 获取二级评论列表
559 */
560 async function getSecondCommentList(item: any) {
561 try {
562 const res = await icpGetSecondCommentListByOther(
563 activeAccountId,
564 item,
565 item.data.id,
566 item.data.sub_comment_cursor,
567 );
568 console.log('------ getSecondCommentList', res);
569
570 // 更新二级评论Map
571 setSecondCommentsMap((prev) => ({
572 ...prev,
573 [item.data.id]: res.list || [],
574 }));
575
576 // 更新当前评论列表中的二级评论
577 setCurrentComments((prevComments) =>
578 prevComments.map((comment) =>
579 comment.data.id === item.data.id
580 ? {
581 ...comment,
582 subCommentList: res.list || [],
583 isSubCommentsLoaded: true,
584 }
585 : comment,
586 ),
587 );
588 } catch (error) {
589 console.error('获取二级评论失败', error);
590 message.error('获取二级评论失败');
591 }
592 }
593
594 /**
595 * 打开作品评论
596 * @param data
597 */
598 function openReplyWorks(data: any) {
599 // 确保数据格式兼容
600 const workData: WorkData = {
601 dataId: data.dataId || data.dataId,
602 title: data.title || '',
603 coverUrl: data.cover || data.coverUrl || '',
604 // 添加其他必要的字段
605 authorId: data.author.id,
606 };
607 Ref_ReplyWorks.current?.init(activeAccountId, workData);
608 }
609
610 /**
611 * 打开评论回复
612 * @param data
613 */
614 function openReplyComment(data: CommentData) {
615 data.videoAuthId = currentPost.author.id;
616 console.log('------ openReplyComment', data);
617 Ref_ReplyComment.current?.init(activeAccountId, data);
618 }
619
620 /**
621 * 打开创建自动任务
622 * @param data
623 */
624 function openAddAutoRun(data: WorkData) {
625 Ref_AddAutoRun.current?.init(activeAccountId, data.dataId);
626 }
627
628 /**
629 * 显示评论列表弹窗
630 */
631 const showCommentModal = async (post: any) => {
632 console.log('------ showCommentModal post', post);
633 setCurrentPost(post);
634 setCommentModalVisible(true);
635
636 try {
637 // 获取评论列表
638 const res = await icpGetCommentListByOther(activeAccountId, {
639 dataId: post.dataId,
640 option: {
641 xsec_token: post.option.xsec_token || post.xsec_token,
642 },
643 });
644 console.log('------ getCommentListByOther res', res);
645 // 为每个评论添加加载状态标记
646 const commentsWithLoadingState = (res.list || []).map((comment) => ({
647 ...comment,
648 isSubCommentsLoaded: false,
649 isLoadingSubComments: false,
650 }));
651
652 setCurrentComments(commentsWithLoadingState);
653 } catch (error) {
654 console.error('获取评论失败', error);
655 message.error('获取评论失败');
656 }
657 };
658
659 /**
660 * 加载二级评论
661 */
662 const loadSubComments = async (comment: any) => {
663 // 如果已经加载过,直接返回
664 if (comment.isSubCommentsLoaded) return;
665
666 // 设置加载状态
667 setCurrentComments((prevComments) =>
668 prevComments.map((item) =>
669 item.data.id === comment.data.id
670 ? { ...item, isLoadingSubComments: true }
671 : item,
672 ),
673 );
674
675 try {
676 await getSecondCommentList(comment);
677 } finally {
678 // 无论成功失败,都取消加载状态
679 setCurrentComments((prevComments) =>
680 prevComments.map((item) =>
681 item.data.id === comment.data.id
682 ? { ...item, isLoadingSubComments: false }
683 : item,
684 ),
685 );
686 }
687 };
688
689 /**
690 * 点赞帖子
691 */
692 const likePost = async (post: any) => {
693 try {
694 // 如果已经点赞,则不重复操作
695 if (likedPosts[post.dataId] || post?.data?.note_card?.interact_info?.liked || post?.data?.statistics?.digg_count) {
696 message.info('已经点赞过了');
697 return;
698 }
699
700 const res = await icpDianzanDyOther(activeAccountId, post.dataId, {
701 authid: post.author.id,
702 });
703 console.log('------ likePost', res);
704 if (
705 res === true ||
706 res.status_code == 0 ||
707 res.data?.code == 0 ||
708 res.data?.visionVideoLike.result == 1
709 ) {
710 message.success('点赞成功');
711 // 更新点赞状态
712 setLikedPosts((prev) => ({
713 ...prev,
714 [post.dataId]: true,
715 }));
716
717 // 更新点赞数量
718 setPostList((prevList) =>
719 prevList.map((item) =>
720 item.dataId === post.dataId
721 ? {
722 ...item,
723 stats: {
724 ...item.stats,
725 likeCount: (item.stats?.likeCount || 0) + 1,
726 },
727 }
728 : item,
729 ),
730 );
731 } else {
732 message.error('点赞失败');
733 }
734 } catch (error) {
735 message.error('点赞操作失败');
736 }
737 };
738
739 /**
740 * 收藏帖子
741 */
742 const collectPost = async (post: any) => {
743 try {
744 // 如果已经收藏,则不重复操作
745 if (collectedPosts[post.dataId] || post?.data?.note_card?.interact_info?.collected || post?.data?.statistics?.collect_count) {
746 message.info('已经收藏过了');
747 return;
748 }
749
750 const res = await icpShoucangDyOther(activeAccountId, post.dataId);
751 console.log('------ collectPost res', res);
752 if (res.status_code == 0 || res.data?.code == 0) {
753 message.success('收藏成功');
754 // 更新收藏状态
755 setCollectedPosts((prev) => ({
756 ...prev,
757 [post.dataId]: true,
758 }));
759
760 // 更新收藏数量
761 setPostList((prevList) =>
762 prevList.map((item) =>
763 item.dataId === post.dataId
764 ? {
765 ...item,
766 stats: {
767 ...item.stats,
768 collectCount: (item.stats?.collectCount || 0) + 1,
769 },
770 }
771 : item,
772 ),
773 );
774 } else {
775 message.error('收藏失败');
776 }
777 } catch (error) {
778 message.error('收藏操作失败');
779 }
780 };
781
782 /**
783 * 点击图片打开链接
784 */
785 const handleImageClick = (post: any) => {
786 if (!post || !post.dataId) return;
787
788 let url = '';
789 // 判断平台类型
790 if (activeAccountType === 'xhs' || post.url?.includes('xiaohongshu.com')) {
791 // 小红书链接格式
792 url = `https://www.xiaohongshu.com/explore/${post.dataId}?xsec_token=${post.option.xsec_token || ''}&xsec_source=pc_search&source=web_explore_feed`;
793 } else if (
794 activeAccountType === 'douyin' ||
795 post.url?.includes('douyin.com')
796 ) {
797 // 抖音链接格式
798 console.log('------ post.dataId', post.dataId);
799 url = `https://www.douyin.com/video/${post.dataId}`;
800 console.log('------ url 2:', url);
801 } else if (activeAccountType == 'KWAI') {
802 // 快手链接格式
803 url = `https://www.kuaishou.com/short-video/${post.dataId}`;
804 } else {
805 // 默认使用已有的url或者根据noteId构建通用链接
806 url = post.url || `https://www.xiaohongshu.com/explore/${post.dataId}`;
807 }
808
809 setCurrentUrl(url);
810 setIsWebviewLoading(true);
811 setWebviewModalVisible(true);
812 };
813
814 /**
815 * 点击图片打开链接
816 */
817 const handleUriClick = (link: any) => {
818 console.log('------ handleUriClick', link);
819 if (!link) return;
820
821 const url = link;
822
823 setCurrentUrl(url);
824 setIsWebviewLoading(true);
825 setWebviewModalVisible(true);
826 };
827
828 // 计算断点值,用于响应式布局
829 const breakpointColumnsObj = {
830 default: 6, // 默认显示5列
831 2270: 5, // 宽度小于2270px时显示5列
832 1939: 4, // 宽度小于1900px时显示4列
833 1600: 3, // 宽度小于1600px时显示4列
834 1200: 2, // 宽度小于1200px时显示3列
835 900: 2, // 宽度小于900px时显示2列
836 600: 1, // 宽度小于600px时显示1列
837 };
838
839 // 处理搜索提交
840 const handleSearch = () => {
841 // 重置列表和分页信息
842 setPostList([]);
843 setPageInfo({
844 count: 0,
845 hasMore: true,
846 pcursor: 1,
847 });
848
849 // 执行搜索
850 setTimeout(() => {
851 getSearchListFunc(activeAccountId, searchKeyword, true);
852 }, 1000);
853 };
854
855 // 删除搜索任务
856 const deleteSearchTask = async (taskId: string) => {
857 try {
858 const res = await taskApi.deleteSearchNotesTask({
859 userId: userStore.userInfo?.id || '',
860 taskType: 'xhs_comments',
861 taskId: taskId,
862 });
863 if (res) {
864 message.success('删除成功');
865 // 刷新任务列表
866 getSearchTaskList();
867 }
868 } catch (error) {
869 message.error('删除失败');
870 }
871 };
872
873 // 随机选择函数
874 const handleRandomSelect = () => {
875 // 根据当前激活的标签页获取对应的数据列表
876 const currentDataList = activeTabKey === '4' ? searchTaskResults : postList;
877
878 // 清空当前选择
879 setSelectedPosts([]);
880
881 // 如果没有数据,直接返回
882 if (!currentDataList || currentDataList.length === 0) {
883 message.info('当前没有可选择的作品');
884 return;
885 }
886
887 // 计算要选择的数量(约一半)
888 const selectCount = Math.ceil(currentDataList.length / 2);
889
890 // 随机选择作品
891 const shuffled = [...currentDataList].sort(() => 0.5 - Math.random());
892 const selected = shuffled.slice(0, selectCount);
893
894 // 更新选中状态
895 const selectedIds = selected.map(item => item.dataId);
896 setSelectedPosts(selectedIds);
897
898 message.success(`已随机选择 ${selectedIds.length} 个作品`);
899 };
900
901 return (
902 <div
903 className={styles.reply}
904 style={{ alignItems: 'flex-start', overflowX: 'hidden' }}
905 >
906 <div style={{ display: 'flex', flexDirection: 'row', height: '100%' }}>
907 <AccountSidebar
908 activeAccountId={activeAccountId}
909 excludePlatforms={[PlatType.WxSph]}
910 onAccountChange={useCallback(
911 (info) => {
912 console.log('------ onAccountChange', info);
913 setPageInfo({
914 count: 0,
915 hasMore: false,
916 pcursor: 1,
917 });
918 setActiveAccount(info);
919 setActiveAccountType(info.type);
920
921 setPostList([]);
922
923 setActiveAccountId(info.id);
924 setTimeout(() => {
925 getSearchListFunc(info.id, searchKeyword, true);
926 }, 600);
927 },
928 [getCreatorList],
929 )}
930 />
931
932 <div className={styles.postList} style={{ flex: 1, padding: '20px' }}>
933 {activeAccountId === -1 ? (
934 <div className={styles.account}>
935 <div className="account-noSelect">
936 <QuestionCircleOutlined />
937 <span>点击左侧账户</span>
938 </div>
939 </div>
940 ) : (
941 <>
942 <Tabs
943 defaultActiveKey="1"
944 activeKey={activeTabKey}
945 onChange={handleTabChange}
946 items={[
947 {
948 key: '1',
949 label: '按内容搜笔记',
950 children: (
951 <>
952 <Row
953 justify="space-between"
954 align="middle"
955 style={{ marginBottom: 20 }}
956 >
957 <Col>
958 <Typography.Title level={4} style={{ margin: 0 }}>
959 任务:
960 </Typography.Title>
961 </Col>
962 <Col flex="auto" style={{ margin: '0 20px' }}>
963 <Input.Search
964 placeholder="输入关键词搜索"
965 value={searchKeyword}
966 onChange={(e) => setSearchKeyword(e.target.value)}
967 onSearch={handleSearch}
968 style={{ width: '100%' }}
969 enterButton
970 />
971 </Col>
972 <Col>
973 <Space>
974 <Button
975 type={isSelectMode ? 'primary' : 'default'}
976 icon={<DownOutlined />}
977 onClick={handleSelectModeToggle}
978 size="large"
979 >
980 {isSelectMode ? '取消选择' : '选择作品'}
981 </Button>
982
983 {isSelectMode && (
984 <>
985 <Button
986 type="default"
987 icon={<CheckSquareOutlined />}
988 onClick={handleRandomSelect}
989 size="large"
990 >
991 随机选择
992 </Button>
993 <Button
994 type="primary"
995 icon={<SendOutlined />}
996 onClick={() => setTaskModalVisible(true)}
997 size="large"
998 disabled={selectedPosts.length === 0}
999 >
1000 下发任务 ({selectedPosts.length})
1001 </Button>
1002 </>
1003 )}
1004 </Space>
1005 </Col>
1006 </Row>
1007
1008 <Masonry
1009 breakpointCols={breakpointColumnsObj}
1010 className={styles.myMasonryGrid}
1011 columnClassName={styles.myMasonryGridColumn}
1012 >
1013 {postList.map((item: any, index: number) => (
1014 <List.Item
1015 key={`${item.dataId || item.coverUrl}-${index}`}
1016 className={styles.masonryItem}
1017 onClick={() => {
1018 if (isSelectMode) {
1019 handlePostSelect(item.dataId);
1020 }
1021 }}
1022 style={{
1023 cursor: isSelectMode ? 'pointer' : 'default',
1024 background: selectedPosts.some(
1025 (p) => (p as any).dataId === item.dataId,
1026 )
1027 ? 'rgba(24, 144, 255, 0.1)'
1028 : 'transparent',
1029 }}
1030 >
1031 <Card
1032 hoverable={isSelectMode}
1033 className={styles.postCard}
1034 cover={
1035 <div
1036 style={{
1037 cursor: 'pointer',
1038 position: 'relative',
1039 }}
1040 onClick={() =>
1041 !isSelectMode && handleImageClick(item)
1042 }
1043 >
1044 {isSelectMode && (
1045 <div
1046 style={{
1047 position: 'absolute',
1048 top: 10,
1049 left: 10,
1050 zIndex: 1,
1051 }}
1052 >
1053 <Checkbox
1054 checked={selectedPosts.includes(
1055 item.dataId,
1056 )}
1057 onChange={() =>
1058 handlePostSelect(item.dataId)
1059 }
1060 />
1061 </div>
1062 )}
1063 <div
1064 style={{
1065 width: '200px',
1066 height: '200px',
1067 position: 'relative',
1068 overflow: 'hidden',
1069 }}
1070 >
1071 <img
1072 src={item.coverUrl}
1073 alt={item.title}
1074 style={{
1075 width: '100%',
1076 height: '100%',
1077 objectFit: 'cover',
1078 }}
1079 onError={(e) => {
1080 const target =
1081 e.target as HTMLImageElement;
1082 target.style.display = 'none';
1083 const titleDiv =
1084 document.createElement('div');
1085 titleDiv.style.cssText = `
1086 width: 100%;
1087 height: 100%;
1088 display: flex;
1089 align-items: center;
1090 justify-content: center;
1091 background: #f5f5f5;
1092 padding: 10px;
1093 text-align: center;
1094 word-break: break-word;
1095 `;
1096 titleDiv.textContent = item.title;
1097 target.parentNode?.appendChild(
1098 titleDiv,
1099 );
1100 }}
1101 />
1102 </div>
1103 </div>
1104 }
1105 actions={[
1106 <Space
1107 key="like"
1108 onClick={() => likePost(item)}
1109 >
1110 <LikeOutlined
1111 style={{
1112 color: (likedPosts[item.dataId] || item?.data.note_card?.interact_info?.liked || item?.data.statistics?.digg_count)
1113 ? '#ff4d4f'
1114 : undefined,
1115 fontSize: (likedPosts[item.dataId] || item?.data.note_card?.interact_info?.liked || item?.data.statistics?.digg_count)
1116 ? '18px'
1117 : undefined,
1118 }}
1119 />
1120 <span>{item.likeCount || ''}</span>
1121 </Space>,
1122 <Space
1123 key="comment-list"
1124 onClick={() => showCommentModal(item)}
1125 >
1126 <UnorderedListOutlined />
1127 <span>{item.commentCount || ''}</span>
1128 </Space>,
1129 <Space
1130 key="reply"
1131 onClick={() => openReplyWorks(item)}
1132 >
1133 <CommentOutlined />
1134 <span>评论</span>
1135 </Space>,
1136 <Space
1137 key="collect"
1138 onClick={() => collectPost(item)}
1139 >
1140 <StarOutlined
1141 style={{
1142 color: (collectedPosts[item.dataId] || item?.data.note_card?.interact_info?.collected || item?.data.statistics?.collect_count)
1143 ? '#faad14'
1144 : undefined,
1145 fontSize: (collectedPosts[item.dataId] || item?.data.note_card?.interact_info?.collected || item?.data.statistics?.collect_count)
1146 ? '18px'
1147 : undefined,
1148 }}
1149 />
1150 <span>{item.collectCount || ''}</span>
1151 </Space>,
1152 ]}
1153 >
1154 <Card.Meta
1155 avatar={
1156 <Avatar src={`${item.author?.avatar}`} />
1157 }
1158 title={item.author?.name}
1159 description={
1160 <div>
1161 <Text
1162 strong
1163 ellipsis
1164 style={{ display: 'block' }}
1165 >
1166 {item.title}
1167 </Text>
1168 <Text type="secondary" ellipsis>
1169 {item.content}
1170 </Text>
1171 </div>
1172 }
1173 />
1174 </Card>
1175 </List.Item>
1176 ))}
1177 </Masonry>
1178
1179 {/* 加载更多区域 */}
1180 <div ref={loadMoreRef} className={styles.loadMoreArea}>
1181 {isLoadingMore && (
1182 <div className={styles.loadingMore}>
1183 <Spin size="small" />
1184 <span style={{ marginLeft: 8 }}>加载中...</span>
1185 </div>
1186 )}
1187
1188 {!pageInfo.hasMore && postList.length > 0 && (
1189 <div className={styles.noMoreData}>
1190 <Divider plain>没有更多数据了</Divider>
1191 </div>
1192 )}
1193 </div>
1194 </>
1195 ),
1196 },
1197 ...(activeAccountType === 'xhs'
1198 ? [
1199 {
1200 key: '4',
1201 label: '按评论搜笔记',
1202 children: (
1203 <div style={{ padding: '20px', overflowX: 'hidden' }}>
1204 <Card style={{ width: '100%' }}>
1205 <Form layout="vertical">
1206 <Form.Item>
1207 <Input.Search
1208 placeholder="输入评论"
1209 value={searchKeyword}
1210 onChange={(e) => setSearchKeyword(e.target.value)}
1211 onSearch={submitSearchTask}
1212 enterButton="搜索任务"
1213 />
1214 <div
1215 style={{
1216 marginTop: '8px',
1217 color: '#999',
1218 fontSize: '12px',
1219 paddingLeft: '2px',
1220 display: 'flex',
1221 alignItems: 'center',
1222 gap: '8px',
1223 }}
1224 >
1225 <QuestionCircleOutlined />{' '}
1226 小红书搜索评论需要5-10分钟,请稍后查看
1227 <Button
1228 type="link"
1229 icon={<SyncOutlined />}
1230 onClick={getSearchTaskList}
1231 style={{ padding: 0, height: 'auto' }}
1232 />
1233 </div>
1234 </Form.Item>
1235 </Form>
1236
1237 <Table
1238 dataSource={searchTaskList}
1239 rowKey="_id"
1240 columns={[
1241 {
1242 title: '任务ID',
1243 dataIndex: 'taskId',
1244 key: 'taskId',
1245 width: 220,
1246 ellipsis: true,
1247 },
1248 {
1249 title: '关键词',
1250 dataIndex: 'keywords',
1251 key: 'keywords',
1252 ellipsis: true,
1253 },
1254 {
1255 title: '状态',
1256 dataIndex: 'status',
1257 key: 'status',
1258 width: 100,
1259 render: (status: number) => {
1260 const statusMap: Record<number, string> = {
1261 0: '等待运行',
1262 1: '运行完成',
1263 2: '正在运行',
1264 };
1265 return statusMap[status] || '未知';
1266 },
1267 },
1268 {
1269 title: '创建时间',
1270 dataIndex: 'createTime',
1271 key: 'createTime',
1272 width: 150,
1273 },
1274 {
1275 title: '数据范围',
1276 dataIndex: 'dateType',
1277 key: 'dateType',
1278 width: 100,
1279 render: (dateType: string) => {
1280 const dateTypeMap: Record<string, string> = {
1281 '7d': '最近7天',
1282 '30d': '最近30天',
1283 '90d': '最近90天',
1284 };
1285 return dateTypeMap[dateType] || dateType;
1286 },
1287 },
1288 {
1289 title: '最大数量',
1290 dataIndex: 'maxCounts',
1291 key: 'maxCounts',
1292 width: 100,
1293 },
1294 {
1295 title: '操作',
1296 key: 'action',
1297 width: 150,
1298 fixed: 'right',
1299 render: (_, record) => (
1300 <Space>
1301 <Button
1302 type="link"
1303 onClick={() => viewTaskResult(record.taskId, record.keywords)}
1304 disabled={record.status != 1}
1305 >
1306 查看结果
1307 </Button>
1308 <Button
1309 type="link"
1310 danger
1311 onClick={() => deleteSearchTask(record.taskId)}
1312 >
1313 删除
1314 </Button>
1315 </Space>
1316 ),
1317 },
1318 ]}
1319 pagination={false}
1320 size="small"
1321 />
1322
1323 {searchTaskResults.length > 0 ? (
1324 <div
1325 style={{
1326 marginTop: '20px',
1327 overflowX: 'hidden',
1328 }}
1329 >
1330 <div
1331 style={{
1332 marginBottom: '16px',
1333 display: 'flex',
1334 justifyContent: 'space-between',
1335 alignItems: 'center',
1336 }}
1337 >
1338 <Space>
1339 {isSelectMode && (
1340 <Button
1341 type="primary"
1342 icon={<CheckSquareOutlined />}
1343 onClick={() => {
1344 if (
1345 selectedPosts.length ===
1346 searchTaskResults.length
1347 ) {
1348 setSelectedPosts([]);
1349 } else {
1350 setSelectedPosts([
1351 ...searchTaskResults.map(
1352 (item) => item.dataId,
1353 ),
1354 ]);
1355 }
1356 }}
1357 size="large"
1358 >
1359 {selectedPosts.length ===
1360 searchTaskResults.length
1361 ? '取消全选'
1362 : '全选'}
1363 </Button>
1364 )}
1365 </Space>
1366
1367 <Space>
1368 <Button
1369 type={
1370 isSelectMode ? 'primary' : 'default'
1371 }
1372 icon={<DownOutlined />}
1373 onClick={handleSelectModeToggle}
1374 size="large"
1375 >
1376 {isSelectMode
1377 ? '取消选择'
1378 : '选择作品'}
1379 </Button>
1380 {isSelectMode && (
1381 <Button
1382 type="primary"
1383 icon={<SendOutlined />}
1384 onClick={() =>
1385 setTaskModalVisible(true)
1386 }
1387 size="large"
1388 disabled={
1389 selectedPosts.length === 0
1390 }
1391 >
1392 下发任务 ({selectedPosts.length})
1393 </Button>
1394 )}
1395 </Space>
1396 </div>
1397
1398 <List
1399 itemLayout="horizontal"
1400 dataSource={searchTaskResults}
1401 renderItem={(item: any) => (
1402 <List.Item
1403 key={item.dataId}
1404 onClick={() => {
1405 if (isSelectMode) {
1406 handlePostSelect(item.dataId);
1407 }
1408 }}
1409 style={{
1410 cursor: isSelectMode
1411 ? 'pointer'
1412 : 'default',
1413 background: selectedPosts.includes(
1414 item.dataId,
1415 )
1416 ? 'rgba(24, 144, 255, 0.1)'
1417 : 'transparent',
1418 padding: '16px',
1419 borderRadius: '8px',
1420 marginBottom: '8px',
1421 border: '1px solid #f0f0f0',
1422 overflow: 'hidden',
1423 }}
1424 actions={[
1425 <Space
1426 key="like"
1427 onClick={() => likePost(item)}
1428 >
1429 <LikeOutlined
1430 style={{
1431 color: likedPosts[item.dataId]
1432 ? '#ff4d4f'
1433 : undefined,
1434 fontSize: likedPosts[
1435 item.dataId
1436 ]
1437 ? '18px'
1438 : undefined,
1439 }}
1440 />
1441 <span>{item.likeCount || ''}</span>
1442 </Space>,
1443 <Space
1444 key="comment-list"
1445 onClick={() =>
1446 showCommentModal(item)
1447 }
1448 >
1449 <UnorderedListOutlined />
1450 <span>
1451 {item.commentCount || ''}
1452 </span>
1453 </Space>,
1454 <Space
1455 key="reply"
1456 onClick={() =>
1457 openReplyWorks(item)
1458 }
1459 >
1460 <CommentOutlined />
1461 <span>评论</span>
1462 </Space>,
1463 <Space
1464 key="collect"
1465 onClick={() => collectPost(item)}
1466 >
1467 <StarOutlined
1468 style={{
1469 color: collectedPosts[
1470 item.dataId
1471 ]
1472 ? '#faad14'
1473 : undefined,
1474 fontSize: collectedPosts[
1475 item.dataId
1476 ]
1477 ? '18px'
1478 : undefined,
1479 }}
1480 />
1481 <span>
1482 {item.collectCount || ''}
1483 </span>
1484 </Space>,
1485 ]}
1486 >
1487 <List.Item.Meta
1488 avatar={
1489 <div
1490 style={{
1491 display: 'flex',
1492 alignItems: 'center',
1493 }}
1494 >
1495 {isSelectMode && (
1496 <Checkbox
1497 checked={selectedPosts.includes(
1498 item.dataId,
1499 )}
1500 onClick={(e) =>
1501 e.stopPropagation()
1502 }
1503 onChange={() =>
1504 handlePostSelect(
1505 item.dataId,
1506 )
1507 }
1508 style={{
1509 marginRight: '12px',
1510 }}
1511 />
1512 )}
1513 <div
1514 style={{
1515 position: 'relative',
1516 cursor: 'pointer',
1517 }}
1518 onClick={() =>
1519 !isSelectMode &&
1520 handleImageClick(item)
1521 }
1522 >
1523 {item.coverUrl ? (
1524 <div
1525 style={{
1526 width: '120px',
1527 height: '120px',
1528 position: 'relative',
1529 overflow: 'hidden',
1530 }}
1531 >
1532 <img
1533 src={item.coverUrl}
1534 alt={item.title}
1535 style={{
1536 width: '100%',
1537 height: '100%',
1538 objectFit: 'cover',
1539 }}
1540 onError={(e) => {
1541 const target =
1542 e.target as HTMLImageElement;
1543 target.style.display =
1544 'none';
1545 const titleDiv =
1546 document.createElement(
1547 'div',
1548 );
1549 titleDiv.style.cssText = `
1550 width: 100%;
1551 height: 100%;
1552 display: flex;
1553 align-items: center;
1554 justify-content: center;
1555 background: #f5f5f5;
1556 padding: 10px;
1557 text-align: center;
1558 word-break: break-word;
1559 `;
1560 titleDiv.textContent =
1561 item.title;
1562 target.parentNode?.appendChild(
1563 titleDiv,
1564 );
1565 }}
1566 />
1567 </div>
1568 ) : (
1569 <div
1570 style={{
1571 width: '120px',
1572 height: '120px',
1573 display: 'flex',
1574 alignItems: 'center',
1575 justifyContent:
1576 'center',
1577 background: '#f0f0f0',
1578 borderRadius: '8px',
1579 padding: '10px',
1580 textAlign: 'center',
1581 fontSize: '12px',
1582 overflow: 'hidden',
1583 wordBreak: 'break-word',
1584 }}
1585 >
1586 {item.content}
1587 </div>
1588 )}
1589 </div>
1590 </div>
1591 }
1592 title={
1593 <div
1594 style={{ marginLeft: '12px' }}
1595 >
1596 <div
1597 onClick={() =>
1598 handleUriClick(
1599 item.profileUrl,
1600 )
1601 }
1602 >
1603 {item.author?.name}
1604 </div>
1605 <div
1606 style={{
1607 fontWeight: 'bold',
1608 marginTop: '8px',
1609 }}
1610 onClick={() =>
1611 !isSelectMode &&
1612 handleImageClick(item)
1613 }
1614 >
1615 {item.title}
1616 </div>
1617 </div>
1618 }
1619 description={
1620 <div
1621 style={{ marginLeft: '12px' }}
1622 >
1623 <Text type="secondary" ellipsis>
1624 {item.content}
1625 </Text>
1626 {item.aboutsComments && (
1627 <div
1628 style={{
1629 marginTop: '8px',
1630 display: 'flex',
1631 flexDirection: 'row',
1632 alignItems: 'center',
1633 }}
1634 >
1635 <div
1636 style={{
1637 fontWeight: 'bold',
1638 marginBottom: '4px',
1639 width: '72px',
1640 }}
1641 >
1642 相关评论:
1643 </div>
1644 <div
1645 style={{
1646 padding: '8px',
1647 background: '#f5f5f5',
1648 borderRadius: '4px',
1649 marginBottom: '4px',
1650 }}
1651 >
1652 {/* 高亮显示与搜索关键词匹配的部分 */}
1653 {(() => {
1654 if (
1655 !searchKeywordSelected
1656 )
1657 return item.aboutsComments;
1658 const regex =
1659 new RegExp(
1660 `(${searchKeywordSelected})`,
1661 'gi',
1662 );
1663 const parts =
1664 item.aboutsComments.split(
1665 regex,
1666 );
1667 return parts.map(
1668 (
1669 part: string,
1670 i: number,
1671 ) =>
1672 regex.test(part) ? (
1673 <span
1674 key={i}
1675 style={{
1676 color: 'red',
1677 fontWeight:
1678 'bold',
1679 }}
1680 >
1681 {part}
1682 </span>
1683 ) : (
1684 part
1685 ),
1686 );
1687 })()}
1688 </div>
1689 </div>
1690 )}
1691 </div>
1692 }
1693 />
1694 </List.Item>
1695 )}
1696 />
1697 </div>
1698 ) : (
1699 <div
1700 style={{
1701 marginTop: '20px',
1702 textAlign: 'center',
1703 padding: '40px',
1704 background: '#f5f5f5',
1705 borderRadius: '8px',
1706 }}
1707 >
1708 <Empty
1709 image={Empty.PRESENTED_IMAGE_SIMPLE}
1710 description="此评论未搜索出结果"
1711 />
1712 </div>
1713 )}
1714 </Card>
1715 </div>
1716 ),
1717 },
1718 ]
1719 : []),
1720 {
1721 key: '2',
1722 label: '互动记录',
1723 children: (
1724 <div style={{ marginTop: 0 }}>
1725 <div style={{ marginBottom: 16, textAlign: 'right' }}>
1726 <Button
1727 type="primary"
1728 icon={<SyncOutlined />}
1729 onClick={getInteractionRecords}
1730 >
1731 刷新
1732 </Button>
1733 </div>
1734 <Table
1735 columns={[
1736 {
1737 title: '作品ID',
1738 dataIndex: 'worksId',
1739 key: 'worksId',
1740 },
1741 {
1742 title: '作品标题',
1743 dataIndex: 'worksTitle',
1744 key: 'worksTitle',
1745 },
1746 {
1747 title: '评论内容',
1748 dataIndex: 'commentContent',
1749 key: 'commentContent',
1750 },
1751 {
1752 title: '评论反馈',
1753 dataIndex: 'commentRemark',
1754 key: 'commentRemark',
1755 },
1756 {
1757 title: '点赞状态',
1758 dataIndex: 'isLike',
1759 key: 'isLike',
1760 render: (isLike) =>
1761 isLike ? '已点赞' : '未点赞',
1762 },
1763 {
1764 title: '收藏状态',
1765 dataIndex: 'isCollect',
1766 key: 'isCollect',
1767 render: (isCollect) =>
1768 isCollect == 1 ? '已收藏' : '未收藏',
1769 },
1770 {
1771 title: '互动时间',
1772 dataIndex: 'updateTime',
1773 key: 'updateTime',
1774 render: (updateTime: any) => {
1775 const date = new Date(updateTime);
1776 const month = (date.getMonth() + 1)
1777 .toString()
1778 .padStart(2, '0');
1779 const day = date
1780 .getDate()
1781 .toString()
1782 .padStart(2, '0');
1783 const hours = date
1784 .getHours()
1785 .toString()
1786 .padStart(2, '0');
1787 const minutes = date
1788 .getMinutes()
1789 .toString()
1790 .padStart(2, '0');
1791 return `${month}-${day} ${hours}:${minutes}`;
1792 },
1793 },
1794 ]}
1795 dataSource={interactionRecords}
1796 rowKey="id"
1797 pagination={{
1798 total: interactionPageInfo.total,
1799 pageSize: interactionPageInfo.page_size,
1800 current: interactionPageInfo.page_no,
1801 onChange: (page, pageSize) => {
1802 setInteractionPageInfo((prev) => ({
1803 ...prev,
1804 page_no: page,
1805 page_size: pageSize,
1806 }));
1807 getInteractionRecords();
1808 },
1809 }}
1810 />
1811 </div>
1812 ),
1813 },
1814 {
1815 key: '3',
1816 label: '进程中互动',
1817 children: (
1818 <div style={{ marginTop: 20 }}>
1819 <div style={{ marginBottom: 16, textAlign: 'right' }}>
1820 <Button
1821 type="primary"
1822 icon={<SyncOutlined />}
1823 onClick={getRunningInteractions}
1824 >
1825 刷新
1826 </Button>
1827 </div>
1828 <Table
1829 columns={[
1830 {
1831 title: '标题',
1832 dataIndex: 'title',
1833 key: 'title',
1834 },
1835 {
1836 title: '信息',
1837 dataIndex: 'message',
1838 key: 'message',
1839 },
1840 {
1841 title: '状态',
1842 dataIndex: 'status',
1843 key: 'status',
1844 render: (status: number) => {
1845 const statusMap: Record<
1846 number | string,
1847 string
1848 > = {
1849 0: '进行中',
1850 1: '已完成',
1851 '-1': '失败',
1852 };
1853 return statusMap[status] || '未知';
1854 },
1855 },
1856 {
1857 title: '开始时间',
1858 dataIndex: 'createTime',
1859 key: 'createTime',
1860 render: (startTime: any) => {
1861 const date = new Date(startTime);
1862 const month = (date.getMonth() + 1)
1863 .toString()
1864 .padStart(2, '0');
1865 const day = date
1866 .getDate()
1867 .toString()
1868 .padStart(2, '0');
1869 const hours = date
1870 .getHours()
1871 .toString()
1872 .padStart(2, '0');
1873 const minutes = date
1874 .getMinutes()
1875 .toString()
1876 .padStart(2, '0');
1877 return `${month}-${day} ${hours}:${minutes}`;
1878 },
1879 },
1880 ]}
1881 dataSource={runningInteractions}
1882 rowKey="id"
1883 pagination={false}
1884 />
1885 </div>
1886 ),
1887 },
1888 ]}
1889 />
1890 </>
1891 )}
1892 </div>
1893 </div>
1894
1895 {/* 任务下发弹窗 */}
1896 <Modal
1897 title={
1898 <div style={{ display: 'flex', alignItems: 'center' }}>
1899 <SettingOutlined style={{ marginRight: 8 }} />
1900 <span>任务下发设置</span>
1901 </div>
1902 }
1903 open={taskModalVisible}
1904 onCancel={() => setTaskModalVisible(false)}
1905 footer={null}
1906 width={800}
1907 destroyOnClose
1908 maskClosable={false}
1909 >
1910 <Form
1911 form={taskForm}
1912 layout="vertical"
1913 onFinish={submitTask}
1914 initialValues={{
1915 likeProb: 70,
1916 commentProb: 90,
1917 commentType: 'custom',
1918 collectProb: 30,
1919 }}
1920 >
1921 {/* 点赞概率 */}
1922 <Form.Item label="点赞概率" name="likeProb">
1923 <Slider
1924 marks={{
1925 0: '0%',
1926 25: '25%',
1927 50: '50%',
1928 75: '75%',
1929 100: '100%',
1930 }}
1931 />
1932 </Form.Item>
1933
1934 <Divider orientation="left">评论设置</Divider>
1935
1936 {/* 评论概率 */}
1937 <Form.Item label="评论概率" name="commentProb">
1938 <Slider
1939 marks={{
1940 0: '0%',
1941 25: '25%',
1942 50: '50%',
1943 75: '75%',
1944 100: '100%',
1945 }}
1946 />
1947 </Form.Item>
1948
1949 {/* 评论类型 */}
1950 <Row gutter={24}>
1951 <Col span={12}>
1952 <Form.Item label="评论类型" name="commentType">
1953 <Radio.Group onChange={(e) => setCommentType(e.target.value)}>
1954 <Tooltip title="使用自定义评论">
1955 <Radio.Button value="custom">
1956 <UserOutlined /> 自定义评论
1957 </Radio.Button>
1958 </Tooltip>
1959 <Tooltip title="使用AI生成评论">
1960 <Radio.Button value="ai">
1961 <RobotOutlined /> Deepseek评论
1962 </Radio.Button>
1963 </Tooltip>
1964 <Tooltip title="复制当前内容下热门评论">
1965 <Radio.Button value="copy">
1966 <CopyOutlined /> 评论复刻
1967 </Radio.Button>
1968 </Tooltip>
1969
1970 </Radio.Group>
1971 </Form.Item>
1972 </Col>
1973 </Row>
1974
1975 {/* 自定义评论列表 */}
1976 {commentType === 'custom' && (
1977 <div className={styles.customCommentsSection}>
1978 <div className={styles.commentsList}>
1979 {customComments.map((comment, index) => (
1980 <div key={index} className={styles.commentItem}>
1981 <span>{comment}</span>
1982 <Button
1983 type="text"
1984 danger
1985 size="small"
1986 onClick={() => removeCustomComment(index, 'comment')}
1987 >
1988 删除
1989 </Button>
1990 </div>
1991 ))}
1992 </div>
1993
1994 <Row gutter={8}>
1995 <Col flex="auto">
1996 <Form.Item name="newComment">
1997 <Input placeholder="添加自定义评论" />
1998 </Form.Item>
1999 </Col>
2000 <Col>
2001 <Button
2002 type="primary"
2003 onClick={() =>
2004 addCustomComment(
2005 taskForm.getFieldValue('newComment'),
2006 'comment',
2007 )
2008 }
2009 >
2010 添加
2011 </Button>
2012 </Col>
2013 </Row>
2014 </div>
2015 )}
2016
2017 {/* 收藏概率 */}
2018 <Form.Item label="收藏概率" name="collectProb">
2019 <Slider
2020 marks={{
2021 0: '0%',
2022 25: '25%',
2023 50: '50%',
2024 75: '75%',
2025 100: '100%',
2026 }}
2027 />
2028 </Form.Item>
2029
2030 {/* 提交按钮 */}
2031 <Form.Item style={{ marginTop: 20, textAlign: 'right' }}>
2032 <Button
2033 onClick={() => setTaskModalVisible(false)}
2034 style={{ marginRight: 8 }}
2035 >
2036 取消
2037 </Button>
2038 <Button type="primary" htmlType="submit" icon={<SendOutlined />}>
2039 下发任务
2040 </Button>
2041 </Form.Item>
2042 </Form>
2043 </Modal>
2044
2045 {/* 评论弹窗 */}
2046 <Modal
2047 title={
2048 <div>
2049 <div
2050 style={{
2051 display: 'flex',
2052 alignItems: 'center',
2053 marginBottom: 10,
2054 }}
2055 >
2056 <Avatar src={`${currentPost?.author?.avatar}`} />
2057 <Text strong style={{ marginLeft: 10 }}>
2058 {currentPost?.author?.name}
2059 </Text>
2060 </div>
2061 <Text
2062 style={{ fontSize: '18px', fontWeight: 'bold', marginBottom: 20 }}
2063 >
2064 {currentPost?.title}
2065 </Text>
2066 </div>
2067 }
2068 open={commentModalVisible}
2069 onCancel={() => setCommentModalVisible(false)}
2070 footer={null}
2071 width={600}
2072 >
2073 <div style={{ maxHeight: '60vh', overflow: 'auto' }}>
2074 <List
2075 itemLayout="vertical"
2076 dataSource={currentComments}
2077 renderItem={(comment) => (
2078 <List.Item
2079 actions={[
2080 <Button
2081 type="text"
2082 size="small"
2083 onClick={() => openReplyComment(comment)}
2084 >
2085 回复
2086 </Button>,
2087 comment.data?.sub_comment_count > 0 &&
2088 !comment.isSubCommentsLoaded && (
2089 <Button
2090 type="text"
2091 size="small"
2092 loading={comment.isLoadingSubComments}
2093 onClick={() => loadSubComments(comment)}
2094 >
2095 查看{comment.data.sub_comment_count}条回复
2096 </Button>
2097 ),
2098 ]}
2099 >
2100 <List.Item.Meta
2101 avatar={<Avatar src={comment.headUrl} />}
2102 title={comment.nikeName}
2103 description={comment.content}
2104 />
2105
2106 {/* 二级评论列表 */}
2107 {comment.isSubCommentsLoaded &&
2108 comment.subCommentList &&
2109 comment.subCommentList.length > 0 && (
2110 <div style={{ marginLeft: 40, marginTop: 10 }}>
2111 <List
2112 itemLayout="vertical"
2113 dataSource={comment.subCommentList}
2114 renderItem={(subComment: any) => (
2115 <List.Item>
2116 <List.Item.Meta
2117 avatar={
2118 <Avatar src={subComment.headUrl} size="small" />
2119 }
2120 title={
2121 <Space>
2122 <span>{subComment.nikeName}</span>
2123 <span
2124 onClick={() => openReplyComment(subComment)}
2125 style={{
2126 color: '#999',
2127 fontSize: '10px',
2128 cursor: 'pointer',
2129 }}
2130 >
2131 回复
2132 </span>
2133 </Space>
2134 }
2135 description={
2136 subComment.content +
2137 ' @ ' +
2138 subComment.data.target_comment?.user_info
2139 .nickname
2140 }
2141 />
2142 </List.Item>
2143 )}
2144 />
2145
2146 {/* 如果还有更多二级评论 */}
2147 {/* {comment.data.sub_comment_has_more && (
2148 <div style={{ textAlign: 'center', marginTop: 8 }}>
2149 <Button
2150 type="link"
2151 size="small"
2152 onClick={() => getSecondCommentList(comment)}
2153 >
2154 加载更多回复
2155 </Button>
2156 </div>
2157 )} */}
2158 </div>
2159 )}
2160 </List.Item>
2161 )}
2162 />
2163 </div>
2164 </Modal>
2165
2166 {/* 自定义网页内容弹出层 */}
2167 {webviewModalVisible && (
2168 <div className={styles.customWebviewModal}>
2169 <div
2170 className={styles.modalOverlay}
2171 onClick={() => setWebviewModalVisible(false)}
2172 ></div>
2173 <div className={styles.modalContent}>
2174 <div className={styles.modalHeader}>
2175 <Button
2176 type="text"
2177 icon={<CloseOutlined />}
2178 onClick={() => setWebviewModalVisible(false)}
2179 className={styles.closeButton}
2180 />
2181 </div>
2182 <div className={styles.modalBody}>
2183 {/*{isWebviewLoading && (*/}
2184 {/* <div className={styles.loadingContainer}>*/}
2185 {/* <Spin size="large" tip="加载中..." />*/}
2186 {/* </div>*/}
2187 {/*)}*/}
2188 {currentUrl ? (
2189 <WebView
2190 url={currentUrl}
2191 partition={true}
2192 cookieParams={{
2193 cookies: JSON.parse(activeAccount!.loginCookie!),
2194 }}
2195 />
2196 ) : (
2197 <Empty description="无法加载内容" />
2198 )}
2199 </div>
2200 </div>
2201 </div>
2202 )}
2203
2204 <ReplyWorks ref={Ref_ReplyWorks} />
2205 <ReplyComment ref={Ref_ReplyComment} />
2206 <AddAutoRun ref={Ref_AddAutoRun} />
2207 </div>
2208 );
2209 }
2210
2210 lines Plain Text