index.tsx 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. import { useAjax } from "@/Hook/useAjax";
  2. import { DeleteOutlined, DownOutlined, PauseCircleOutlined, PlayCircleOutlined, PlusOutlined, QuestionCircleOutlined } from "@ant-design/icons";
  3. import { Button, Checkbox, Col, DatePicker, Dropdown, Input, Menu, Modal, Row, Select, Space, Tooltip, Typography, message } from "antd"
  4. import React, { useCallback, useEffect, useState } from "react"
  5. import { ADGROUP_STATUS } from "../const";
  6. import { getAdqV3AdListApi, modifyStatusBatchApi, syncBatchApi } from "@/services/launchAdq/adqv3";
  7. import tableConfig from "./tableConfig";
  8. import { txAdConfig } from "../config";
  9. import UpdateAd from "./updateAd";
  10. import TableData from "@/pages/launchSystemNew/components/TableData";
  11. import AddDynamic from "../../tencentAdPutIn/create/addDynamic";
  12. import { arraysHaveSameValues } from "@/utils/utils";
  13. import { MARKETING_CARRIER_TYPE_ENUM, MARKETING_GOAL_ENUM, MARKETING_TARGET_TYPE_ENUM, SITE_SET_ENUM } from "../../tencentAdPutIn/const";
  14. import Log from "../components/log";
  15. import '../../tencentAdPutIn/index.less'
  16. import UserTactics from "../../tencentAdPutIn/create/TacticsS/userTactics";
  17. import UpdateAd3 from "./updateAd3";
  18. const { Text } = Typography;
  19. const Ad: React.FC<ADQV3.AdProps> = ({ userId, creativeHandle }) => {
  20. /*****************************************/
  21. const [queryFrom, set_queryFrom] = useState<ADQV3.GetAdListProps>({ pageNum: 1, pageSize: 20, useType: 1 })
  22. const [isClearSelect, setIsClearSelect] = useState(true)
  23. const [selectedRows, setSelectedRows] = useState<any[]>([])
  24. const [tactics, setTactics] = useState<any>()
  25. const [update, setUpdate] = useState<{ visible: boolean }>({ visible: false })
  26. const [addDynamicVisible, setAddDynamicVisible] = useState<boolean>(false)
  27. const [handleType, setHandleType] = useState<number>(1)
  28. const [czjlShow, setCzjlShow] = useState(false)
  29. const [updateData, setUpdateDate] = useState<{ visible: boolean, type: '修改出价' | '修改名称' | '修改日限额' | '修改投放时间' | '删除' | '深度优化ROI' | '修改投放首日开始时间' }>({ visible: false, type: '修改出价' })
  30. const syncBatch = useAjax((params) => syncBatchApi(params))
  31. const modifyStatusBatch = useAjax((params) => modifyStatusBatchApi(params))
  32. const getAdqV3AdList = useAjax((params) => getAdqV3AdListApi(params), { formatResult: true })
  33. /*****************************************/
  34. useEffect(() => {
  35. getList({ pageNum: 1, pageSize: 20, useType: 1 })
  36. }, [userId])
  37. // 获取列表
  38. const getList = useCallback((params: ADQV3.GetAdListProps) => {
  39. getAdqV3AdList.run({ ...params, userId })
  40. }, [userId, getAdqV3AdList])
  41. // 同步
  42. const sync = useCallback(() => {
  43. if (selectedRows?.length > 0) {
  44. let accountAdgroupMaps = [...new Set(selectedRows?.map(item => item.accountId + ',' + item.adgroupId))]
  45. syncBatch.run({ accountAdgroupMaps }).then(res => {
  46. res && getAdqV3AdList.refresh()
  47. res ? message.success('同步成功!') : message.error('同步失败!')
  48. })
  49. } else {
  50. message.error('请勾选需要同步的广告')
  51. }
  52. }, [getAdqV3AdList, selectedRows])
  53. // 批量启停
  54. const adStatus = (type: boolean) => {
  55. let newSelectedRows = []
  56. if (type) {
  57. newSelectedRows = selectedRows.filter((item: { configuredStatus: string, adgroupId: number }) => item.configuredStatus === 'AD_STATUS_SUSPEND')
  58. } else {
  59. newSelectedRows = selectedRows.filter((item: { configuredStatus: string, adgroupId: number }) => item.configuredStatus === 'AD_STATUS_NORMAL')
  60. }
  61. if (newSelectedRows.length === 0) {
  62. message.warn(`所有广告都是${type ? '启动' : '暂停'}状态,无需${type ? '启动' : '暂停'}操作`)
  63. return
  64. }
  65. let accountAdgroupMaps = [...new Set(newSelectedRows?.map(item => item.accountId + ',' + item.adgroupId))]
  66. modifyStatusBatch.run({ accountAdgroupMaps, suspend: !type }).then(res => {
  67. message.success(`${type ? '启动' : '暂停'}成功`)
  68. getAdqV3AdList.refresh()
  69. setSelectedRows([])
  70. })
  71. }
  72. // 添加创意
  73. const addDynamic = () => {
  74. setAddDynamicVisible(true)
  75. }
  76. return <div>
  77. <Row gutter={[6, 6]} align='middle' style={{ marginBottom: 15 }}>
  78. <Col>
  79. <Select
  80. placeholder='应用类型'
  81. style={{ width: 90 }}
  82. showSearch
  83. filterOption={(input: any, option: any) =>
  84. (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
  85. }
  86. value={queryFrom.useType}
  87. onChange={(value: any) => {
  88. set_queryFrom({ ...queryFrom, useType: value })
  89. }}
  90. >
  91. <Select.Option value={1}>小说</Select.Option>
  92. <Select.Option value={2}>游戏</Select.Option>
  93. </Select>
  94. </Col>
  95. <Col>
  96. <Input
  97. placeholder='广告账号(多个,分割)'
  98. allowClear
  99. style={{ width: 160 }}
  100. onChange={(e) => {
  101. let value = e.target.value
  102. let arr: any = []
  103. if (value) {
  104. value = value.replace(/[,,\s]/g, ',')
  105. arr = value.split(',').filter((a: any) => a)
  106. }
  107. set_queryFrom({ ...queryFrom, accountIdList: arr })
  108. }}
  109. />
  110. </Col>
  111. <Col>
  112. <Input
  113. placeholder='广告ID(多个,分割)'
  114. allowClear
  115. style={{ width: 150 }}
  116. onChange={(e) => {
  117. let value = e.target.value
  118. let arr: any = []
  119. if (value) {
  120. value = value.replace(/[,,\s]/g, ',')
  121. arr = value.split(',').filter((a: any) => a)
  122. }
  123. set_queryFrom({ ...queryFrom, adgroupIdList: arr })
  124. }}
  125. />
  126. </Col>
  127. <Col>
  128. <Select
  129. placeholder='广告状态'
  130. mode="multiple"
  131. style={{ minWidth: 120 }}
  132. showSearch
  133. filterOption={(input: any, option: any) =>
  134. (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
  135. }
  136. allowClear
  137. onChange={(value: any) => {
  138. set_queryFrom({ ...queryFrom, systemStatusList: value })
  139. }}
  140. >
  141. {Object.keys(ADGROUP_STATUS).map(key => {
  142. return <Select.Option value={key} key={key}>{ADGROUP_STATUS[key as keyof typeof ADGROUP_STATUS]}</Select.Option>
  143. })}
  144. </Select>
  145. </Col>
  146. <Col>
  147. <Input
  148. placeholder='广告名称'
  149. allowClear
  150. style={{ width: 120 }}
  151. onChange={(e) => {
  152. let value = e.target.value
  153. set_queryFrom({ ...queryFrom, adgroupName: value })
  154. }}
  155. />
  156. </Col>
  157. <Col>
  158. <Input
  159. placeholder='腾讯备注'
  160. allowClear
  161. style={{ width: 120 }}
  162. onChange={(e) => {
  163. let value = e.target.value
  164. let arr: any = []
  165. if (value) {
  166. value = value.replace(/[,,\s]/g, ',')
  167. arr = value.split(',').filter((a: any) => a)
  168. }
  169. set_queryFrom({ ...queryFrom, accountMemo: arr })
  170. }}
  171. />
  172. </Col>
  173. <Col>
  174. <Input
  175. placeholder='本地备注'
  176. allowClear
  177. style={{ width: 120 }}
  178. onChange={(e) => {
  179. let value = e.target.value
  180. let arr: any = []
  181. if (value) {
  182. value = value.replace(/[,,\s]/g, ',')
  183. arr = value.split(',').filter((a: any) => a)
  184. }
  185. set_queryFrom({ ...queryFrom, accountRemark: arr })
  186. }}
  187. />
  188. </Col>
  189. <Col>
  190. <DatePicker.RangePicker
  191. placeholder={['创建开始日期', '创建结束日期']}
  192. onChange={(e, options) => {
  193. set_queryFrom({ ...queryFrom, beginDate: options[0], endDate: options[1] })
  194. }}
  195. />
  196. </Col>
  197. <Col>
  198. <Space>
  199. <Button
  200. type="primary"
  201. onClick={() => {
  202. if (isClearSelect) {
  203. setSelectedRows([])
  204. }
  205. getList({ ...queryFrom, pageNum: 1 })
  206. }}
  207. >
  208. <Space>
  209. <span>搜索</span>
  210. <Checkbox className='clearCheckbox' onClick={(e) => e.stopPropagation()} checked={isClearSelect} onChange={(e) => setIsClearSelect(e.target.checked)} />
  211. <Tooltip title="勾选搜索清空已选">
  212. <QuestionCircleOutlined />
  213. </Tooltip>
  214. </Space>
  215. </Button>
  216. {selectedRows?.length > 0 && <Button type='link' style={{ padding: 0, color: 'red' }} onClick={() => {
  217. setSelectedRows([])
  218. }}>清空已选({selectedRows?.length})</Button>}
  219. </Space>
  220. </Col>
  221. </Row>
  222. <TableData
  223. isCard={false}
  224. columns={() => tableConfig(() => getAdqV3AdList.refresh(), creativeHandle)}
  225. ajax={getAdqV3AdList}
  226. syncAjax={sync}
  227. fixed={{ left: 2, right: 5 }}
  228. dataSource={getAdqV3AdList?.data?.data?.records}
  229. loading={getAdqV3AdList?.loading || syncBatch?.loading}
  230. scroll={{ y: 560 }}
  231. total={getAdqV3AdList?.data?.data?.total}
  232. page={getAdqV3AdList?.data?.data?.current}
  233. pageSize={getAdqV3AdList?.data?.data?.size}
  234. myKey={'adgroupId'}
  235. gutter={[0, 10]}
  236. config={txAdConfig}
  237. configName="腾讯广告3.0"
  238. leftChild={<Space direction='vertical'>
  239. <Row gutter={[10, 10]} align='middle'>
  240. <Col><Select
  241. style={{ width: 120 }}
  242. onChange={(e) => {
  243. setHandleType(e)
  244. setSelectedRows([])
  245. }}
  246. value={handleType}
  247. dropdownMatchSelectWidth={false}
  248. options={[{ label: '广告操作', value: 1 }, { label: '创意操作', value: 2 }, { label: '修改深度优化期望ROI', value: 3 }]}
  249. /></Col>
  250. <Col>
  251. <Button type='dashed' onClick={() => { setCzjlShow(true) }}>操作记录</Button>
  252. </Col>
  253. {handleType === 1 ? <>
  254. <Col><Button type='primary' style={{ background: '#67c23a', borderColor: '#67c23a' }} loading={modifyStatusBatch.loading} icon={<PlayCircleOutlined />} disabled={selectedRows.length === 0} onClick={() => adStatus(true)}>启动</Button></Col>
  255. <Col><Button type='primary' style={{ background: '#e6a23c', borderColor: '#e6a23c' }} loading={modifyStatusBatch.loading} icon={<PauseCircleOutlined />} disabled={selectedRows.length === 0} onClick={() => adStatus(false)}>暂停</Button></Col>
  256. <Col><Button type='primary' danger icon={<DeleteOutlined />} disabled={selectedRows.length === 0} onClick={() => {
  257. setUpdateDate({ visible: true, type: '删除' })
  258. }}>删除</Button></Col>
  259. <Col><Dropdown
  260. overlay={<Menu>
  261. <Menu.Item disabled={selectedRows.length === 0} onClick={() => {
  262. setUpdateDate({ visible: true, type: '修改出价' })
  263. }}><span style={{ display: 'inline-block', width: 120 }}>修改出价</span></Menu.Item>
  264. <Menu.Item disabled={selectedRows.length === 0} onClick={() => {
  265. setUpdateDate({ visible: true, type: '修改名称' })
  266. }}>修改名称</Menu.Item>
  267. <Menu.Item disabled={selectedRows.length === 0} onClick={() => {
  268. setUpdateDate({ visible: true, type: '修改日限额' })
  269. }}>修改日限额</Menu.Item>
  270. <Menu.Item disabled={selectedRows.length === 0} onClick={() => {
  271. setUpdateDate({ visible: true, type: '修改投放时间' })
  272. }}>修改投放日期</Menu.Item>
  273. <Menu.Item disabled={selectedRows.length === 0} onClick={() => {
  274. setUpdateDate({ visible: true, type: '修改投放首日开始时间' })
  275. }}>修改投放首日开始时间</Menu.Item>
  276. </Menu>}
  277. placement="bottomLeft"
  278. arrow
  279. >
  280. <Button>
  281. <Space>
  282. 修改广告
  283. <DownOutlined />
  284. </Space>
  285. </Button>
  286. </Dropdown></Col>
  287. </> : handleType === 2 ? <>
  288. <Col><UserTactics
  289. type="updateAd"
  290. onChange={(value) => {
  291. setTactics(value)
  292. addDynamic()
  293. }}
  294. userId={userId}
  295. /></Col>
  296. <Col><Button type='primary' icon={<PlusOutlined />} disabled={selectedRows.length === 0} onClick={addDynamic}>添加创意</Button></Col>
  297. <Col>
  298. <Space>
  299. <Tooltip title="选择的广告必须与已选广告的营销目的、营销载体、推广内容、广告版位、微信公众号与小程序定投、版位选择一致">
  300. <QuestionCircleOutlined style={{ color: 'red' }} />
  301. </Tooltip>
  302. {selectedRows?.length > 0 && <div style={{ maxWidth: '380px' }}>
  303. <Text type="danger" ellipsis={{ tooltip: true }} strong style={{ fontSize: 12 }}>
  304. {`当前广告选择:
  305. 营销目的:${MARKETING_GOAL_ENUM[selectedRows?.[0]?.marketingGoal as keyof typeof MARKETING_GOAL_ENUM]},
  306. 推广产品类型:${MARKETING_TARGET_TYPE_ENUM[selectedRows?.[0]?.marketingTargetType as keyof typeof MARKETING_TARGET_TYPE_ENUM]},
  307. 营销载体类型:${MARKETING_CARRIER_TYPE_ENUM[selectedRows?.[0]?.marketingCarrierType as keyof typeof MARKETING_CARRIER_TYPE_ENUM]},
  308. 版位选择:${selectedRows?.[0]?.automaticSiteEnabled ? '自动版位' : '选择特定版位'},
  309. ${!selectedRows?.[0]?.automaticSiteEnabled && `广告版位:${selectedRows?.[0]?.siteSet.map((item: string | number) => SITE_SET_ENUM[item as keyof typeof SITE_SET_ENUM]).toString()}`}
  310. `}
  311. </Text>
  312. </div>}
  313. </Space>
  314. </Col>
  315. </> : handleType === 3 ? <>
  316. <Col><Button type='primary' disabled={selectedRows.length === 0} onClick={() => {
  317. setUpdateDate({ visible: true, type: '深度优化ROI' })
  318. }}>修改深度优化期望ROI</Button></Col>
  319. </> : null}
  320. </Row>
  321. </Space>}
  322. rowSelection={{
  323. selectedRowKeys: selectedRows.map(item => item.adgroupId.toString()),
  324. // hideSelectAll: handleType === 3,
  325. getCheckboxProps: (record: any) => {
  326. if (handleType === 2 && selectedRows?.length > 0) {
  327. const { siteSet, marketingCarrierType, marketingGoal, marketingTargetType, sceneSpec, automaticSiteEnabled } = selectedRows[0]
  328. return {
  329. disabled: record.isDeleted || !(
  330. record?.marketingGoal === marketingGoal && // 营销内容
  331. record?.marketingCarrierType === marketingCarrierType && // 营销载体
  332. record?.marketingTargetType === marketingTargetType && // 推广产品
  333. record?.automaticSiteEnabled === automaticSiteEnabled && // 自动版位
  334. arraysHaveSameValues(siteSet || [], record?.siteSet || []) && // 版位选择
  335. arraysHaveSameValues(record?.sceneSpec?.wechatPosition || [], sceneSpec?.wechatPosition || []) // 微信公众号与小程序定投
  336. )
  337. }
  338. } else {
  339. return {
  340. disabled: handleType === 3 ? record.isDeleted || !(record?.deepConversionSpec?.deepConversionWorthSpec?.goal === 'GOAL_1DAY_PURCHASE_ROAS')
  341. : record.isDeleted
  342. }
  343. }
  344. },
  345. onSelect: (record: { adgroupId: number, mpName: string }, selected: boolean) => {
  346. if (selected) {
  347. selectedRows.push({ ...record })
  348. setSelectedRows([...selectedRows])
  349. } else {
  350. let newSelectAccData = selectedRows.filter((item: { adgroupId: number }) => item.adgroupId !== record.adgroupId)
  351. setSelectedRows([...newSelectAccData])
  352. }
  353. },
  354. onSelectAll: (selected: boolean, selectedRowss: { adgroupId: number }[], changeRows: { adgroupId: number }[]) => {
  355. if (selected) {
  356. let newSelectAccData = [...selectedRows]
  357. let firstRow = newSelectAccData?.[0] || changeRows?.[0] || {}
  358. changeRows.forEach((item: { adgroupId: number }) => {
  359. let index = newSelectAccData.findIndex((ite: { adgroupId: number }) => ite.adgroupId === item.adgroupId)
  360. if (index === -1) {
  361. let data: any = { ...item }
  362. if (handleType === 2) {
  363. const { siteSet, marketingCarrierType, marketingGoal, marketingTargetType, sceneSpec, automaticSiteEnabled } = firstRow
  364. if (
  365. data?.marketingGoal === marketingGoal && // 营销内容
  366. data?.marketingCarrierType === marketingCarrierType && // 营销载体
  367. data?.marketingTargetType === marketingTargetType && // 推广产品
  368. data?.automaticSiteEnabled === automaticSiteEnabled && // 自动版位
  369. arraysHaveSameValues(siteSet || [], data?.siteSet || []) && // 版位选择
  370. arraysHaveSameValues(data?.sceneSpec?.wechatPosition || [], sceneSpec?.wechatPosition || []) // 微信公众号与小程序定投
  371. ) {
  372. newSelectAccData.push(data)
  373. }
  374. } else {
  375. newSelectAccData.push(data)
  376. }
  377. }
  378. })
  379. setSelectedRows([...newSelectAccData])
  380. } else {
  381. let newSelectAccData = selectedRows.filter((item: { adgroupId: number }) => {
  382. let index = changeRows.findIndex((ite: { adgroupId: number }) => ite.adgroupId === item.adgroupId)
  383. if (index !== -1) {
  384. return false
  385. } else {
  386. return true
  387. }
  388. })
  389. setSelectedRows([...newSelectAccData])
  390. }
  391. }
  392. }}
  393. onChange={(props: any) => {
  394. let { pagination, sortData } = props
  395. let { current, pageSize } = pagination
  396. let newQueryForm = JSON.parse(JSON.stringify(queryFrom))
  397. if (sortData && sortData?.order) {
  398. newQueryForm['isAsc'] = sortData?.order === 'ascend'
  399. newQueryForm['orderByColumn'] = [sortData?.field]
  400. } else {
  401. delete newQueryForm['isAsc']
  402. delete newQueryForm['orderByColumn']
  403. }
  404. newQueryForm.pageNum = current
  405. newQueryForm.pageSize = pageSize
  406. set_queryFrom(newQueryForm)
  407. getList(newQueryForm)
  408. }}
  409. />
  410. {/* 修改广告 */}
  411. {update.visible && <UpdateAd
  412. {...update}
  413. selectedRows={selectedRows}
  414. onChange={() => {
  415. setUpdate({ visible: false })
  416. getAdqV3AdList.refresh()
  417. setSelectedRows([])
  418. }}
  419. onClose={() => { setUpdate({ visible: false }) }}
  420. />}
  421. {/* 新增创意 */}
  422. {addDynamicVisible && <AddDynamic
  423. adData={selectedRows}
  424. visible={addDynamicVisible}
  425. onClose={() => {
  426. setAddDynamicVisible(false)
  427. }}
  428. tactics={tactics}
  429. onChange={() => {
  430. setAddDynamicVisible(false)
  431. getAdqV3AdList.refresh()
  432. setSelectedRows([])
  433. setTactics(undefined)
  434. }}
  435. />}
  436. {czjlShow && <Modal
  437. visible={czjlShow}
  438. onCancel={() => { setCzjlShow(false) }}
  439. onOk={() => { setCzjlShow(false) }}
  440. width={1200}
  441. footer={null}
  442. title={<strong>广告操作记录</strong>}
  443. className="modalResetCss"
  444. >
  445. <Log userId={userId} />
  446. </Modal>}
  447. {/* 修改广告 */}
  448. {updateData.visible && <UpdateAd3
  449. {...updateData}
  450. updateData={selectedRows}
  451. onClose={() => {
  452. setUpdateDate({ visible: false, type: '修改出价' })
  453. }}
  454. onChange={() => {
  455. setUpdateDate({ visible: false, type: '修改出价' })
  456. getAdqV3AdList.refresh()
  457. if (updateData.type === '删除') {
  458. setSelectedRows([])
  459. }
  460. }}
  461. />}
  462. </div>
  463. }
  464. export default React.memo(Ad)