index.tsx 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. import Tables from "@/components/Tables"
  2. import { useAjax } from "@/Hook/useAjax"
  3. import { CreateAdProps } from "@/services/launchAdq/createAd"
  4. import { BidModeEnum, BidStrategyEnum, OptimizationGoalEnum, PromotedObjectType, SiteSetEnum } from "@/services/launchAdq/enum"
  5. import { getTagsList } from "@/services/launchAdq/global"
  6. import { getSysAdgroupsInfo } from "@/services/launchAdq/localAd"
  7. import { getsysTargetingInfo } from "@/services/launchAdq/targeting"
  8. import { CloseOutlined, SearchOutlined } from "@ant-design/icons"
  9. import { Button, Card, Col, Empty, Row, Select, Space, Spin, Tooltip, Image, message } from "antd"
  10. import React, { useEffect, useState } from "react"
  11. import { useModel } from "umi"
  12. import AdModal from "../../components/adModal"
  13. import DataSourceModal from "../../components/dataSourceModal"
  14. import GoodsModal from "../../components/goodsModal"
  15. import IdModal from "../../components/idModal"
  16. import LookLanding from "../../components/lookLanding"
  17. import SelectCloud from "../../components/selectCloud"
  18. import TargetingModal from "../../components/targetingModal"
  19. import TargetingTooltip from "../../components/targetingTooltip"
  20. import { WxAutoButton } from "../../req"
  21. import style from './index.less'
  22. import Selector from "./selector"
  23. import SubmitModal from "./submitModal"
  24. import columns from "./tableConfig"
  25. const CreateAd: React.FC = () => {
  26. /*************************/
  27. const { getAdAccount } = useModel('useLaunchAdq.useAdAuthorize')
  28. const [queryForm, setQueryForm] = useState<Partial<CreateAdProps>>({
  29. campaignName: '', // 计划名称
  30. campaignType: 'CAMPAIGN_TYPE_NORMAL', // 计划类型 CAMPAIGN_TYPE_NORMAL CAMPAIGN_TYPE_SEARCH
  31. promotedObjectType: 'PROMOTED_OBJECT_TYPE_WECHAT_OFFICIAL_ACCOUNT', // 推广目标类型
  32. speedMode: 'SPEED_MODE_STANDARD', // 投放速度模式
  33. sysAdgroupsId: undefined, // 广告组内容
  34. sysTargetingId: undefined, // 定向包 id
  35. adName: undefined, // 广告名称
  36. configuredStatus: 'AD_STATUS_SUSPEND', // 广告状态
  37. sysAdcreativeId: undefined, // 创意ID
  38. sysPageId: undefined, // 落地页Id
  39. })
  40. const [accountCreateLogs, setAccountCreateLogs] = useState<{ adAccountId: number, id: number, userActionSetsList?: number, productList?: any, conversionList?: any }[]>([]) // 账户
  41. const [adVisible, setAdVisible] = useState<boolean>(false) // 选择广告弹窗控制
  42. const [dxVisible, setDxVisible] = useState<boolean>(false) // 选择定向弹窗控制
  43. const [goodsVisible, setGoodsVisible] = useState<boolean>(false) // 选择商品弹窗控制
  44. const [sourceVisible, setSourceVisible] = useState<boolean>(false) // 选择数据源弹窗控制
  45. const [idVisible, setIdVisible] = useState<boolean>(false) // 选择转化ID弹窗控制
  46. const [selectImgVisible, setSelectImgVisible] = useState<boolean>(false) // 选择转化ID弹窗控制
  47. const [lookVisible, setLookVisible] = useState<boolean>(false) // 选择转化ID弹窗控制
  48. const [subVisible, setSubVisible] = useState<boolean>(false) // 选择设置名称弹窗控制
  49. const [wxButtonList, setWxButtonList] = useState<WxAutoButton[]>([])
  50. const [tableData, setTableData] = useState<any[]>([]) // 预览表格
  51. const [tableSelect, setTableSelect] = useState<any[]>([])
  52. const [geoLocationList, setGeoLocationList] = useState<any>({}) // 所有地域列表
  53. const [modelList, setModelList] = useState<any>({}) // 所有品牌手机
  54. const { init, get } = useModel('useLaunchAdq.useBdMediaPup')
  55. const tagsList_REGION = useAjax((params) => getTagsList(params))
  56. const tagsList_MODEL = useAjax((params) => getTagsList(params))
  57. const getSysAdgroups = useAjax((params) => getSysAdgroupsInfo(params))
  58. const getsysTargeting = useAjax((params) => getsysTargetingInfo(params))
  59. /*************************/
  60. // 设置地域
  61. useEffect(() => {
  62. tagsList_REGION.run({ type: 'REGION' }).then(res => {
  63. if (res) {
  64. setGeoLocationList(() => (res as any[])?.reduce((prev: any, cur: { id: number }) => {
  65. prev[cur.id] = cur
  66. return prev
  67. }, {}))
  68. }
  69. })
  70. tagsList_MODEL.run({ type: 'DEVICE_BRAND_MODEL' }).then(res => {
  71. if (res) {
  72. setModelList(() => (res as any[])?.reduce((prev: any, cur: { id: number }) => {
  73. prev[cur.id] = cur
  74. return prev
  75. }, {}))
  76. }
  77. })
  78. }, [])
  79. // 获取账户列表
  80. useEffect(() => {
  81. getAdAccount.run()
  82. init({ mediaType: 'PAGE' })
  83. }, [])
  84. /** 获取广告详情 */
  85. useEffect(() => {
  86. if (queryForm?.sysAdgroupsId) {
  87. getSysAdgroups.run(queryForm?.sysAdgroupsId)
  88. }
  89. }, [queryForm?.sysAdgroupsId])
  90. /** 获取定向详情 */
  91. useEffect(() => {
  92. if (queryForm?.sysTargetingId) {
  93. getsysTargeting.run(queryForm?.sysTargetingId)
  94. }
  95. }, [queryForm?.sysTargetingId])
  96. /** 获取落地页详情 */
  97. useEffect(() => {
  98. if (queryForm?.sysPageId) {
  99. get.run({ mediaType: 'PAGE', sysMediaId: queryForm?.sysPageId }).then(res => {
  100. let data = res
  101. let pageElementsSpecList = data?.pageSpecsList[0]?.pageElementsSpecList
  102. setWxButtonList(() => (pageElementsSpecList as any[])?.filter((item: { elementType: string }) => item?.elementType === 'ENTERPRISE_WX'))
  103. })
  104. }
  105. }, [queryForm?.sysPageId])
  106. /** 删除商品内容 */
  107. const goodsDel = (index: number) => {
  108. let newArr = JSON.parse(JSON.stringify(accountCreateLogs))
  109. delete newArr[index].productList
  110. setAccountCreateLogs(newArr)
  111. }
  112. /** 删除数据源 */
  113. const sourceDel = (index: number, num: number) => {
  114. console.log(index, num);
  115. let newArr = JSON.parse(JSON.stringify(accountCreateLogs))
  116. newArr[index].userActionSetsList?.splice(num, 1)
  117. setAccountCreateLogs(newArr)
  118. }
  119. /** 设置落地页 */
  120. const setPage = (e: any) => {
  121. setQueryForm({ ...queryForm, sysPageId: e[0]?.id || undefined })
  122. setSelectImgVisible(false)
  123. }
  124. /** 预览 */
  125. const preview = () => {
  126. if (accountCreateLogs?.length === 0) {
  127. message.error('请选择媒体账户')
  128. return
  129. }
  130. if (!queryForm.promotedObjectType) {
  131. message.error('请选择推广目标')
  132. return
  133. }
  134. if (!queryForm.sysAdgroupsId) {
  135. message.error('请先设置广告基本信息')
  136. return
  137. }
  138. if (!queryForm.sysTargetingId) {
  139. message.error('请选择定向')
  140. return
  141. }
  142. // if (!queryForm.sysAdcreativeId) {
  143. // message.error('请设置创意的基本信息')
  144. // return
  145. // }
  146. if (!queryForm.sysPageId) {
  147. message.error('请选择落地页')
  148. return
  149. }
  150. let data = accountCreateLogs.map((item: any, index) => {
  151. return { ...item, id: index + 1, ...queryForm, sysAdGroupData: getSysAdgroups?.data, targetingData: getsysTargeting?.data, pageData: get?.data }
  152. })
  153. console.log('data--->', data)
  154. setTableData(data)
  155. }
  156. const submit = (data: { adName: string, campaignName: string }) => {
  157. console.log(111111, tableSelect);
  158. console.log(222222, data)
  159. }
  160. return <Space direction="vertical" style={{ width: '100%' }}>
  161. <Card title={<div className={style.cardTitle}>配置区</div>} className={style.createAd} hoverable>
  162. <Space>
  163. <Selector label="媒体账户">
  164. <Select
  165. mode="multiple"
  166. style={{ minWidth: 200 }}
  167. placeholder="请选择媒体账户"
  168. maxTagCount={1}
  169. allowClear
  170. bordered={false}
  171. filterOption={(input: any, option: any) =>
  172. (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
  173. }
  174. onChange={(e, option) => {
  175. setAccountCreateLogs(option?.map((item: any) => ({ adAccountId: item?.children, id: item?.value })))
  176. }}
  177. >
  178. {getAdAccount?.data?.data?.map((item: any) => <Select.Option value={item.id} key={item.id}>{item.accountId}</Select.Option>)}
  179. {/* <Select.OptGroup label="Engineer">
  180. <Select.Option value="20632113">20632113</Select.Option>
  181. </Select.OptGroup> */}
  182. </Select>
  183. </Selector>
  184. <Selector label="推广目标">
  185. <Select style={{ width: 200 }} value={queryForm?.promotedObjectType} placeholder="请选择推广目标" bordered={false} showSearch filterOption={(input: any, option: any) =>
  186. (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
  187. } onChange={(e) => { setQueryForm({ ...queryForm, promotedObjectType: e }) }}>
  188. {Object.keys(PromotedObjectType).map(key => {
  189. return <Select.Option value={key} key={key}>{PromotedObjectType[key]}</Select.Option>
  190. })}
  191. </Select>
  192. </Selector>
  193. </Space>
  194. <div className={style.cardBody}>
  195. <Row className={style.content}>
  196. <Col span={16} className={style.conLeft}>
  197. <Row className={`${style.conTitle} ${style.conRightBorder}`}><Col span={24}>广告</Col></Row>
  198. <Row className={style.items}>
  199. {/* =============广告基本信息=========== */}
  200. <Col className={style.conRightBorder}>
  201. <div className={style.top}>广告基本信息</div>
  202. <div className={style.center}>
  203. <Spin spinning={getSysAdgroups.loading}>
  204. <div className={style.centerContent}>
  205. {getSysAdgroups?.data ? <>
  206. <div>广告名称: <span>{getSysAdgroups?.data?.adgroupName}</span></div>
  207. <div>推广目标: <span>{PromotedObjectType[getSysAdgroups?.data?.promotedObjectType]}</span></div>
  208. <div>广告版位: <span>{getSysAdgroups?.data?.siteSet?.map((item: string) => SiteSetEnum[item]).toString()}</span></div>
  209. <div>投放日期: <span>{getSysAdgroups?.data?.endDate ? getSysAdgroups?.data?.beginDate + '~' + getSysAdgroups?.data?.endDate : getSysAdgroups?.data?.beginDate + '~' + '长期投放'}</span></div>
  210. <div>出价方式: <span>{BidModeEnum[getSysAdgroups?.data?.bidMode]}</span></div>
  211. <div>优化目标: <span>{OptimizationGoalEnum[getSysAdgroups?.data?.optimizationGoal]}</span></div>
  212. <div>出价类型: <span>{getSysAdgroups?.data?.smartBidType === 'SMART_BID_TYPE_CUSTOM' ? '手动出价' : '自动出价'}</span></div>
  213. <div>出价策略: <span>{BidStrategyEnum[getSysAdgroups?.data?.bidStrategy]}</span></div>
  214. <div>广告出价: <span>{getSysAdgroups?.data?.bidAmount}</span></div>
  215. <div>广告日预算: <span>{getSysAdgroups?.data?.dailyBudget || '不限'}</span></div>
  216. </> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
  217. </div>
  218. </Spin>
  219. </div>
  220. <div className={style.bottom}>
  221. {queryForm?.promotedObjectType ? <span onClick={() => { setAdVisible(true) }}>{getSysAdgroups?.data ? '修改' : '添加'}</span> : <Tooltip title="请先选择推广目标">
  222. <span>添加</span>
  223. </Tooltip>}
  224. </div>
  225. </Col>
  226. {/* =============定向包=========== */}
  227. <Col className={style.conRightBorder}>
  228. <div className={style.top}>
  229. 定向{/* <span>已选:{1}</span> */}
  230. </div>
  231. <div className={style.center}>
  232. <Spin spinning={getsysTargeting.loading}>
  233. <div className={style.centerContent}>
  234. {getsysTargeting?.data ? <>
  235. <div>定向名称: <span>{getsysTargeting?.data?.targetingName}</span></div>
  236. <div>定向描述: <span>{getsysTargeting?.data?.description}</span></div>
  237. <TargetingTooltip data={getsysTargeting?.data} geoLocationList={geoLocationList} modelList={modelList}/>
  238. </> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
  239. </div>
  240. </Spin>
  241. </div>
  242. <div className={style.bottom}><span onClick={() => { setDxVisible(true) }}>{getsysTargeting?.data ? '修改' : '添加'}</span></div>
  243. </Col>
  244. {/* =============商品=========== */}
  245. <Col className={style.conRightBorder}>
  246. <div className={style.top}>
  247. 商品{/* <span>已选:{1}</span> */}
  248. </div>
  249. <div className={style.center}>
  250. <div className={style.centerContent}>
  251. {accountCreateLogs?.map((item: any, index: number) => {
  252. if (item?.productList) {
  253. return <div className={style.acc} key={index}>
  254. <div className={style.accName}>{item.adAccountId}</div>
  255. {
  256. item?.productList?.map((pack: { productName: string, author: string, id: number }, index: number) => {
  257. return <div className={style.accCon} key={pack.id}>{pack.productName}<CloseOutlined className={style.close} onClick={() => {
  258. goodsDel(index)
  259. }} /></div>
  260. })
  261. }
  262. </div>
  263. } else {
  264. return null
  265. }
  266. })}
  267. </div>
  268. </div>
  269. <div className={style.bottom}>
  270. {accountCreateLogs?.length > 0 ? <span onClick={() => { setGoodsVisible(true) }}>编辑</span> : <Tooltip title="请先选择媒体账户">
  271. <span>编辑</span>
  272. </Tooltip>}
  273. </div>
  274. </Col>
  275. {/* 数据源 */}
  276. <Col className={style.conRightBorder}>
  277. <div className={style.top}>
  278. 数据源 {/* <span>已选:{1}</span> */}
  279. </div>
  280. <div className={style.center}>
  281. {/* userActionSetsList */}
  282. <div className={style.centerContent}>
  283. {accountCreateLogs?.map((item: any, index: number) => {
  284. if (item?.userActionSetsList && item?.userActionSetsList?.length > 0) {
  285. return <div className={style.acc} key={index}>
  286. <div className={style.accName}>{item.adAccountId}</div>
  287. {
  288. item?.userActionSetsList?.map((pack: { name: string, type: string, id: number }, index1: number) => {
  289. return <div className={style.accCon} key={pack.id}> <span className={style.title}>{pack.name}{' > '}{pack.type}</span> <CloseOutlined className={style.close} onClick={() => {
  290. sourceDel(index, index1)
  291. }} /></div>
  292. })
  293. }
  294. </div>
  295. } else {
  296. return null
  297. }
  298. })}
  299. </div>
  300. </div>
  301. <div className={style.bottom}>
  302. {accountCreateLogs?.length > 0 ? <span onClick={() => { setSourceVisible(true) }}>编辑</span> : <Tooltip title="请先选择媒体账户">
  303. <span>编辑</span>
  304. </Tooltip>}
  305. </div>
  306. </Col>
  307. </Row>
  308. </Col>
  309. {/* =============广告创意=========== */}
  310. <Col span={8} className={style.conRight}>
  311. <Row className={style.conTitle}><Col span={24}>广告创意</Col></Row>
  312. <Row className={style.items}>
  313. <Col span={12} className={style.conRightBorder}>
  314. <div className={style.top}>创意基本信息</div>
  315. <div className={style.center}>
  316. </div>
  317. <div className={style.bottom}><span onClick={() => { }}>编辑</span></div>
  318. </Col>
  319. <Col span={12} >
  320. <div className={style.top}>
  321. 落地页
  322. {wxButtonList?.length > 0 && <Button type="link" size="small">配置客服</Button>}
  323. </div>
  324. <div className={style.center}>
  325. <Spin spinning={get.loading}>
  326. <div className={style.centerContent}>
  327. {queryForm?.sysPageId ? <>
  328. <div>落地页名称:{get?.data?.pageName || ''}</div>
  329. <div>分享名称:{get?.data?.shareContentSpec?.shareTitle || ''}</div>
  330. <div>分享描述:{get?.data?.shareContentSpec?.shareDescription || ''}</div>
  331. <div>原生推广页顶部素材预览:
  332. <div>{get?.data?.pageSpecsList[0]?.pageElementsSpecList?.filter((item: any, index: number) => index === 0)?.map((item: { elementType: 'TOP_IMAGE' | 'TOP_VIDEO' | 'TOP_SLIDER', topImageSpec: any, topSliderSpec: any, topVideoSpec: any }, index: number) => {
  333. switch (item?.elementType) {
  334. case 'TOP_IMAGE':
  335. return <Image width={80} src={item.topImageSpec.imageUrl} style={{ borderRadius: 8, overflow: 'hidden' }} key={index} />
  336. case 'TOP_SLIDER':
  337. return <Space wrap>
  338. {item?.topSliderSpec?.imageUrlList?.map((url: string, index: number) => <Image width={70} src={url} style={{ borderRadius: 8 }} key={index} />)}
  339. </Space>
  340. case 'TOP_VIDEO':
  341. return <video src={item.topVideoSpec.videoUrl} width={150} controls key={index}></video>
  342. }
  343. })}</div>
  344. </div>
  345. </> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
  346. </div>
  347. </Spin>
  348. </div>
  349. <div className={style.bottom}>{queryForm?.sysAdgroupsId ? <>
  350. {queryForm?.sysPageId && <Button type="link" onClick={() => { setLookVisible(true) }}>查看</Button>}
  351. <Button type="link" onClick={() => { setSelectImgVisible(true) }}>{queryForm?.sysPageId ? '修改' : '选择落地页'}</Button>
  352. </> : <Tooltip title="请先设置广告基本信息和创意">
  353. <Button type="link"><span>选择落地页</span></Button>
  354. </Tooltip>}
  355. </div>
  356. </Col>
  357. </Row>
  358. </Col>
  359. </Row>
  360. {/* =============广告底部按钮=========== */}
  361. <Space className={style.bts}>
  362. {/* <Button type='primary' onClick={severBd}>暂存到本地</Button> */}
  363. <Button type='primary' onClick={preview}><SearchOutlined /> 批量预览广告</Button>
  364. {/* <Button onClick={delBdPlan}>清空本地配置</Button> */}
  365. </Space>
  366. </div>
  367. </Card>
  368. <Card
  369. className={style.createAd}
  370. hoverable
  371. extra={tableData?.length > 0 ? <Space>
  372. <span>推广计划总数:{accountCreateLogs?.length}</span>
  373. <span>广告总数:{accountCreateLogs?.length}</span>
  374. {tableSelect?.length > 0 && <span> 已选:<span style={{ color: '#1890FF' }}>{tableSelect?.length}</span> 条</span>}
  375. {
  376. <Button type='primary' onClick={() => {
  377. if (tableSelect.length === 0) {
  378. message.error('请选择要提交的计划!')
  379. return
  380. };
  381. setSubVisible(true)
  382. }}>批量提交审核</Button>
  383. }
  384. </Space> : false
  385. }
  386. >
  387. {tableData?.length > 0 ? <div className={style.cardBody}>
  388. <div className={style.content} style={{ marginTop: 20 }}>
  389. <Tables
  390. columns={columns()}
  391. dataSource={tableData}
  392. total={0}
  393. size="small"
  394. bordered
  395. scroll={{ x: 1800 }}
  396. rowSelection={{
  397. selectedRowKeys: tableSelect?.map((item: any) => item?.id.toString()),
  398. onChange: (selectedRowKeys: React.Key[], selectedRows: any) => {
  399. setTableSelect(selectedRows)
  400. }
  401. }}
  402. />
  403. </div>
  404. </div> : <div style={{ minHeight: 400, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
  405. <Empty description="请先完成模块配置后,再预览广告计划" />
  406. </div>}
  407. </Card>
  408. {/* 选择广告 */}
  409. {adVisible && <AdModal visible={adVisible} onClose={() => setAdVisible(false)} promotedObjectType={queryForm.promotedObjectType as string} onChange={(e) => { setQueryForm({ ...queryForm, sysAdgroupsId: e }); setAdVisible(false) }} sysAdgroupsId={queryForm?.sysAdgroupsId}/>}
  410. {/* 选择定向 */}
  411. {dxVisible && <TargetingModal visible={dxVisible} onClose={() => setDxVisible(false)} onChange={(e) => { setQueryForm({ ...queryForm, sysTargetingId: e }); setDxVisible(false) }} sysTargetingId={queryForm?.sysTargetingId}/>}
  412. {/* 选择商品 */}
  413. {goodsVisible && <GoodsModal visible={goodsVisible} data={accountCreateLogs} onClose={() => setGoodsVisible(false)} onChange={(e) => { setAccountCreateLogs(e); setGoodsVisible(false) }} />}
  414. {/* 选择数据源 */}
  415. {sourceVisible && <DataSourceModal visible={sourceVisible} data={accountCreateLogs} onClose={() => setSourceVisible(false)} onChange={(e) => { setAccountCreateLogs(e); setSourceVisible(false) }} />}
  416. {/* 选择转化ID */}
  417. {idVisible && <IdModal visible={idVisible} data={accountCreateLogs} onClose={() => setIdVisible(false)} onChange={(e) => { setAccountCreateLogs(e); setSourceVisible(false) }} />}
  418. {/* 选择素材 */}
  419. {selectImgVisible && <SelectCloud visible={selectImgVisible} onClose={() => setSelectImgVisible(false)} onChange={setPage} isBack={false} />}
  420. {/* 查看落地页 */}
  421. {lookVisible && <LookLanding visible={lookVisible} onClose={() => setLookVisible(false)} id={queryForm?.sysPageId as any} />}
  422. {/* 设置名称 */}
  423. {subVisible && <SubmitModal visible={subVisible} onClose={() => setSubVisible(false)} onChange={submit} />}
  424. </Space>
  425. }
  426. export default CreateAd