selectGroupCloudNew.tsx 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. import React, { useEffect, useRef, useState } from "react"
  2. import style from './index.less'
  3. import { Button, Card, Checkbox, Divider, Empty, Form, message, Pagination, Popover, Radio, Result, Select, Space, Spin, Typography, Image } from "antd"
  4. import { useAjax } from "@/Hook/useAjax"
  5. import { getPageRemoteImageDataListApi } from "@/services/adqV3/cloudNew"
  6. import { useModel } from "umi"
  7. import { formatBytes, formatSecondsToTime, groupBy } from "@/utils/utils"
  8. import SelectGroupSearch from "./selectGroupSearch"
  9. import './global.less'
  10. import '../../tencentAdPutIn/index.less'
  11. import { showFieldList } from "./const"
  12. import { EyeOutlined, SortAscendingOutlined } from "@ant-design/icons"
  13. import PlayVideo from "./playVideo"
  14. import Lazyimg from "react-lazyimg-component"
  15. const { Text, Paragraph } = Typography;
  16. /**
  17. * 选择账户组媒体素材
  18. * @param param0
  19. * @returns
  20. */
  21. const SelectGroupCloudNew: React.FC<CLOUDNEW.SelectGroupCloudNewProps> = ({ num, defaultParams, checkedFolderList, setCheckedFolderList, sortData, setSortData, showField, setShowField, accountCreateLogs }) => {
  22. /*****************************************/
  23. const { getAllUserAccount } = useModel('useLaunchAdq.useAdAuthorize')
  24. const [rowNum, setRowNum] = useState<number>(0)
  25. const [checkFolderAll, setCheckFolderAll] = useState<boolean>(false);
  26. const [indeterminateFolder, setIndeterminateFolder] = useState<boolean>(false);
  27. const [queryParams, setQueryParams] = useState<Omit<CLOUDNEW.GetPageRemoteImageDataListProps, 'materialType'>>({ pageNum: 1, pageSize: 20 })
  28. const [searchParams, setSearchParams] = useState<Partial<Omit<CLOUDNEW.GetPageRemoteImageDataListProps, 'materialType'>>>({})
  29. const [ownerAccountId, setOwnerAccountId] = useState<number>()
  30. const [previewData, setPreviewData] = useState<{ visible: boolean, url?: string }>({ visible: false })
  31. const getPageRemoteImageDataList = useAjax((params) => getPageRemoteImageDataListApi(params))
  32. /*****************************************/
  33. // 根据内容宽度计算列数
  34. useEffect(() => {
  35. let rowNum = Math.floor(1350 / 220)
  36. setRowNum(rowNum || 1)
  37. }, [])
  38. // 处理全选按钮状态
  39. useEffect(() => {
  40. let data: any[] = getPageRemoteImageDataList?.data?.records || []
  41. let dataIds = data.map(item => item.image_id || item.video_id)
  42. let selectIds = checkedFolderList.map(item => item.id)
  43. if (selectIds.length === 0 || dataIds.length === 0) {
  44. setCheckFolderAll(false)
  45. setIndeterminateFolder(false);
  46. } else if (dataIds.every((element) => selectIds.includes(element))) {
  47. setCheckFolderAll(true)
  48. setIndeterminateFolder(false);
  49. } else if (dataIds.some((element) => selectIds.includes(element))) {
  50. setCheckFolderAll(false)
  51. setIndeterminateFolder(true);
  52. } else {
  53. setCheckFolderAll(false)
  54. setIndeterminateFolder(false);
  55. }
  56. }, [checkedFolderList, getPageRemoteImageDataList?.data?.records])
  57. useEffect(() => {
  58. if (accountCreateLogs?.length) {
  59. getAllUserAccount.run().then(res => {
  60. if (res?.data?.length) {
  61. const list = accountCreateLogs.map(item => item.accountId)
  62. const selectAccountList = res.data.filter((item: { accountId: any }) => list.includes(item.accountId))
  63. const groupAccount = groupBy(selectAccountList, (item) => item.groupId, true)
  64. const GrounpArray = Object.keys(groupAccount).map(function (group) {
  65. return groupAccount[group];
  66. })
  67. if (GrounpArray.length === 1 && !groupAccount['undefined']) {
  68. setOwnerAccountId(GrounpArray?.[0]?.[0]?.authMainAccountId || GrounpArray?.[0]?.[0]?.accountId)
  69. } else if (GrounpArray.length === 2 && groupAccount?.['undefined']?.length === 1 && groupAccount?.['undefined']?.[0]?.isGroupMainAccount) {
  70. const undefinedAccount = groupAccount?.['undefined'][0].accountId
  71. let authMainAccountId: any
  72. Object.keys(groupAccount).forEach(key => {
  73. if (key !== 'undefined') {
  74. authMainAccountId = groupAccount[key][0].authMainAccountId
  75. }
  76. })
  77. if (undefinedAccount === authMainAccountId) {
  78. setOwnerAccountId(authMainAccountId)
  79. } else {
  80. setOwnerAccountId(-1)
  81. }
  82. } else if (groupAccount?.['undefined']?.length === 1 && groupAccount?.['undefined']?.[0]?.isGroupMainAccount) {
  83. setOwnerAccountId(groupAccount?.['undefined']?.[0]?.accountId)
  84. } else {
  85. setOwnerAccountId(-1)
  86. }
  87. }
  88. })
  89. }
  90. }, [accountCreateLogs])
  91. useEffect(() => {
  92. if (ownerAccountId) {
  93. let params = { ...searchParams, ...defaultParams, ...queryParams, ownerAccountId }
  94. if (sortData?.sortField) {
  95. params = { ...params, ...sortData }
  96. }
  97. getPageRemoteImageDataList.run(params)
  98. }
  99. }, [queryParams, defaultParams, searchParams, sortData, showField, ownerAccountId])
  100. // 选择
  101. const onCheckboxChange = (checked: boolean, item: any) => {
  102. let newCheckedFolderList: any[] = JSON.parse(JSON.stringify(checkedFolderList))
  103. if (checked) { // 选中
  104. newCheckedFolderList.push({ ...item, id: item.image_id || item.video_id, material_type: defaultParams.materialType, oss_url: item.preview_url, materialType: 1 })
  105. } else { // 取消
  106. let id = item.image_id || item.video_id
  107. newCheckedFolderList = newCheckedFolderList.filter(i => i.id !== id)
  108. }
  109. setCheckedFolderList(newCheckedFolderList)
  110. };
  111. return <Spin spinning={getAllUserAccount.loading} wrapperClassName={'select_group_spin'}>
  112. <div className={style.select_cloudNew_layout}>
  113. {ownerAccountId && ownerAccountId !== -1 ? <>
  114. {/* 搜索 */}
  115. <SelectGroupSearch
  116. onSearch={(value) => { setSearchParams(value) }}
  117. />
  118. <Card
  119. style={{ height: '100%', flex: '1 0', overflow: 'hidden' }}
  120. bodyStyle={{ padding: 0, overflow: 'auto hidden', height: '100%' }}
  121. className="cardResetCss buttonResetCss"
  122. bordered
  123. >
  124. <div className={style.cloudNew}>
  125. <div className={style.material} style={{ height: '100%' }}>
  126. <div className={style.operates}>
  127. <div className={style.left_bts}>
  128. <Checkbox
  129. onChange={(e) => {
  130. let newCheckedFolderList: any[] = JSON.parse(JSON.stringify(checkedFolderList))
  131. const data: any[] = getPageRemoteImageDataList?.data?.records
  132. if (e.target.checked) { // 全选
  133. const selectIds = newCheckedFolderList.map(item => item.id)
  134. const remainData = data.filter(item => {
  135. const id = item.image_id || item.video_id
  136. return !selectIds.includes(id)
  137. })
  138. const remainDataLength = remainData.length
  139. const remainNum = num - newCheckedFolderList.length
  140. if (remainNum > remainDataLength) {
  141. newCheckedFolderList.push(...remainData.map(i => ({ ...i, material_type: defaultParams.materialType, id: i.image_id || i.video_id, oss_url: i.preview_url, materialType: 1 })))
  142. } else {
  143. newCheckedFolderList.push(...remainData.splice(0, remainNum).map(i => ({ ...i, material_type: defaultParams.materialType, id: i.image_id || i.video_id, oss_url: i.preview_url, materialType: 1 })))
  144. }
  145. newCheckedFolderList = Array.from(new Set(newCheckedFolderList.map(item => JSON.stringify(item)))).map(item => JSON.parse(item));
  146. } else { // 取消全选
  147. const dataIds = data.map(item => item.image_id || item.video_id)
  148. newCheckedFolderList = newCheckedFolderList.filter(i => !dataIds.includes(i.id))
  149. }
  150. setCheckedFolderList(newCheckedFolderList)
  151. }}
  152. disabled={checkedFolderList?.length >= num}
  153. indeterminate={indeterminateFolder}
  154. checked={checkFolderAll}
  155. >全选</Checkbox>
  156. <span>已选 <span style={{ color: '#1890FF' }}>{checkedFolderList?.length || 0}</span>/{num} 个素材</span>
  157. {checkedFolderList.length > 0 && <a style={{ color: 'red' }} onClick={() => setCheckedFolderList([])}>清除所有</a>}
  158. {sortData?.sortField && <Text>「排序-{showFieldList.find(item => item.value === sortData.sortField)?.label}-{sortData.sortType ? '正序' : '倒序'}」</Text>}
  159. </div>
  160. <div className={style.left_bts}>
  161. <Popover
  162. content={<div style={{ width: 320 }}>
  163. <Form
  164. labelCol={{ span: 5 }}
  165. labelAlign="left"
  166. colon={false}
  167. >
  168. <Form.Item label={<strong>展示指标</strong>}>
  169. <Select
  170. placeholder="选择展示指标"
  171. showSearch
  172. filterOption={(input, option) =>
  173. (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
  174. }
  175. mode="multiple"
  176. options={showFieldList}
  177. value={showField}
  178. onChange={(value) => {
  179. if (value.length > 6) {
  180. message.warn('最多只能选择6个指标')
  181. return
  182. }
  183. if (value.length < 1) {
  184. message.warn('最少选择1个指标')
  185. return
  186. }
  187. setSortData({ ...sortData, sortField: undefined })
  188. setShowField(value as any)
  189. }}
  190. />
  191. </Form.Item>
  192. <Form.Item label={<strong>排序</strong>}>
  193. <Space>
  194. <Select
  195. style={{ width: 125 }}
  196. placeholder="选择排序指标"
  197. showSearch
  198. filterOption={(input, option) =>
  199. (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
  200. }
  201. options={showFieldList.filter(item => showField.includes(item.value) && item.value !== 'description')}
  202. value={sortData.sortField}
  203. allowClear
  204. onChange={(value) => {
  205. setSortData({ ...sortData, sortField: value as any })
  206. }}
  207. />
  208. <Radio.Group value={sortData.sortType} onChange={(e) => setSortData({ ...sortData, sortType: e.target.value })} buttonStyle="solid">
  209. <Radio.Button value={true}>正序</Radio.Button>
  210. <Radio.Button value={false}>倒序</Radio.Button>
  211. </Radio.Group>
  212. </Space>
  213. </Form.Item>
  214. </Form>
  215. </div>}
  216. trigger={['click']}
  217. title={<strong>自定义展示指标与排序</strong>}
  218. placement="bottomRight"
  219. >
  220. <Button icon={<SortAscendingOutlined />}>自定义展示指标与排序</Button>
  221. </Popover>
  222. </div>
  223. </div>
  224. <div className={`${style.content} content_global`}>
  225. <Spin spinning={getPageRemoteImageDataList.loading}>
  226. <div className={`${style.content_scroll} scroll`}>
  227. {getPageRemoteImageDataList?.data?.records?.length > 0 ? <Checkbox.Group value={checkedFolderList?.map(item => item.id)} style={{ width: '100%' }}>
  228. <div className={style.content_scroll_div}>
  229. {getPageRemoteImageDataList?.data?.records.map((item: any) => {
  230. let id = defaultParams.materialType === 'image' ? item.image_id : item.video_id
  231. const isSelect = checkedFolderList?.map(item => item.id).includes(id)
  232. const disabled = checkedFolderList.length >= num && !isSelect
  233. return <div key={id} className={style.content_row} style={{ width: rowNum ? (1 / rowNum * 100) + '%' : 220 }}>
  234. <Card
  235. hoverable
  236. bodyStyle={{ padding: 0 }}
  237. style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}
  238. className={`${style.content_col}`}
  239. cover={<div style={{ height: 120, padding: 0 }} className={style.content_cover}>
  240. <div className={style.checkbox}><Checkbox disabled={disabled} value={id} onChange={(e) => onCheckboxChange(e.target.checked, item)} /></div>
  241. {defaultParams.materialType === 'video' ? <div className={style.playr}>
  242. <PlayVideo videoUrl={item.preview_url}>{(onPlay) => <img onClick={(e) => {
  243. e.stopPropagation(); e.preventDefault()
  244. onPlay()
  245. }} src={require('../../../../../public/image/play.png')} alt="" />}</PlayVideo>
  246. </div> : <div className={style.imgPreview} onClick={(e) => {
  247. e.stopPropagation()
  248. setPreviewData({ visible: true, url: item.preview_url })
  249. }}>
  250. <Space><EyeOutlined /><span>预览</span></Space>
  251. </div>}
  252. <div className={style.file_info}>
  253. <div>{item.width}*{item.height}</div>
  254. {defaultParams.materialType === 'video' && item.image_duration_millisecond && <div>{formatSecondsToTime(Math.floor(item.image_duration_millisecond / 1000))}</div>}
  255. </div>
  256. <Lazyimg
  257. animateType="transition"
  258. src={defaultParams.materialType === 'image' ? item.preview_url : item?.key_frame_image_url}
  259. className={`${style.coverImg} lazy`}
  260. animateClassName={['transition-enter', 'transition-enter-active']}
  261. />
  262. </div>}
  263. onClick={() => !disabled && onCheckboxChange(!isSelect, item)}
  264. >
  265. <div className={style.body}>
  266. <Text ellipsis strong style={{ flex: '1 0' }}>{item?.material_name}</Text>
  267. <Text style={{ fontSize: 12 }}>{formatBytes(item?.file_size)}</Text>
  268. </div>
  269. <Divider style={{ margin: '0 0 4px 0' }} />
  270. <div style={{ padding: '0 10px 6px' }}>
  271. {showField.map(field => {
  272. switch (field) {
  273. case 'material.create_time':
  274. return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>创建时间:{item?.create_time}</Paragraph>
  275. case 'material_data_day.cost':
  276. return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>消耗:{(item?.cost === null || item?.cost === undefined) ? '--' : item?.cost}</Paragraph>
  277. case 'material_data_day.order_pv':
  278. return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>下单次数:{(item?.order_pv === null || item?.order_pv === undefined) ? '--' : item?.order_pv}</Paragraph>
  279. case 'material_data_day.order_cost':
  280. return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>下单成本:{(item?.order_cost === null || item?.order_cost === undefined) ? '--' : item?.order_cost}</Paragraph>
  281. case 'material_data_day.ctr':
  282. return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>点击率:{(item?.ctr === null || item?.ctr === undefined) ? '--' : (item?.ctr * 100).toFixed(2) + '%'}</Paragraph>
  283. case 'material_data_day.conversions_rate':
  284. return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>目标转化率:{(item?.conversions_rate === null || item?.conversions_rate === undefined) ? '--' : (item?.conversions_rate * 100).toFixed(2) + '%'}</Paragraph>
  285. case 'material_data_day.adgroup_count':
  286. return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>广告关联数:{(item?.adgroup_count === null || item?.adgroup_count === undefined) ? '--' : item?.adgroup_count}</Paragraph>
  287. case 'material_data_day.dynamic_creative_count':
  288. return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>创意关联数:{(item?.dynamic_creative_count === null || item?.dynamic_creative_count === undefined) ? '--' : item?.dynamic_creative_count}</Paragraph>
  289. default:
  290. return null
  291. }
  292. })}
  293. {showField.includes('description') && <Paragraph style={{ fontSize: 12, marginBottom: 1 }} ellipsis={{ tooltip: true }}>备注:{item.description || '--'}</Paragraph>}
  294. </div>
  295. </Card>
  296. </div>
  297. })}
  298. </div>
  299. </Checkbox.Group> : <div style={{ height: '100%', width: '100%', alignContent: 'center' }}><Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /></div>}
  300. </div>
  301. </Spin>
  302. </div>
  303. <div className={style.fotter}>
  304. <Pagination
  305. size="small"
  306. total={getPageRemoteImageDataList?.data?.total || 0}
  307. showSizeChanger
  308. showQuickJumper
  309. pageSize={getPageRemoteImageDataList?.data?.size || 20}
  310. current={getPageRemoteImageDataList?.data?.current || 1}
  311. onChange={(page: number, pageSize: number) => {
  312. setQueryParams({ ...queryParams, pageNum: page, pageSize })
  313. }}
  314. />
  315. </div>
  316. </div>
  317. </div>
  318. </Card>
  319. </> : ownerAccountId === -1 ? <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
  320. <Result
  321. title="警告"
  322. status="500"
  323. subTitle="选择的账户不在一个账户分组中."
  324. />
  325. </div> : undefined}
  326. </div>
  327. {/* 预览 */}
  328. {previewData.visible && <Image
  329. width={200}
  330. style={{ display: 'none' }}
  331. preview={{
  332. visible: previewData.visible,
  333. src: previewData.url,
  334. onVisibleChange: value => {
  335. setPreviewData({ visible: false })
  336. },
  337. }}
  338. />}
  339. </Spin>
  340. }
  341. export default React.memo(SelectGroupCloudNew)