| 1 | /* |
| 2 | * @Author: nevin |
| 3 | * @Date: 2025-02-10 22:20:15 |
| 4 | * @LastEditTime: 2025-03-25 16:01:56 |
| 5 | * @LastEditors: nevin |
| 6 | * @Description: 评论页面 reply |
| 7 | */ |
| 8 | import { icpCreatorList, WorkData } from '@/icp/reply'; |
| 9 | import { |
| 10 | Popconfirm, |
| 11 | Tabs, |
| 12 | Tooltip, |
| 13 | Card, |
| 14 | Typography, |
| 15 | Divider, |
| 16 | Spin, |
| 17 | } from 'antd'; |
| 18 | import { useCallback, useRef, useState } from 'react'; |
| 19 | import AccountSidebar from '../account/components/AccountSidebar/AccountSidebar'; |
| 20 | import ReplyWorks, { ReplyWorksRef } from './components/replyWorks'; |
| 21 | import AddAutoRun, { AddAutoRunRef } from './components/addAutoRun'; |
| 22 | import CommentList, { CommentListRef } from './components/commentList'; |
| 23 | import { QuestionCircleOutlined } from '@ant-design/icons'; |
| 24 | import styles from './reply.module.scss'; |
| 25 | import AutoRun from './autoRun'; |
| 26 | import OneKeyReply, { OneKeyReplyRef } from './components/oneKeyReply'; |
| 27 | import Masonry from 'react-masonry-css'; |
| 28 | import { useInView } from 'react-intersection-observer'; |
| 29 | |
| 30 | export default function Page() { |
| 31 | const [wordList, setWordList] = useState<WorkData[]>([]); |
| 32 | const [activeAccountId, setActiveAccountId] = useState<number>(-1); |
| 33 | const [pageInfo, setPageInfo] = useState<{ |
| 34 | count: number; |
| 35 | hasMore: boolean; |
| 36 | pcursor?: string; |
| 37 | }>({ |
| 38 | count: 0, |
| 39 | hasMore: false, |
| 40 | }); |
| 41 | const Ref_ReplyWorks = useRef<ReplyWorksRef>(null); |
| 42 | const Ref_AddAutoRun = useRef<AddAutoRunRef>(null); |
| 43 | const Ref_CommentList = useRef<CommentListRef>(null); |
| 44 | const Ref_OneKeyReply = useRef<OneKeyReplyRef>(null); |
| 45 | const [isLoadingMore, setIsLoadingMore] = useState(false); |
| 46 | const { Text } = Typography; |
| 47 | |
| 48 | // 使用 react-intersection-observer 创建一个观察器 |
| 49 | const { ref: loadMoreRef, inView } = useInView({ |
| 50 | threshold: 0.5, |
| 51 | triggerOnce: false, |
| 52 | }); |
| 53 | |
| 54 | // 监听 inView 变化,当元素可见时加载更多 |
| 55 | const loadMorePosts = useCallback(async () => { |
| 56 | if (!pageInfo.hasMore || isLoadingMore) return; |
| 57 | setIsLoadingMore(true); |
| 58 | try { |
| 59 | await getCreatorList(activeAccountId); |
| 60 | } finally { |
| 61 | setIsLoadingMore(false); |
| 62 | } |
| 63 | }, [pageInfo.hasMore, isLoadingMore, activeAccountId]); |
| 64 | |
| 65 | // 监听 inView 变化 |
| 66 | if (inView && pageInfo.hasMore && !isLoadingMore) { |
| 67 | loadMorePosts(); |
| 68 | } |
| 69 | |
| 70 | async function getCreatorList(accountId: number) { |
| 71 | if (accountId === -1) return; |
| 72 | const res = await icpCreatorList(accountId, pageInfo.pcursor); |
| 73 | setPageInfo(res.pageInfo); |
| 74 | setWordList([...wordList, ...res.list]); |
| 75 | } |
| 76 | |
| 77 | /** |
| 78 | * 打开作品评论 |
| 79 | * @param data |
| 80 | */ |
| 81 | function openReplyWorks(data: WorkData) { |
| 82 | Ref_ReplyWorks.current?.init(activeAccountId, data); |
| 83 | } |
| 84 | |
| 85 | /** |
| 86 | * 打开评论列表 |
| 87 | * @param data |
| 88 | */ |
| 89 | function openCommentList(data: WorkData) { |
| 90 | Ref_CommentList.current?.init(activeAccountId, data); |
| 91 | } |
| 92 | |
| 93 | /** |
| 94 | * 打开创建自动任务 |
| 95 | * @param data |
| 96 | */ |
| 97 | function openAddAutoRun(data: WorkData) { |
| 98 | Ref_AddAutoRun.current?.init(activeAccountId, data); |
| 99 | } |
| 100 | |
| 101 | // 计算断点值,用于响应式布局 |
| 102 | const breakpointColumnsObj = { |
| 103 | default: 6, |
| 104 | 2270: 5, |
| 105 | 1939: 4, |
| 106 | 1600: 3, |
| 107 | 1200: 2, |
| 108 | 900: 2, |
| 109 | 600: 1, |
| 110 | }; |
| 111 | |
| 112 | return ( |
| 113 | <div className={styles.account}> |
| 114 | <AccountSidebar |
| 115 | activeAccountId={activeAccountId} |
| 116 | onAccountChange={useCallback((info) => { |
| 117 | setWordList([]); |
| 118 | setActiveAccountId(info.id); |
| 119 | getCreatorList(info.id); |
| 120 | }, [])} |
| 121 | /> |
| 122 | |
| 123 | <div className="w-full p-4 text-gray-500"> |
| 124 | <Tabs defaultActiveKey="1" className="w-full"> |
| 125 | <Tabs.TabPane tab="作品列表" key="1"> |
| 126 | {activeAccountId === -1 ? ( |
| 127 | <div className="flex items-center justify-center h-[300px]"> |
| 128 | <Tooltip title="请先在左侧侧边栏选择账户"> |
| 129 | <QuestionCircleOutlined className="mr-2 text-3xl" /> |
| 130 | </Tooltip> |
| 131 | 点击左侧账户 |
| 132 | </div> |
| 133 | ) : ( |
| 134 | <div className="p-4"> |
| 135 | <Masonry |
| 136 | breakpointCols={breakpointColumnsObj} |
| 137 | className={styles.myMasonryGrid} |
| 138 | columnClassName={styles.myMasonryGridColumn} |
| 139 | > |
| 140 | {wordList.map((item) => ( |
| 141 | <div key={item.dataId} className={styles.masonryItem}> |
| 142 | <Card |
| 143 | hoverable |
| 144 | className={styles.cardContainer} |
| 145 | cover={<img alt="example" src={item.coverUrl} />} |
| 146 | actions={[ |
| 147 | <Tooltip key="comment-list" title="评论列表"> |
| 148 | <span |
| 149 | className=" cursor-pointer" |
| 150 | onClick={() => openCommentList(item)} |
| 151 | > |
| 152 | 评论列表 |
| 153 | </span> |
| 154 | </Tooltip>, |
| 155 | <Tooltip key="reply" title="评论作品"> |
| 156 | <span |
| 157 | className=" cursor-pointer" |
| 158 | onClick={() => openReplyWorks(item)} |
| 159 | > |
| 160 | 评论作品 |
| 161 | </span> |
| 162 | </Tooltip>, |
| 163 | <Tooltip key="onekey" title="一键评论"> |
| 164 | <Popconfirm |
| 165 | title="确认进行一键评论" |
| 166 | onConfirm={() => { |
| 167 | Ref_OneKeyReply.current?.init( |
| 168 | activeAccountId, |
| 169 | item, |
| 170 | ); |
| 171 | }} |
| 172 | okText="是" |
| 173 | cancelText="否" |
| 174 | > |
| 175 | <span className=" cursor-pointer">一键评论</span> |
| 176 | </Popconfirm> |
| 177 | </Tooltip>, |
| 178 | <Tooltip key="auto" title="自动评论"> |
| 179 | <span |
| 180 | className="cursor-pointer" |
| 181 | onClick={() => openAddAutoRun(item)} |
| 182 | > |
| 183 | 自动评论 |
| 184 | </span> |
| 185 | </Tooltip>, |
| 186 | ]} |
| 187 | > |
| 188 | <Card.Meta |
| 189 | title={item.title || '无标题'} |
| 190 | description={ |
| 191 | <Text type="secondary"> |
| 192 | {item.desc || '暂无描述'} |
| 193 | </Text> |
| 194 | } |
| 195 | /> |
| 196 | </Card> |
| 197 | </div> |
| 198 | ))} |
| 199 | </Masonry> |
| 200 | |
| 201 | {/* 加载更多区域 */} |
| 202 | <div ref={loadMoreRef} className={styles.loadMoreArea}> |
| 203 | {isLoadingMore && ( |
| 204 | <div className={styles.loadingMore}> |
| 205 | <Spin size="small" /> |
| 206 | <span style={{ marginLeft: 8 }}>加载中...</span> |
| 207 | </div> |
| 208 | )} |
| 209 | |
| 210 | {!pageInfo.hasMore && wordList.length > 0 && ( |
| 211 | <div className={styles.noMoreData}> |
| 212 | <Divider plain>没有更多数据了</Divider> |
| 213 | </div> |
| 214 | )} |
| 215 | </div> |
| 216 | </div> |
| 217 | )} |
| 218 | </Tabs.TabPane> |
| 219 | <Tabs.TabPane tab="自动任务" key="2"> |
| 220 | <div style={{ width: '100%' }}> |
| 221 | <AutoRun /> |
| 222 | </div> |
| 223 | </Tabs.TabPane> |
| 224 | </Tabs> |
| 225 | </div> |
| 226 | |
| 227 | <ReplyWorks ref={Ref_ReplyWorks} /> |
| 228 | <AddAutoRun ref={Ref_AddAutoRun} /> |
| 229 | <CommentList ref={Ref_CommentList} /> |
| 230 | <OneKeyReply ref={Ref_OneKeyReply} /> |
| 231 | </div> |
| 232 | ); |
| 233 | } |
| 234 |