selectComponentsUnit.tsx 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. import React, { useEffect, useState, useRef } from "react"
  2. import style from './index.less'
  3. import { Card, Checkbox, Divider, Empty, message, Pagination, Result, Space, Spin, Typography, Image } from "antd"
  4. import { useAjax } from "@/Hook/useAjax"
  5. import './global.less'
  6. import '../../tencentAdPutIn/index.less'
  7. import { EyeOutlined } from "@ant-design/icons"
  8. import PlayVideo from "./playVideo"
  9. import Lazyimg from "react-lazyimg-component"
  10. import SyncCloudSc from "./syncCloudSc"
  11. import { checkAccountUnitApi } from "@/services/launchAdq/adAuthorize"
  12. import { getCreativeComponentListApi, GetCreativeComponentProps } from "@/services/adqV3/global"
  13. import { COMPONENT_GENERATION_TYPE_ENUM, COMPONENT_SUB_TYPE, getComponentType } from "../../tencenTasset/manageComponent/const"
  14. import moment from "moment"
  15. import SelectComponentsUnitSearch from "./selectComponentsUnitSearch"
  16. const { Text, Paragraph } = Typography;
  17. interface Props extends CLOUDNEW.SelectGroupCloudNewProps {
  18. isGroup?: boolean
  19. componentCount?: number
  20. }
  21. /**
  22. * 选择业务单元组件
  23. * @param param0
  24. * @returns
  25. */
  26. const SelectComponentsUnit: React.FC<Props> = ({ num: count, defaultParams, checkedFolderList, setCheckedFolderList, accountCreateLogs, active, isGroup, componentCount }) => {
  27. /*****************************************/
  28. const num = (isGroup ? componentCount : count) || 1
  29. const [rowNum, setRowNum] = useState<number>(0)
  30. const refScllor = useRef<HTMLDivElement>(null)
  31. const [checkFolderAll, setCheckFolderAll] = useState<boolean>(false);
  32. const [indeterminateFolder, setIndeterminateFolder] = useState<boolean>(false);
  33. const [queryParams, setQueryParams] = useState<GetCreativeComponentProps>({ pageNum: 1, pageSize: 20 })
  34. const [searchParams, setSearchParams] = useState<Partial<GetCreativeComponentProps>>({})
  35. const [unitAccountId, setUnitAccountId] = useState<number>()
  36. const [previewData, setPreviewData] = useState<{ visible: boolean, url?: string[] }>({ visible: false })
  37. const [componentSubType, setComponentSubType] = useState<string[]>([])
  38. const getCreativeComponentList = useAjax((params) => getCreativeComponentListApi(params))
  39. const checkAccountUnit = useAjax((params) => checkAccountUnitApi(params))
  40. /*****************************************/
  41. // 根据内容宽度计算列数
  42. useEffect(() => {
  43. let rowNum = Math.floor(1350 / 220)
  44. setRowNum(rowNum || 1)
  45. }, [])
  46. // 处理全选按钮状态
  47. useEffect(() => {
  48. let data: any[] = getCreativeComponentList?.data?.records || []
  49. let dataIds = data.map(item => item.componentId)
  50. let selectIds = checkedFolderList.map(item => item.id)
  51. if (selectIds.length === 0 || dataIds.length === 0) {
  52. setCheckFolderAll(false)
  53. setIndeterminateFolder(false);
  54. } else if (dataIds.every((element) => selectIds.includes(element))) {
  55. setCheckFolderAll(true)
  56. setIndeterminateFolder(false);
  57. } else if (dataIds.some((element) => selectIds.includes(element))) {
  58. setCheckFolderAll(false)
  59. setIndeterminateFolder(true);
  60. } else {
  61. setCheckFolderAll(false)
  62. setIndeterminateFolder(false);
  63. }
  64. }, [checkedFolderList, getCreativeComponentList?.data?.records])
  65. useEffect(() => {
  66. if (accountCreateLogs?.length) {
  67. checkAccountUnit.run(accountCreateLogs.map(item => item.accountId)).then(res => {
  68. setUnitAccountId(res?.adUnitAccountId)
  69. })
  70. }
  71. }, [accountCreateLogs])
  72. useEffect(() => {
  73. console.log('defaultParams--->', defaultParams)
  74. if (unitAccountId) {
  75. const materialType = defaultParams.materialType
  76. let componentSubType: string[] = []
  77. if (isGroup) {
  78. switch (count) {
  79. case 1:
  80. componentSubType = ['IMAGE_LIST_1X1_1']
  81. break
  82. case 3:
  83. componentSubType = ['IMAGE_LIST_1X1_3']
  84. break
  85. case 4:
  86. componentSubType = ['IMAGE_LIST_1X1_4']
  87. break
  88. case 6:
  89. componentSubType = ['IMAGE_LIST_1X1_6']
  90. break
  91. case 9:
  92. componentSubType = ['IMAGE_LIST_1X1_9']
  93. break
  94. }
  95. } else {
  96. // 组件化创意
  97. if (defaultParams?.sizeQueries && defaultParams?.sizeQueries?.length > 1) {
  98. if (defaultParams?.materialType === "image") {
  99. componentSubType = ['IMAGE_1X1', 'IMAGE_16X9', 'IMAGE_20X7', 'IMAGE_3X2', 'IMAGE_9X16']
  100. } else if (defaultParams?.materialType === "video") {
  101. componentSubType = ['VIDEO_16X9', 'VIDEO_9X16', 'VIDEO_4X3']
  102. }
  103. } else {
  104. const { width, height } = defaultParams?.sizeQueries?.[0] || { width: 0, height: 0 }
  105. if (width === 1280 && height === 720) {
  106. if (materialType === "video") {
  107. componentSubType = ['VIDEO_16X9']
  108. } else {
  109. componentSubType = ['IMAGE_16X9']
  110. }
  111. }
  112. }
  113. }
  114. setComponentSubType(componentSubType)
  115. // defaultParams
  116. const params = { ...searchParams, ...queryParams, adAccountId: unitAccountId, componentSubType, isDeleted: false }
  117. getCreativeComponentList.run(params)
  118. }
  119. }, [queryParams, defaultParams, searchParams, unitAccountId, isGroup, count])
  120. // 选择
  121. const onCheckboxChange = (checked: boolean, item: any, type: "IMAGE" | "VIDEO" | "IMAGE_LIST" | "OTHER") => {
  122. let newCheckedFolderList: any[] = JSON.parse(JSON.stringify(checkedFolderList))
  123. let data: { oss_url: string, key_frame_image_url?: string } = { oss_url: '' }
  124. switch (type) {
  125. case "IMAGE":
  126. data = { oss_url: item?.componentValue?.image?.value?.imageUrl }
  127. break
  128. case "VIDEO":
  129. data = {
  130. oss_url: item?.componentValue?.video?.value?.videoUrl || item?.componentValue?.shortVideo?.value?.shortVideo1Url,
  131. key_frame_image_url: item?.componentValue?.video?.value?.coverUrl || item?.componentValue?.shortVideo?.value?.shortVideo2Url
  132. }
  133. break
  134. case "IMAGE_LIST":
  135. data = { oss_url: item?.componentValue?.imageList?.value?.list }
  136. break
  137. }
  138. if (active || active === 0) {
  139. if (checked) { // 选中
  140. newCheckedFolderList[active] = { ...item, ...data, id: item.componentId, material_type: defaultParams.materialType, materialType: 4, isUnitComponent: true }
  141. } else { // 取消
  142. message.warning('请选择其他图片替换')
  143. return
  144. }
  145. } else {
  146. if (checked) { // 选中
  147. newCheckedFolderList.push({ ...item, ...data, id: item.componentId, material_type: defaultParams.materialType, materialType: 4, isUnitComponent: true })
  148. } else { // 取消
  149. const id = item.componentId
  150. newCheckedFolderList = newCheckedFolderList.filter(i => i.id !== id)
  151. }
  152. }
  153. setCheckedFolderList(newCheckedFolderList)
  154. };
  155. return <Spin spinning={checkAccountUnit.loading} wrapperClassName={'select_group_spin'}>
  156. <div className={style.select_cloudNew_layout}>
  157. {unitAccountId ? <>
  158. {/* 搜索 */}
  159. <SelectComponentsUnitSearch
  160. type={defaultParams.materialType}
  161. componentSubType={componentSubType}
  162. onSearch={(value) => {
  163. refScllor.current?.scrollTo(0, 0)
  164. setSearchParams(value)
  165. }}
  166. />
  167. <Card
  168. style={{ height: '100%', flex: '1 0', overflow: 'hidden' }}
  169. bodyStyle={{ padding: 0, overflow: 'auto hidden', height: '100%' }}
  170. className="cardResetCss buttonResetCss"
  171. bordered
  172. >
  173. <div className={style.cloudNew}>
  174. <div className={style.material} style={{ height: '100%' }}>
  175. <div className={style.operates}>
  176. <div className={style.left_bts}>
  177. <Checkbox
  178. onChange={(e) => {
  179. let newCheckedFolderList: any[] = JSON.parse(JSON.stringify(checkedFolderList))
  180. const data: any[] = getCreativeComponentList?.data?.records?.filter((item: { status: string }) => item?.status === '正常')
  181. if (e.target.checked) { // 全选
  182. const selectIds = newCheckedFolderList.map(item => item.id)
  183. const remainData = data.filter(item => {
  184. const id = item.componentId
  185. return !selectIds.includes(id)
  186. })
  187. const remainDataLength = remainData.length
  188. const remainNum = num - newCheckedFolderList.length
  189. if (remainNum > remainDataLength) {
  190. newCheckedFolderList.push(...remainData.map(i => {
  191. let data: { oss_url: string, key_frame_image_url?: string } = { oss_url: '' }
  192. const type = getComponentType(i.componentSubType)
  193. switch (type) {
  194. case "IMAGE":
  195. data = { oss_url: i?.componentValue?.image?.value?.imageUrl }
  196. break
  197. case "VIDEO":
  198. data = {
  199. oss_url: i?.componentValue?.video?.value?.videoUrl || i?.componentValue?.shortVideo?.value?.shortVideo1Url,
  200. key_frame_image_url: i?.componentValue?.video?.value?.coverUrl || i?.componentValue?.shortVideo?.value?.shortVideo2Url
  201. }
  202. break
  203. case "IMAGE_LIST":
  204. data = { oss_url: i?.componentValue?.imageList?.value?.list }
  205. break
  206. }
  207. return { ...i, ...data, material_type: defaultParams.materialType, id: i.componentId, materialType: 4, isUnitComponent: true }
  208. }))
  209. } else {
  210. newCheckedFolderList.push(...remainData.splice(0, remainNum).map(i => {
  211. let data: { oss_url: string, key_frame_image_url?: string } = { oss_url: '' }
  212. const type = getComponentType(i.componentSubType)
  213. switch (type) {
  214. case "IMAGE":
  215. data = { oss_url: i?.componentValue?.image?.value?.imageUrl }
  216. break
  217. case "VIDEO":
  218. data = {
  219. oss_url: i?.componentValue?.video?.value?.videoUrl || i?.componentValue?.shortVideo?.value?.shortVideo1Url,
  220. key_frame_image_url: i?.componentValue?.video?.value?.coverUrl || i?.componentValue?.shortVideo?.value?.shortVideo2Url
  221. }
  222. break
  223. case "IMAGE_LIST":
  224. data = { oss_url: i?.componentValue?.imageList?.value?.list }
  225. break
  226. }
  227. return { ...i, ...data, material_type: defaultParams.materialType, id: i.componentId, materialType: 4, isUnitComponent: true }
  228. }))
  229. }
  230. newCheckedFolderList = Array.from(new Set(newCheckedFolderList.map(item => JSON.stringify(item)))).map(item => JSON.parse(item));
  231. } else { // 取消全选
  232. const dataIds = data.map(item => item.componentId)
  233. newCheckedFolderList = newCheckedFolderList.filter(i => !dataIds.includes(i.id))
  234. }
  235. setCheckedFolderList(newCheckedFolderList)
  236. }}
  237. disabled={checkedFolderList?.length >= num || !!active || active === 0}
  238. indeterminate={indeterminateFolder}
  239. checked={checkFolderAll}
  240. >全选</Checkbox>
  241. <span>已选 <span style={{ color: '#1890FF' }}>{checkedFolderList?.length || 0}</span>/{num} 个素材</span>
  242. {checkedFolderList.length > 0 && <a style={{ color: 'red' }} onClick={() => setCheckedFolderList([])}>清除所有</a>}
  243. {/* 同步素材 */}
  244. <SyncCloudSc
  245. accountId={unitAccountId}
  246. />
  247. </div>
  248. <div className={style.left_bts}>
  249. </div>
  250. </div>
  251. <div className={`${style.content} content_global`}>
  252. <Spin spinning={getCreativeComponentList.loading}>
  253. <div className={`${style.content_scroll} scroll`} ref={refScllor}>
  254. {getCreativeComponentList?.data?.records?.length > 0 ? <Checkbox.Group value={checkedFolderList?.filter((_, index) => (active || active === 0) ? index === active : true)?.map(item => item.id)} style={{ width: '100%' }}>
  255. <div className={style.content_scroll_div}>
  256. {getCreativeComponentList?.data?.records.map((item: any) => {
  257. let id = item.componentId
  258. const componentSubType = item.componentSubType
  259. const type = getComponentType(componentSubType)
  260. const isSelect = checkedFolderList?.filter((_, index) => (active || active === 0) ? index === active : true)?.map(item => item.id).includes(id)
  261. const disabled = ((active || active === 0) ? false : (checkedFolderList.length >= num && !isSelect)) || item?.status !== '正常'
  262. const imageListLength = item?.componentValue?.imageList?.value?.list?.length || 0
  263. return <div key={id} className={style.content_row} style={{ width: rowNum ? (1 / rowNum * 100) + '%' : 220 }}>
  264. <Card
  265. hoverable
  266. bodyStyle={{ padding: 0 }}
  267. style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}
  268. className={`${style.content_col}`}
  269. cover={<div style={{ height: 120, padding: 0 }} className={style.content_cover}>
  270. <div className={style.checkbox}><Checkbox disabled={disabled} value={id} onChange={(e) => onCheckboxChange(e.target.checked, item, type)} /></div>
  271. {defaultParams.materialType === 'video' ? <div className={style.playr}>
  272. <PlayVideo videoUrl={item?.componentValue?.video?.value?.videoUrl || item?.componentValue?.shortVideo?.value?.shortVideo1Url}>{(onPlay) => <img onClick={(e) => {
  273. e.stopPropagation(); e.preventDefault()
  274. onPlay()
  275. }} src={require('../../../../../public/image/play.png')} alt="" />}</PlayVideo>
  276. </div> : <div className={style.imgPreview} onClick={(e) => {
  277. e.stopPropagation()
  278. setPreviewData({
  279. visible: true,
  280. url: item?.componentValue?.image?.value?.imageUrl ? [item?.componentValue?.image?.value?.imageUrl] : item?.componentValue?.imageList?.value?.list?.map((i: { imageUrl: any }) => i.imageUrl)
  281. })
  282. }}>
  283. <Space><EyeOutlined /><span>预览</span></Space>
  284. </div>}
  285. <div className={style.file_info}>
  286. <span></span>
  287. <div>{COMPONENT_SUB_TYPE?.find(item => item.value === componentSubType)?.label || '--'}</div>
  288. </div>
  289. {type === 'IMAGE' ? <Lazyimg
  290. animateType="transition"
  291. src={item?.componentValue?.image?.value?.imageUrl}
  292. className={`${style.coverImg} lazy`}
  293. animateClassName={['transition-enter', 'transition-enter-active']}
  294. /> : type === 'IMAGE_LIST' ? <div className={style.mediaPic_select}>
  295. <div className={`${style.mediaPicImgList} ${style['mediaPicImgList' + imageListLength]}`}>
  296. {item?.componentValue?.imageList?.value?.list?.map((item: { componentValue: { imageList: { value: { list: string | any[] } } }; imageId: string; imageUrl: string }, _: any, records: any[]) => <Lazyimg
  297. key={item.imageId}
  298. animateType="transition"
  299. src={item.imageUrl}
  300. className={`lazy`}
  301. animateClassName={['transition-enter', 'transition-enter-active']}
  302. />)}
  303. </div>
  304. </div> : type === 'VIDEO' ? <Lazyimg
  305. animateType="transition"
  306. src={item?.componentValue?.video?.value?.coverUrl || item?.componentValue?.shortVideo?.value?.shortVideo2Url}
  307. className={`${style.coverImg} lazy`}
  308. animateClassName={['transition-enter', 'transition-enter-active']}
  309. /> : '联系技术添加'}
  310. </div>}
  311. onClick={() => !disabled && onCheckboxChange(!isSelect, item, type)}
  312. >
  313. <div className={style.body}>
  314. <Text ellipsis strong style={{ flex: '1 0', color: item?.status !== '正常' ? 'red' : 'rgba(0, 0, 0, 0.85)' }}>{item?.status !== '正常' ? item?.status : item?.componentCustomName}</Text>
  315. </div>
  316. <Divider style={{ margin: '0 0 4px 0' }} />
  317. <div style={{ padding: '0 10px 6px' }}>
  318. <Paragraph style={{ fontSize: 12, marginBottom: 1 }}>创建时间:{moment.unix(item?.createdTime).format('YYYY-MM-DD')}</Paragraph>
  319. <Paragraph style={{ fontSize: 12, marginBottom: 1 }}>组件ID:{item?.componentId}</Paragraph>
  320. <Paragraph style={{ fontSize: 12, marginBottom: 1 }}>来源:{COMPONENT_GENERATION_TYPE_ENUM['COMPONENT_GENERATION_TYPE_' + item.generationType as keyof typeof COMPONENT_GENERATION_TYPE_ENUM] || '--'}</Paragraph>
  321. </div>
  322. </Card>
  323. </div>
  324. })}
  325. </div>
  326. </Checkbox.Group> : <div style={{ height: '100%', width: '100%', alignContent: 'center' }}><Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /></div>}
  327. </div>
  328. </Spin>
  329. </div>
  330. <div className={style.fotter}>
  331. <Pagination
  332. size="small"
  333. total={getCreativeComponentList?.data?.total || 0}
  334. showSizeChanger
  335. showQuickJumper
  336. pageSize={getCreativeComponentList?.data?.size || 20}
  337. current={getCreativeComponentList?.data?.current || 1}
  338. onChange={(page: number, pageSize: number) => {
  339. refScllor.current?.scrollTo(0, 0)
  340. setQueryParams({ ...queryParams, pageNum: page, pageSize })
  341. }}
  342. pageSizeOptions={[10, 15, 20, 50, 100]}
  343. />
  344. </div>
  345. </div>
  346. </div>
  347. </Card>
  348. </> : checkAccountUnit?.data ? <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
  349. <Result
  350. title="警告"
  351. status="500"
  352. subTitle="选择的账户不在一个业务单元中或者没有加入业务单元"
  353. />
  354. </div> : undefined}
  355. </div>
  356. {/* 预览 */}
  357. {previewData.visible && <Image.PreviewGroup preview={{ visible: previewData.visible, onVisibleChange: vis => setPreviewData({ visible: vis, url: [] }) }}>
  358. {previewData?.url?.map((url: string, index: number) => <Image src={url} key={index} />)}
  359. </Image.PreviewGroup>}
  360. </Spin>
  361. }
  362. export default React.memo(SelectComponentsUnit)