index.tsx 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. import { useAjax } from "@/Hook/useAjax"
  2. import { delBatchCreativeApi, getDynamicCreativeV3ListApi, syncBatchCreativeApi, updateBatchDynamicCreativesInfoApi } from "@/services/launchAdq/adqv3"
  3. import { Badge, Button, Checkbox, Col, DatePicker, Form, Input, Modal, Popconfirm, Row, Select, Space, Table, Tooltip, message } from "antd"
  4. import React, { useEffect, useState } from "react"
  5. import { txDynamicConfig } from "../config"
  6. import tableConfig from "./tableConfig"
  7. import TableData from "@/pages/launchSystemNew/components/TableData"
  8. import ReviewDetails from "./reviewDetails"
  9. import { DeleteOutlined, PauseCircleOutlined, PlayCircleOutlined, QuestionCircleOutlined } from "@ant-design/icons"
  10. import '../../tencentAdPutIn/index.less'
  11. import HandleLog from "./handleLog"
  12. import moment from "moment"
  13. import RetriaModal from "./retriaModal"
  14. import ReplacePage from "./replacePage"
  15. import ReplaceGfPage from "./replaceGfPage"
  16. import { useLocalStorageState } from "ahooks"
  17. /** 审核结果 */
  18. export const AD_STATUS = {
  19. NORMAL: <Badge status="success" text="审核通过" />,
  20. PENDING: <Badge status="default" text="审核中" />,
  21. DENIED: <Badge status="error" text="有违规" />,
  22. PARTIALLY_NORMAL: <Badge status="warning" text="部分审核通过" />,
  23. }
  24. let isFist = true
  25. const Creative: React.FC<ADQV3.CreativeProps> = ({ queryForm, setQueryForm, userId }) => {
  26. /*********************************/
  27. const [searchParams, setSearchParams] = useLocalStorageState<{ isSaveSearchParams: boolean, queryForm?: any }>('SAVE_CREATIVE_SEARCH_FORM', { isSaveSearchParams: false });
  28. const [form] = Form.useForm();
  29. const [dynimicData, setDynamicData] = useState<any>({})
  30. const [dynimicVisible, setDynamicVisible] = useState<boolean>(false)
  31. const [selectedRows, setSelectedRows] = useState<any[]>([])
  32. const [failIdList, setFailIdList] = useState<{ adgroupId: number, code: number, message: string, messageCn: string }[]>([])
  33. const [failVisible, setFailVisible] = useState<boolean>(false)
  34. const [logVisible, setLogVisible] = useState<boolean>(false)
  35. const [handleType, setHandleType] = useState<number>(1)
  36. const [replaceVisible, setReplaceVisible] = useState<boolean>(false)
  37. const [replaceGfVisible, setReplaceGfVisible] = useState<boolean>(false)
  38. const getDynamicCreativeV3List = useAjax((params) => getDynamicCreativeV3ListApi(params), { formatResult: true })
  39. const delBatchCreative = useAjax((params) => delBatchCreativeApi(params))
  40. const updateBatchDynamicCreativesInfo = useAjax((params) => updateBatchDynamicCreativesInfoApi(params))
  41. const syncBatchCreative = useAjax((params) => syncBatchCreativeApi(params))
  42. /*********************************/
  43. useEffect(() => {
  44. if (queryForm?.adgroupId) {
  45. form.setFieldsValue({ adgroupId: queryForm.adgroupId })
  46. } else if (searchParams?.isSaveSearchParams && searchParams?.queryForm) {
  47. const { beginDate, endDate, ...params } = searchParams.queryForm
  48. if (beginDate && endDate) {
  49. params.date = [moment(beginDate), moment(endDate)]
  50. }
  51. form.setFieldsValue(params)
  52. }
  53. }, [queryForm?.adgroupId])
  54. // queryForm = queryForm.adgroupId ? queryForm : (searchParams?.isSaveSearchParams && searchParams?.queryForm && Object.keys(searchParams?.queryForm).length > 0) ? searchParams?.queryForm : queryForm
  55. useEffect(() => {
  56. const params: any = isFist && (searchParams?.isSaveSearchParams && searchParams?.queryForm && Object.keys(searchParams?.queryForm).length > 0) ? { ...searchParams.queryForm, userId } : { ...queryForm, userId }
  57. isFist = false
  58. if (params?.accountId) {
  59. params.accountId = params.accountId?.split(/[,,\s\n]+/)
  60. }
  61. if (params?.scIds) {
  62. params[params?.scType === 'IMAGE' ? 'imageId' : params?.scType === 'VIDEO' ? 'videoId' : 'text'] = params.scIds?.split(/[,,\s\n]+/)
  63. }
  64. getDynamicCreativeV3List.run(params)
  65. }, [userId, queryForm])
  66. const onFinish = (values: any) => {
  67. console.log(values)
  68. const { date, ...value } = values
  69. let newQueryForm = { ...queryForm, ...value }
  70. if (date?.length) {
  71. newQueryForm.beginDate = moment(date[0]).format('YYYY-MM-DD')
  72. newQueryForm.endDate = moment(date[1]).format('YYYY-MM-DD')
  73. } else {
  74. delete newQueryForm?.beginDate
  75. delete newQueryForm?.endDate
  76. }
  77. if (searchParams?.isSaveSearchParams) {
  78. setSearchParams({ isSaveSearchParams: true, queryForm: newQueryForm })
  79. }
  80. setQueryForm(newQueryForm)
  81. }
  82. const reviewStatusDetails = (value: any) => {
  83. setDynamicData(value)
  84. setDynamicVisible(true)
  85. }
  86. const dynamicHandle = (type: '删除' | '启动' | '暂停', data?: any) => {
  87. const accountAdgroupMaps = data ? [data.accountId + ',' + data.dynamicCreativeId] : [...new Set(selectedRows?.map(item => item.accountId + ',' + item.dynamicCreativeId))]
  88. let hide: any
  89. if (data) {
  90. hide = message.loading(`正在设置...`, 0, () => {
  91. message.success('设置成功');
  92. });
  93. }
  94. switch (type) {
  95. case '删除':
  96. delBatchCreative.run({ accountAdgroupMaps }).then(res => {
  97. if (res?.failIdList?.length === 0) {
  98. message.success(`修改操作完成!`)
  99. getDynamicCreativeV3List.refresh()
  100. setSelectedRows([])
  101. } else {
  102. setFailIdList(res?.list || [])
  103. setFailVisible(true)
  104. }
  105. })
  106. break
  107. case '启动':
  108. case '暂停':
  109. updateBatchDynamicCreativesInfo.run({ accountAdgroupMaps, suspend: type === '暂停' }).then(res => {
  110. if (res?.failIdList?.length === 0) {
  111. message.success(`修改操作完成!`)
  112. getDynamicCreativeV3List.refresh()
  113. setSelectedRows([])
  114. } else {
  115. setFailIdList(res?.list || [])
  116. setFailVisible(true)
  117. }
  118. if (hide) {
  119. hide()
  120. }
  121. })
  122. break
  123. }
  124. }
  125. const sync = () => {
  126. if (selectedRows?.length > 0) {
  127. const hide = message.loading(`正在同步...`, 0, () => {
  128. message.success('设置成功');
  129. });
  130. let accountAdgroupMaps = [...new Set(selectedRows?.map(item => item.accountId + ',' + item.dynamicCreativeId))]
  131. syncBatchCreative.run({ accountAdgroupMaps }).then(res => {
  132. res && getDynamicCreativeV3List.refresh()
  133. res ? message.success('同步成功!') : message.error('同步失败!')
  134. hide()
  135. }).catch(() => hide())
  136. } else {
  137. message.error('请勾选需要同步的创意')
  138. }
  139. }
  140. return <>
  141. <Form
  142. layout="inline"
  143. form={form}
  144. name="basignCreative"
  145. initialValues={{ ...queryForm, scType: 'IMAGE' }}
  146. onFinish={onFinish}
  147. style={{ marginBottom: 6 }}
  148. >
  149. <Row gutter={[10, 10]}>
  150. <Col><Form.Item name='accountId' style={{ marginRight: 0 }}>
  151. <Input placeholder="广告账号,多个逗号隔开" style={{ width: 180 }} allowClear />
  152. </Form.Item></Col>
  153. <Col><Form.Item name='scIds' style={{ marginRight: 0 }}>
  154. <Input
  155. placeholder="腾讯素材ID/文案,多个逗号隔开"
  156. style={{ width: 240 }}
  157. allowClear
  158. addonBefore={<Form.Item name='scType' noStyle><Select>
  159. <Select.Option value="IMAGE">图片</Select.Option>
  160. <Select.Option value="VIDEO">视频</Select.Option>
  161. <Select.Option value="TEXT">文案</Select.Option>
  162. </Select></Form.Item>}
  163. />
  164. </Form.Item></Col>
  165. <Col><Form.Item name='adgroupId' style={{ marginRight: 0 }}>
  166. <Input placeholder="广告ID" style={{ width: 120 }} allowClear />
  167. </Form.Item></Col>
  168. <Col><Form.Item name='creativeName' style={{ marginRight: 0 }}>
  169. <Input placeholder="创意名称" style={{ width: 120 }} allowClear />
  170. </Form.Item></Col>
  171. <Col><Form.Item name='creativeId' style={{ marginRight: 0 }}>
  172. <Input placeholder="创意ID" style={{ width: 120 }} allowClear />
  173. </Form.Item></Col>
  174. <Col><Form.Item name='configuredStatus' style={{ marginRight: 0 }}>
  175. <Select
  176. placeholder='启用禁用状态'
  177. style={{ minWidth: 120 }}
  178. showSearch
  179. allowClear
  180. filterOption={(input: any, option: any) =>
  181. (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
  182. }
  183. mode="multiple"
  184. >
  185. <Select.Option value={'AD_STATUS_NORMAL'}>有效</Select.Option>
  186. <Select.Option value={'AD_STATUS_SUSPEND'}>暂停</Select.Option>
  187. </Select>
  188. </Form.Item></Col>
  189. <Col><Form.Item name='deliveryMode' style={{ marginRight: 0 }}>
  190. <Select
  191. placeholder='投放模式'
  192. style={{ minWidth: 120 }}
  193. showSearch
  194. allowClear
  195. maxTagCount={1}
  196. filterOption={(input: any, option: any) =>
  197. (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
  198. }
  199. mode="multiple"
  200. >
  201. <Select.Option value={'DELIVERY_MODE_CUSTOMIZE'}>自定义创意</Select.Option>
  202. <Select.Option value={'DELIVERY_MODE_COMPONENT'}>组件化创意</Select.Option>
  203. </Select>
  204. </Form.Item></Col>
  205. <Col>
  206. <Form.Item name='date' style={{ marginRight: 0 }}>
  207. <DatePicker.RangePicker style={{ width: 260 }} placeholder={['创建开始日期', '创建结束日期']} />
  208. </Form.Item>
  209. </Col>
  210. <Col><Form.Item name='isDeleted' style={{ marginRight: 0 }}>
  211. <Select
  212. placeholder='是否删除?'
  213. style={{ minWidth: 100 }}
  214. showSearch
  215. allowClear
  216. filterOption={(input: any, option: any) =>
  217. (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
  218. }
  219. mode="multiple"
  220. >
  221. <Select.Option value={true}>是</Select.Option>
  222. <Select.Option value={false}>否</Select.Option>
  223. </Select>
  224. </Form.Item></Col>
  225. <Col><Form.Item style={{ marginRight: 0 }}>
  226. <Space>
  227. <Button type="primary" htmlType="submit">
  228. <Space>
  229. <span>搜索</span>
  230. <Checkbox
  231. checked={searchParams?.isSaveSearchParams}
  232. onChange={(e) => {
  233. setSearchParams({ isSaveSearchParams: e.target.checked })
  234. }}
  235. />
  236. <Tooltip title="勾选搜索长保存搜索条件">
  237. <QuestionCircleOutlined />
  238. </Tooltip>
  239. </Space>
  240. </Button>
  241. <Button onClick={() => {
  242. setQueryForm({ pageNum: 1, pageSize: queryForm.pageSize, userId })
  243. form.resetFields()
  244. }}>重置</Button>
  245. <Button disabled={selectedRows.length === 0} style={{ padding: 0 }} danger type="link" onClick={() => setSelectedRows([])}>清空已选({selectedRows.length})</Button>
  246. </Space>
  247. </Form.Item></Col>
  248. </Row>
  249. </Form>
  250. <TableData
  251. isCard={false}
  252. columns={() => tableConfig(reviewStatusDetails, (data, type) => dynamicHandle(type, data))}
  253. ajax={getDynamicCreativeV3List}
  254. fixed={{ left: 2, right: 4 }}
  255. dataSource={getDynamicCreativeV3List?.data?.data?.records}
  256. loading={getDynamicCreativeV3List?.loading || syncBatchCreative.loading || updateBatchDynamicCreativesInfo.loading}
  257. scroll={{ y: 560 }}
  258. syncAjax={sync}
  259. total={getDynamicCreativeV3List?.data?.data?.total}
  260. page={getDynamicCreativeV3List?.data?.data?.current}
  261. pageSize={getDynamicCreativeV3List?.data?.data?.size}
  262. myKey={'dynamicCreativeId'}
  263. gutter={[0, 10]}
  264. config={txDynamicConfig}
  265. configName="创意3.0"
  266. leftChild={<Space>
  267. <Select
  268. style={{ width: 120 }}
  269. onChange={(e) => {
  270. setHandleType(e)
  271. setSelectedRows([])
  272. }}
  273. value={handleType}
  274. dropdownMatchSelectWidth={false}
  275. options={[
  276. { label: '基本操作', value: 1 },
  277. { label: '复审操作', value: 2 },
  278. { label: '落地页替换', value: 3 },
  279. ]}
  280. />
  281. {handleType === 1 ? <>
  282. <Button type='primary' style={{ background: '#67c23a', borderColor: '#67c23a' }} loading={updateBatchDynamicCreativesInfo.loading} icon={<PlayCircleOutlined />} disabled={selectedRows.length === 0} onClick={() => dynamicHandle('启动')}>启动</Button>
  283. <Button type='primary' style={{ background: '#e6a23c', borderColor: '#e6a23c' }} loading={updateBatchDynamicCreativesInfo.loading} icon={<PauseCircleOutlined />} disabled={selectedRows.length === 0} onClick={() => dynamicHandle('暂停')}>暂停</Button>
  284. <Popconfirm
  285. title="确定删除?"
  286. onConfirm={() => dynamicHandle('删除')}
  287. disabled={selectedRows.length === 0}
  288. >
  289. <Button type='primary' danger icon={<DeleteOutlined />} loading={delBatchCreative.loading} disabled={selectedRows.length === 0}>删除</Button>
  290. </Popconfirm>
  291. </> : handleType === 2 ? <>
  292. <RetriaModal
  293. selectedRows={selectedRows}
  294. onChange={() => setSelectedRows([])}
  295. />
  296. </> : <Button
  297. type='primary'
  298. disabled={selectedRows.length === 0}
  299. onClick={() => {
  300. if (selectedRows?.[0]?.creativeComponents?.mainJumpInfo?.[0]?.value?.pageType === "WECHAT_CANVAS") { // 原生推广页
  301. setReplaceVisible(true)
  302. } else if (selectedRows?.[0]?.creativeComponents?.mainJumpInfo?.[0]?.value?.pageType === "OFFICIAL") { // 官方落地页
  303. setReplaceGfVisible(true)
  304. } else {
  305. message.error('暂不支持该类型落地页替换')
  306. }
  307. }}
  308. >修改落地页</Button>}
  309. <Button type='dashed' onClick={() => { setLogVisible(true) }}>操作记录</Button>
  310. </Space>}
  311. rowSelection={{
  312. selectedRowKeys: selectedRows.map(item => item.dynamicCreativeId.toString()),
  313. getCheckboxProps: (record: any) => {
  314. if (handleType === 3) {
  315. if (selectedRows.length) {
  316. const { deliveryMode, creativeTemplateId, creativeComponents: { mainJumpInfo } } = selectedRows[0]
  317. const pageType: string | undefined = mainJumpInfo?.[0]?.value?.pageType
  318. const pageLength: number | undefined = mainJumpInfo?.length
  319. return {
  320. disabled: record.isDeleted ||
  321. record.deliveryMode !== deliveryMode ||
  322. record.creativeTemplateId !== creativeTemplateId ||
  323. record?.creativeComponents?.mainJumpInfo?.[0]?.value?.pageType !== pageType ||
  324. pageLength !== record?.creativeComponents?.mainJumpInfo?.length ||
  325. record?.creativeComponents?.mainJumpInfo?.[0]?.value?.pageSpec?.wechatCanvasSpec?.overrideCanvasHeadOption !== mainJumpInfo?.[0]?.value?.pageSpec?.wechatCanvasSpec?.overrideCanvasHeadOption
  326. }
  327. } else {
  328. return { disabled: record.isDeleted }
  329. }
  330. }
  331. return { disabled: handleType === 2 ? record.isDeleted || record.reviewStatus !== 'AD_STATUS_DENIED' : record.isDeleted }
  332. },
  333. onSelect: (record: { dynamicCreativeId: number }, selected: boolean) => {
  334. if (selected) {
  335. selectedRows.push({ ...record })
  336. setSelectedRows([...selectedRows])
  337. } else {
  338. const newSelectAccData = selectedRows.filter((item: { dynamicCreativeId: number }) => item.dynamicCreativeId !== record.dynamicCreativeId)
  339. setSelectedRows([...newSelectAccData])
  340. }
  341. },
  342. onSelectAll: (selected: boolean, selectedRowss: { dynamicCreativeId: number }[], changeRows: { dynamicCreativeId: number, isDeleted: boolean }[]) => {
  343. if (selected) {
  344. const newSelectAccData = [...selectedRows]
  345. const firstData = selectedRows?.[0] || changeRows?.find((item: { isDeleted: boolean }) => !item.isDeleted)
  346. changeRows.forEach((item: any) => {
  347. const index = newSelectAccData.findIndex((ite: { dynamicCreativeId: number }) => ite.dynamicCreativeId === item.dynamicCreativeId)
  348. if (index === -1) {
  349. if (handleType === 3) {
  350. const isValid = !item.isDeleted &&
  351. item.deliveryMode === firstData.deliveryMode &&
  352. item.creativeTemplateId === firstData.creativeTemplateId &&
  353. item?.creativeComponents?.mainJumpInfo?.[0]?.value?.pageType === firstData?.creativeComponents?.mainJumpInfo?.[0]?.value?.pageType &&
  354. firstData?.creativeComponents?.mainJumpInfo?.length === item?.creativeComponents?.mainJumpInfo?.length &&
  355. item?.creativeComponents?.mainJumpInfo?.[0]?.value?.pageSpec?.wechatCanvasSpec?.overrideCanvasHeadOption === firstData?.creativeComponents?.mainJumpInfo?.[0]?.value?.pageSpec?.wechatCanvasSpec?.overrideCanvasHeadOption;
  356. if (isValid) {
  357. newSelectAccData.push({ ...item });
  358. }
  359. } else {
  360. newSelectAccData.push({ ...item });
  361. }
  362. }
  363. })
  364. setSelectedRows([...newSelectAccData])
  365. } else {
  366. const newSelectAccData = selectedRows.filter((item: { dynamicCreativeId: number }) => {
  367. const index = changeRows.findIndex((ite: { dynamicCreativeId: number }) => ite.dynamicCreativeId === item.dynamicCreativeId)
  368. if (index !== -1) {
  369. return false
  370. } else {
  371. return true
  372. }
  373. })
  374. setSelectedRows([...newSelectAccData])
  375. }
  376. }
  377. }}
  378. onChange={(props: any) => {
  379. const { pagination } = props
  380. const { current, pageSize } = pagination
  381. setQueryForm({ ...queryForm, pageNum: current, pageSize })
  382. }}
  383. />
  384. {dynimicVisible && <ReviewDetails
  385. visible={dynimicVisible}
  386. dynamic={dynimicData}
  387. onClose={() => {
  388. setDynamicData({})
  389. setDynamicVisible(false)
  390. }}
  391. />}
  392. {failVisible && <Modal
  393. title={<strong>报错信息</strong>}
  394. open={failVisible}
  395. className='modalResetCss'
  396. width={650}
  397. onCancel={() => { setFailVisible(false); setFailIdList([]) }}
  398. footer={null}
  399. >
  400. <Table
  401. size="small"
  402. bordered
  403. rowKey={'creativeId'}
  404. columns={[{
  405. title: '创意ID',
  406. dataIndex: 'creativeId',
  407. key: 'creativeId',
  408. width: 110,
  409. render: (value) => <span style={{ fontSize: 12 }}>{value}</span>,
  410. }, {
  411. title: 'code',
  412. dataIndex: 'code',
  413. key: 'code',
  414. width: 70,
  415. align: 'center',
  416. render: (value) => <span style={{ fontSize: 12 }}>{value}</span>,
  417. }, {
  418. title: '错误信息',
  419. dataIndex: 'messageCn',
  420. key: 'messageCn',
  421. render: (value) => <span style={{ fontSize: 12 }}>{value}</span>,
  422. }]}
  423. dataSource={failIdList}
  424. />
  425. </Modal>}
  426. {logVisible && <HandleLog
  427. visible={logVisible}
  428. onClose={() => {
  429. setLogVisible(false)
  430. }}
  431. userId={userId}
  432. />}
  433. {/* 修改原生落地页 */}
  434. {replaceVisible && <ReplacePage
  435. selectedRows={selectedRows}
  436. visible={replaceVisible}
  437. onClose={() => {
  438. setReplaceVisible(false)
  439. }}
  440. onChange={() => {
  441. setReplaceVisible(false)
  442. setSelectedRows([])
  443. }}
  444. />}
  445. {/* 修改官方落地页 */}
  446. {replaceGfVisible && <ReplaceGfPage
  447. selectedRows={selectedRows}
  448. visible={replaceGfVisible}
  449. onClose={() => {
  450. setReplaceGfVisible(false)
  451. }}
  452. onChange={() => {
  453. setReplaceGfVisible(false)
  454. setSelectedRows([])
  455. }}
  456. />}
  457. </>
  458. }
  459. export default React.memo(Creative)