返回 AiToEarn
DouyinCommonComponents.tsx
根目录 / project / aitoearn-electron / src / views / publish / components / CommonComponents / DouyinCommonComponents.tsx
1 import React, { useEffect, useMemo, useRef, useState } from 'react';
2 import {
3 DouyinActivity,
4 DouyinActivityDetailResponse,
5 DouyinHotSentence,
6 DouyinQueryTags,
7 } from '../../../../../electron/plat/douyin/douyin.type';
8 import useDebounceFetcher from '../../children/videoPage/components/VideoPubSetModal/components/useDebounceFetcher';
9 import {
10 getDouyinActivityDetails,
11 icpGetActivityTags,
12 icpGetDouyinActivity,
13 icpGetDoytinHot,
14 icpGetDoytinHotAll,
15 } from '../../../../icp/publish';
16 import {
17 Button,
18 Checkbox,
19 Empty,
20 message,
21 Modal,
22 Select,
23 SelectProps,
24 Spin,
25 Tabs,
26 Tooltip,
27 } from 'antd';
28 import { QuestionCircleOutlined } from '@ant-design/icons';
29 import styles from './commonComponents.module.scss';
30 import { describeNumber } from '../../../../utils';
31 import { onAccountLoginFinish } from '../../../../icp/receiveMsg';
32 import { AccountInfo, AccountPlatInfoMap } from '../../../account/comment';
33 import { ILableValue } from '../../../../../electron/db/models/video';
34 import { PlatType } from '../../../../../commont/AccountEnum';
35
36 interface DouyinCommonComponentsProps
37 extends Omit<SelectProps<ILableValue[]>, 'options' | 'children'> {
38 account?: AccountInfo;
39 children?: React.ReactNode;
40 }
41
42 interface DouyinCommonComponentsHotProps
43 extends Omit<SelectProps<ILableValue>, 'options' | 'children'> {
44 account?: AccountInfo;
45 children?: React.ReactNode;
46 }
47
48 // 抖音热点
49 export const CommonHotspotSelect = ({
50 account,
51 children,
52 ...props
53 }: DouyinCommonComponentsHotProps) => {
54 if (!account) return '';
55
56 const [doytinHotAll, setDoytinHotAll] = useState<DouyinHotSentence[]>([]);
57 const [keywords, setKeywords] = useState('');
58
59 const { fetching, options, debounceFetcher } =
60 useDebounceFetcher<DouyinHotSentence>(async (keywords) => {
61 setKeywords(keywords);
62 const res = await icpGetDoytinHot(account, keywords || '');
63 return res.sentences;
64 });
65
66 useEffect(() => {
67 icpGetDoytinHotAll().then((res) => {
68 setDoytinHotAll(res.all_sentences);
69 });
70 }, []);
71
72 return (
73 <>
74 <h1>
75 申请关联热点
76 <Tooltip title="你可以申请和一个热点做关联,如果视频确实和热点非常相关,将会进入抖音热点榜,若不相关则不会生效。">
77 <QuestionCircleOutlined style={{ marginLeft: '2px' }} />
78 </Tooltip>
79 </h1>
80 <Select
81 showSearch
82 allowClear
83 style={{ width: '100%' }}
84 placeholder="输入热点词搜索"
85 labelInValue
86 filterOption={false}
87 onSearch={debounceFetcher}
88 notFoundContent={fetching ? <Spin size="small" /> : null}
89 options={(!keywords ? doytinHotAll : options)?.map((v) => {
90 return {
91 ...v,
92 label: v.word,
93 value: v.sentence_id,
94 };
95 })}
96 optionRender={({ data }) => {
97 return (
98 <div className={styles.hotspotSelect}>
99 <div className="hotspotSelect-left">
100 <img src={data.word_cover?.url_list[0]} />
101 <span>{data.word}</span>
102 </div>
103 <div className="hotspotSelect-right">
104 {describeNumber(data.hot_value)}在看
105 </div>
106 </div>
107 );
108 }}
109 {...props}
110 />
111 {children}
112 </>
113 );
114 };
115
116 // 抖音活动
117 export const CommonActivitySelect = ({
118 account,
119 children,
120 ...props
121 }: DouyinCommonComponentsProps) => {
122 if (!account) return '';
123 const [loading, setLoading] = useState(false);
124 const [detailsOpen, setDetailsOpen] = useState(false);
125 const [activityOpen, setActivityOpen] = useState(false);
126 const [options, setOptions] = useState<DouyinActivity[]>([]);
127 const activityTagsMap = useRef<Map<number, DouyinQueryTags>>(new Map());
128 // 活动标签数据
129 const [activityTagList, setActivityTagList] = useState<DouyinQueryTags[]>([]);
130 const [activityDetails, setActivityDetails] =
131 useState<DouyinActivityDetailResponse>();
132 const [activityTag, setActivityTag] = useState<string>('');
133 const [value, setValue] = useState<ILableValue[]>([]);
134
135 const init = () => {
136 icpGetDouyinActivity(account!).then((res) => {
137 setOptions(res.activity_list || []);
138 });
139 icpGetActivityTags(account!).then((res) => {
140 if (!res.data.query_tags) return;
141 res.data?.query_tags?.map((v) => {
142 activityTagsMap.current.set(v.id, v);
143 });
144 setActivityTagList(res.data.query_tags || []);
145 setActivityTag(`${res.data.query_tags[0]?.id}`);
146 });
147 };
148
149 useEffect(() => {
150 init();
151
152 return onAccountLoginFinish((newAccount) => {
153 account = newAccount;
154 init();
155 });
156 }, []);
157
158 const selection = (data: DouyinActivity) => {
159 if (value?.some((v) => v.value === data.activity_id)) {
160 setValue(value?.filter((v) => v.value !== data.activity_id));
161 } else {
162 setValue([
163 ...(value || []),
164 {
165 label: data.activity_name,
166 value: data.activity_id,
167 },
168 ]);
169 }
170 };
171
172 useEffect(() => {
173 setValue(props.value || []);
174 }, [props.value]);
175
176 const closeSelectActivity = () => {
177 setActivityOpen(false);
178 setValue(props.value || []);
179 };
180
181 // 选择标签筛选后的数据
182 const tagActivityData = useMemo(() => {
183 return options?.filter((v) => v.query_tag === +activityTag);
184 }, [activityTag, options]);
185
186 return (
187 <>
188 <Modal
189 open={detailsOpen}
190 title="活动详情"
191 footer={null}
192 onCancel={() => setDetailsOpen(false)}
193 >
194 <Spin spinning={loading}>
195 <div className={styles.activityDetails}>
196 <div className="activityDetails-top">
197 <div className="activityDetails-top-left">
198 <div className="activityDetails-top-left-img">
199 <img src={activityDetails?.activity_info.cover_image} />
200 </div>
201 <ul>
202 <li className="activityDetails-top-left-title">
203 {activityDetails?.activity_info.activity_name}
204 </li>
205 <li className="activityDetails-top-left-hot">
206 活动热度:{activityDetails?.activity_info.hot_score}
207 </li>
208 </ul>
209 </div>
210
211 <div className="activityDetails-top-tag">
212 {
213 activityTagsMap.current.get(
214 activityDetails?.activity_info.query_tag || 0,
215 )?.name
216 }
217 </div>
218 </div>
219
220 <div className="activityDetails-bottom">
221 <h2>活动时间</h2>
222 <div className="activityDetails-bottom-text">
223 活动时间:{activityDetails?.publish_start_time}-
224 {activityDetails?.publish_end_time}
225 </div>
226 <h2>活动玩法</h2>
227 <div className="activityDetails-bottom-text">
228 {activityDetails?.activity_description}
229 </div>
230 <h2>关联话题</h2>
231 <ul className="activityDetails-bottom-text">
232 {activityDetails?.topics?.map((v) => {
233 return <li key={v}>#{v}</li>;
234 })}
235 </ul>
236 <h2>流量奖励</h2>
237 <div className="activityDetails-bottom-text">
238 {activityDetails?.reward_rules
239 ? JSON.parse(activityDetails?.reward_rules).text
240 : ''}
241 </div>
242 </div>
243 </div>
244 </Spin>
245 </Modal>
246
247 <Modal
248 title="选择活动"
249 width={700}
250 open={activityOpen}
251 onCancel={closeSelectActivity}
252 onOk={() => {
253 if (value.length > props.maxCount!) {
254 message.warning(
255 `话题和活动最多可添加 ${AccountPlatInfoMap.get(PlatType.Douyin)?.commonPubParamsConfig.topicMax} 个`,
256 );
257 } else {
258 setActivityOpen(false);
259 if (props.onChange) props.onChange(value);
260 }
261 }}
262 >
263 <Tabs
264 activeKey={activityTag}
265 onChange={setActivityTag}
266 type="card"
267 items={activityTagList?.map((v) => {
268 return {
269 key: `${v.id}`,
270 label: v.name,
271 };
272 })}
273 />
274 {tagActivityData && tagActivityData.length !== 0 ? (
275 tagActivityData?.map((data) => {
276 return (
277 <div
278 className={styles.activitySelect}
279 onClick={() => {
280 selection(data);
281 }}
282 key={data.activity_id}
283 >
284 <div className="activitySelect-left">
285 <Checkbox
286 checked={value?.some((v) => v.value === data.activity_id)}
287 onClick={(e) => {
288 e.stopPropagation();
289 }}
290 onChange={(e) => {
291 selection(data);
292 }}
293 />
294 <div className="activitySelect-left-img">
295 <img src={data.cover_image} />
296 </div>
297 <ul>
298 <li>{data.activity_name}</li>
299 <li>热度:{data.hot_score}</li>
300 </ul>
301 </div>
302 <div className="activitySelect-right">
303 <div className="activitySelect-right-top">
304 {activityTagsMap.current?.get(data.query_tag)?.name}
305 </div>
306 <div className="activitySelect-right-bottom">
307 <span>时间:02.27~03.31</span>
308 <Button
309 type="link"
310 onClick={async (e) => {
311 e.stopPropagation();
312 setDetailsOpen(true);
313 setLoading(true);
314 setActivityDetails(undefined);
315 const res = await getDouyinActivityDetails(
316 account!,
317 data.activity_id,
318 );
319 setLoading(false);
320 setActivityDetails(res);
321 }}
322 >
323 活动详情
324 </Button>
325 </div>
326 </div>
327 </div>
328 );
329 })
330 ) : (
331 <Empty />
332 )}
333 </Modal>
334
335 <h1>
336 活动奖励
337 <Tooltip title="添加活动将有机会获得流量奖励">
338 <QuestionCircleOutlined style={{ marginLeft: '2px' }} />
339 </Tooltip>
340 </h1>
341 <Select
342 showSearch={false}
343 allowClear
344 style={{ width: '100%' }}
345 placeholder="点击选择活动奖励"
346 labelInValue
347 mode="multiple"
348 filterOption={false}
349 onClick={(e) => {
350 e.preventDefault();
351 e.stopPropagation();
352 setActivityOpen(true);
353 }}
354 dropdownRender={() => {
355 const customDropdownRef = useRef<HTMLDivElement>(null);
356 if (customDropdownRef.current) {
357 (
358 customDropdownRef.current.parentNode!.parentNode as HTMLDivElement
359 ).style.display = 'none';
360 }
361 return <div ref={customDropdownRef} />;
362 }}
363 options={options?.map((v) => {
364 return {
365 ...v,
366 label: v.activity_name,
367 value: v.activity_id,
368 };
369 })}
370 onDropdownVisibleChange={() => {
371 if (options?.length === 0 && account?.status === 0) {
372 init();
373 }
374 }}
375 {...props}
376 />
377 <p className={styles.tips}>活动奖励 + 话题 最多不能超过五个。</p>
378 {children}
379 </>
380 );
381 };
382
382 lines Plain Text