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