leadAd.tsx 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743
  1. import React, { useCallback, useEffect, useState } from 'react'
  2. import { Modal, Form, Input, Divider, Select, Radio, DatePicker, Switch, Checkbox, message, Tooltip, Row, Col, Space, TimePicker, Button } from 'antd'
  3. import { BidModeEnum, OptimizationGoalEnum, BidStrategyEnum, AdStatus, GoalRoasEnum } from '@/services/launchAdq/enum'
  4. import { ModalConfig } from '../index'
  5. import moment from 'moment';
  6. import { useAjax } from '@/Hook/useAjax';
  7. import { getSceneTagsList } from '@/services/launchAdq/global';
  8. import { ExclamationCircleOutlined } from '@ant-design/icons';
  9. import { CreateAdProps } from '@/services/launchAdq/createAd';
  10. import { createSysAdgroups } from '@/services/launchAdq/localAd';
  11. import AdPositionList from './adPositionList';
  12. import BidAdjustment from './bidAdjustment';
  13. import { RangePickerProps } from 'antd/lib/date-picker';
  14. import { txtLength } from '@/utils/utils';
  15. import InputName from '@/components/InputName';
  16. const { RangePicker }: { RangePicker: any } = DatePicker;
  17. let DatePickers: any = DatePicker
  18. interface Props {
  19. queryForm: Partial<CreateAdProps>,
  20. visible: boolean,
  21. PupFn: (arg: ModalConfig) => void,
  22. callback: (params: any) => void,
  23. confirmLoading?: boolean,
  24. type?: 'add' | 'look' | 'edit',//新增,查看,编辑
  25. dataInfo?: any,
  26. ajax: any
  27. }
  28. export const siteSetData = [
  29. { siteSet: 'SITE_SET_MOMENTS', type: 1 },
  30. { siteSet: 'SITE_SET_MOBILE_UNION', type: 3 },
  31. { siteSet: 'SITE_SET_TENCENT_NEWS', type: 2 },
  32. { siteSet: 'SITE_SET_TENCENT_VIDEO', type: 2 },
  33. { siteSet: 'SITE_SET_KANDIAN', type: 2 },
  34. { siteSet: 'SITE_SET_QQ_MUSIC_GAME', type: 2 },
  35. { siteSet: 'SITE_SET_WECHAT', type: 4 },
  36. { siteSet: 'SITE_SET_WECHAT_PLUGIN', type: 4 },
  37. ]
  38. export interface SiteSetPackageDataProps {
  39. siteSet: string[],
  40. bidCoefficient?: number,
  41. deepBidCoefficient?: number
  42. }
  43. /**收集线索广告弹窗*/
  44. function LeadAdModal(props: Props) {
  45. let { visible, PupFn, callback, type, dataInfo, queryForm, ajax } = props
  46. const createSysAdgroup = useAjax((params) => createSysAdgroups(params))
  47. let arg = type === 'look' ? { footer: null } : {}
  48. let [state, setState] = useState<any>({
  49. isShowTime: []
  50. })
  51. let [template_checked, settemplate_checked] = useState<boolean>(dataInfo?.isTemplate || false)
  52. const sceneTagsList = useAjax((params) => getSceneTagsList(params))
  53. const [form] = Form.useForm();
  54. let dateType = Form.useWatch('dateType', form)
  55. let bidMode = Form.useWatch('bidMode', form)
  56. let smartBidType = Form.useWatch('smartBidType', form)
  57. let autoAcquisitionEnabled = Form.useWatch('autoAcquisitionEnabled', form)
  58. let siteSet = Form.useWatch('siteSet', form)
  59. let wechatPositionType = Form.useWatch('wechatPositionType', form)
  60. let wechatSceneType = Form.useWatch('wechatSceneType', form)
  61. let automaticSiteEnabled = Form.useWatch('automaticSiteEnabled', form)
  62. let optimizationGoal = Form.useWatch('optimizationGoal', form)
  63. let depthConversionEnabled = Form.useWatch('depthConversionEnabled', form)
  64. let deepConversionType = Form.useWatch('deepConversionType', form)
  65. let goal = Form.useWatch('goal', form)
  66. let bidAdjustmentEnabled = Form.useWatch('bidAdjustmentEnabled', form)
  67. let siteSetPackage = Form.useWatch('siteSetPackage', form)
  68. let deepBidAmount = Form.useWatch('deepBidAmount', form)
  69. let bidAmountAdjustmentEnabled = Form.useWatch('bidAmountAdjustmentEnabled', form)
  70. let bidAmount = Form.useWatch('bidAmount', form)
  71. const [behaviorList, setBehaviorList] = useState<string[]>([])
  72. const [worthList, setWorthList] = useState<string[]>([])
  73. // 确定事件
  74. const handleOk = useCallback(() => {
  75. form.validateFields().then(values => {
  76. let newValues = JSON.parse(JSON.stringify(values))
  77. newValues.sceneSpec = {}
  78. if (newValues.dateType === '2') {
  79. newValues['beginDate'] = moment(newValues.date).format('YYYY-MM-DD')
  80. } else {
  81. newValues['beginDate'] = moment(newValues.date[0]).format('YYYY-MM-DD')
  82. newValues['endDate'] = moment(newValues.date[1]).format('YYYY-MM-DD')
  83. }
  84. if (newValues.firstDayBeginTime) {
  85. console.log(newValues.firstDayBeginTime)
  86. newValues['firstDayBeginTime'] = moment(newValues.firstDayBeginTime).format('HH:mm:ss')
  87. }
  88. Object.keys(newValues).forEach(key => {
  89. switch (key) {
  90. case 'wechatPositionType':
  91. if (newValues[key] === '1') {
  92. newValues.sceneSpec = { ...newValues.sceneSpec, wechatPosition: newValues.wechatPosition }
  93. }
  94. break;
  95. case 'wechatSceneType':
  96. if (newValues[key] === '1') {
  97. newValues.sceneSpec = {
  98. ...newValues.sceneSpec, wechatScene: {
  99. officialAccountMediaCategory: newValues.officialAccountMediaCategory,
  100. miniProgramAndMiniGame: newValues.miniProgramAndMiniGame,
  101. payScene: newValues.payScene
  102. }
  103. }
  104. }
  105. break;
  106. case 'siteSetPackage': // 处理分版位出价
  107. if (newValues[key]?.length > 0) {
  108. let newSiteSetPackage: SiteSetPackageDataProps[] = JSON.parse(JSON.stringify(newValues[key]))
  109. if (!newValues?.bidAmountAdjustmentEnabled) {
  110. newSiteSetPackage = newSiteSetPackage.map(item => {
  111. let { bidCoefficient, ...newItem } = item
  112. return { ...newItem }
  113. })
  114. }
  115. if (!newValues?.bidAdjustmentEnabled) {
  116. newSiteSetPackage = newSiteSetPackage.map(item => {
  117. let { deepBidCoefficient, ...newItem } = item
  118. return { ...newItem }
  119. })
  120. }
  121. newValues.bidAdjustment = {
  122. siteSetPackage: newSiteSetPackage
  123. }
  124. delete newValues.siteSetPackage
  125. }
  126. break
  127. case 'deepConversionType': // 处理深度优化
  128. let deepConversionSpec: any = {
  129. deepConversionType: newValues[key]
  130. }
  131. if (newValues[key] === 'DEEP_CONVERSION_WORTH') { // 优化 ROI
  132. deepConversionSpec.deepConversionWorthSpec = {
  133. goal: newValues.goal,
  134. expectedRoi: newValues.deepBidAmount
  135. }
  136. } else if (newValues[key] === 'DEEP_CONVERSION_BEHAVIOR') { // 优化转化行为
  137. deepConversionSpec.deepConversionBehaviorSpec = {
  138. goal: newValues.goal,
  139. bidAmount: newValues.deepBidAmount * 100
  140. }
  141. }
  142. newValues.deepConversionSpec = deepConversionSpec
  143. delete newValues.deepBidAmount
  144. delete newValues.goal
  145. delete newValues.optimizationMode
  146. delete newValues.deepConversionType
  147. delete newValues.depthConversionEnabled
  148. break
  149. }
  150. })
  151. if (newValues.sceneSpec.wechatPosition?.length === 0) {
  152. delete newValues.sceneSpec.wechatPosition
  153. }
  154. if (newValues.sceneSpec.wechatScene) {
  155. newValues.sceneSpec.wechatScene.officialAccountMediaCategory?.length === 0 && (delete newValues.sceneSpec.wechatScene.officialAccountMediaCategory)
  156. newValues.sceneSpec.wechatScene.miniProgramAndMiniGame?.length === 0 && (delete newValues.sceneSpec.wechatScene.miniProgramAndMiniGame)
  157. newValues.sceneSpec.wechatScene.payScene?.length === 0 && (delete newValues.sceneSpec.wechatScene.payScene)
  158. }
  159. if (!newValues.sceneSpec.wechatPosition && !newValues.sceneSpec.wechatScene) {
  160. delete newValues.sceneSpec
  161. }
  162. delete newValues.officialAccountMediaCategory
  163. delete newValues.miniProgramAndMiniGame
  164. delete newValues?.bidAmountAdjustmentEnabled
  165. delete newValues?.bidAdjustmentEnabled
  166. delete newValues.payScene
  167. delete newValues.wechatPositionType
  168. delete newValues.wechatPosition
  169. delete newValues.wechatScene
  170. delete newValues['dateType']
  171. delete newValues['date']
  172. newValues['timeSeries'] = Array(336).fill(1).join('')
  173. newValues['promotedObjectType'] = queryForm.promotedObjectType
  174. console.log(newValues)
  175. newValues['isTemplate'] = template_checked
  176. // 开启存为模板开关执行
  177. // if (template_checked && type==='add') {
  178. // createSysAdgroup.run(newValues).then(res => {
  179. // if (res) {
  180. // callback(newValues)
  181. // }
  182. // })
  183. // } else {
  184. callback(newValues)
  185. // }
  186. })
  187. }, [form, template_checked, queryForm, type])
  188. // 场景定向
  189. useEffect(() => {
  190. sceneTagsList.run({ typeList: ['WECHAT_POSITION', 'OFFICIAL_ACCOUNT_MEDIA_CATEGORY', 'MINI_PROGRAM_AND_MINI_GAME', 'PAY_SCENE'] })
  191. }, [])
  192. // 数据回填
  193. useEffect(() => {
  194. if (dataInfo) {
  195. let formData: any = {
  196. adgroupName: dataInfo?.adgroupName,//广告名称
  197. promotedObjectType: dataInfo?.promotedObjectType,//推广目标
  198. siteSet: dataInfo?.siteSet,//广告版位
  199. autoAcquisitionEnabled: dataInfo?.autoAcquisitionEnabled,//一键起量
  200. bidAmount: dataInfo?.bidAmount,//出价
  201. smartBidType: dataInfo?.smartBidType,//出价类型
  202. bidStrategy: dataInfo?.bidStrategy,//出价策略
  203. bidMode: dataInfo?.bidMode,//出价方式
  204. optimizationGoal: dataInfo?.optimizationGoal,//优化目标
  205. dateType: dataInfo?.endDate ? '1' : '2',//投放日期
  206. dailyBudget: dataInfo?.dailyBudget,//广告日预算
  207. date: dataInfo?.endDate ? [moment(dataInfo?.beginDate), moment(dataInfo?.endDate)] : moment(dataInfo?.beginDate),//日期
  208. autoAcquisitionBudget: dataInfo?.autoAcquisitionBudget,//起量预算
  209. wechatPositionType: dataInfo?.sceneSpec?.wechatPosition ? '1' : '0',//微信公众号与小程序定投
  210. wechatPosition: dataInfo?.sceneSpec?.wechatPosition,//微信公众号与小程序定投
  211. wechatSceneType: dataInfo?.sceneSpec?.wechatScene?.officialAccountMediaCategory || dataInfo?.sceneSpec?.wechatScene?.miniProgramAndMiniGame || dataInfo?.sceneSpec?.wechatScene?.payScene ? '1' : '0',//微信公众号与小程序场景
  212. officialAccountMediaCategory: dataInfo?.sceneSpec?.wechatScene?.officialAccountMediaCategory,//公众号媒体类型
  213. miniProgramAndMiniGame: dataInfo?.sceneSpec?.wechatScene?.miniProgramAndMiniGame,//小程序小游戏流量类型
  214. payScene: dataInfo?.sceneSpec?.wechatScene?.payScene,//订单详情页消费场景
  215. firstDayBeginTime: dataInfo?.firstDayBeginTime ? moment(`${dataInfo?.beginDate} ${dataInfo?.firstDayBeginTime}`) : undefined,//首日开始时间
  216. configuredStatus: dataInfo?.configuredStatus || 'AD_STATUS_SUSPEND',//广告启停
  217. }
  218. Object.keys(dataInfo).forEach(key => {
  219. switch (key) {
  220. case 'bidAdjustment': // 处理分版位出价
  221. if (dataInfo[key]?.siteSetPackage && dataInfo[key]?.siteSetPackage?.length > 0) {
  222. let siteSetPackage: SiteSetPackageDataProps[] = dataInfo[key].siteSetPackage
  223. if (siteSetPackage.some(item => item.bidCoefficient)) { // 判断出价是否开启了分版位
  224. formData.bidAmountAdjustmentEnabled = true
  225. } else {
  226. siteSetPackage = siteSetPackage.map(item => ({ ...item, bidCoefficient: 1 }))
  227. }
  228. if (siteSetPackage.some(item => item.deepBidCoefficient)) { // 判断深度出价是否开启了分版位
  229. formData.bidAdjustmentEnabled = true
  230. } else {
  231. siteSetPackage = siteSetPackage.map(item => ({ ...item, deepBidCoefficient: 1 }))
  232. }
  233. formData.siteSetPackage = siteSetPackage
  234. }
  235. break
  236. case 'deepConversionSpec': // 处理深度优化
  237. if (dataInfo[key]?.deepConversionType === 'DEEP_CONVERSION_WORTH') {
  238. formData = {
  239. ...formData,
  240. optimizationMode: 'DEEP_CONVERSION_TARGET',
  241. depthConversionEnabled: true,
  242. deepConversionType: dataInfo[key]?.deepConversionType,
  243. goal: dataInfo[key]?.deepConversionWorthSpec?.goal,
  244. deepBidAmount: dataInfo[key]?.deepConversionWorthSpec?.expectedRoi,
  245. }
  246. } else if (dataInfo[key]?.deepConversionType === 'DEEP_CONVERSION_BEHAVIOR') {
  247. formData = {
  248. ...formData,
  249. optimizationMode: 'DEEP_CONVERSION_TARGET',
  250. depthConversionEnabled: true,
  251. deepConversionType: dataInfo[key]?.deepConversionType,
  252. goal: dataInfo[key]?.deepConversionBehaviorSpec?.goal,
  253. deepBidAmount: dataInfo[key]?.deepConversionBehaviorSpec?.bidAmount / 100,
  254. }
  255. }
  256. break
  257. }
  258. })
  259. form.setFieldsValue(formData)
  260. if (dataInfo?.firstDayBeginTime) {//存在首日开始时间,选中开关
  261. setState({ ...state, isShowTime: ['1'] })
  262. }
  263. } else {
  264. form.setFieldsValue({
  265. adgroupName: '广告_销售线索',
  266. date: moment().startOf('day').add(2, 'M'),
  267. optimizationGoal: "OPTIMIZATIONGOAL_ECOMMERCE_ORDER",
  268. bidStrategy: "BID_STRATEGY_TARGET_COST",
  269. bidAmount: '1000'
  270. })
  271. }
  272. }, [dataInfo])
  273. // 出价方式改变清空某些数据
  274. const bidModeChange = useCallback((props) => {
  275. form.setFieldsValue({
  276. ...props,
  277. optimizationGoal: 'OPTIMIZATIONGOAL_ECOMMERCE_ORDER',
  278. smartBidType: 'SMART_BID_TYPE_CUSTOM',
  279. // bidAmount:null,
  280. bidStrategy: 'BID_STRATEGY_TARGET_COST',
  281. autoAcquisitionEnabled: false,
  282. autoAcquisitionBudget: null,
  283. dailyBudget: null,
  284. })
  285. }, [])
  286. // 出价和版位改变时查询
  287. useEffect(() => {
  288. if (bidMode && siteSet && siteSet?.length > 0) {
  289. let obj: any = { siteSet, promotedObjectType: queryForm.promotedObjectType }
  290. if (bidMode === 'BID_MODE_OCPC' || bidMode === 'BID_MODE_OCPM') {
  291. obj = { ...obj, bidMode }
  292. }
  293. ajax.run(obj).then((res: any) => {
  294. console.log(res)
  295. })
  296. }
  297. }, [bidMode, siteSet])
  298. // 处理深度转化优化
  299. useEffect(() => {
  300. if (optimizationGoal && ajax?.data) {
  301. let { deepBehaviorOptimizationGoalPermissionList, deepWorthOptimizationGoalPermissionList } = ajax?.data
  302. // deepBehaviorOptimizationGoalPermissionList 优化转化行为
  303. // deepWorthOptimizationGoalPermissionList 优化ROI
  304. let behavior = deepBehaviorOptimizationGoalPermissionList?.find((item: { optimizationGoal: string }) => item.optimizationGoal === optimizationGoal)
  305. let worth = deepWorthOptimizationGoalPermissionList?.find((item: { optimizationGoal: string }) => item.optimizationGoal === optimizationGoal)
  306. setBehaviorList(behavior?.deepBehaviorOptimizationGoalList || [])
  307. setWorthList(worth?.deepWorthOptimizationGoalList || [])
  308. }
  309. }, [optimizationGoal, ajax?.data])
  310. /**处理分版位数据 */
  311. const setSiteSetHandle = (siteSet: string[]) => {
  312. let data: SiteSetPackageDataProps[] = []
  313. if (siteSet && siteSet?.length > 0) {
  314. let data1: string[] = []
  315. let data2: string[] = []
  316. let data3: string[] = []
  317. let data4: string[] = []
  318. siteSet.forEach((item: string) => {
  319. let siteData = siteSetData?.find(item1 => item1.siteSet === item)
  320. if (siteData) {
  321. switch (siteData.type) {
  322. case 1:
  323. data1.push(item)
  324. break
  325. case 2:
  326. data2.push(item)
  327. break
  328. case 3:
  329. data3.push(item)
  330. break
  331. case 4:
  332. data4.push(item)
  333. break
  334. }
  335. }
  336. });
  337. let newSiteSetPackageData: SiteSetPackageDataProps[] = []
  338. if (data1?.length > 0) {
  339. newSiteSetPackageData.push({ siteSet: data1, bidCoefficient: 1, deepBidCoefficient: 1 })
  340. }
  341. if (data2?.length > 0) {
  342. newSiteSetPackageData.push({ siteSet: data2, bidCoefficient: 1, deepBidCoefficient: 1 })
  343. }
  344. if (data3?.length > 0) {
  345. newSiteSetPackageData.push({ siteSet: data3, bidCoefficient: 1, deepBidCoefficient: 1 })
  346. }
  347. if (data4?.length > 0) {
  348. newSiteSetPackageData.push({ siteSet: data4, bidCoefficient: 1, deepBidCoefficient: 1 })
  349. }
  350. data = newSiteSetPackageData
  351. } else {
  352. data = []
  353. }
  354. form.setFieldsValue({
  355. siteSetPackage: data
  356. })
  357. }
  358. /** 禁止选择以前时间 */
  359. const disabledDate: RangePickerProps['disabledDate'] = current => {
  360. // Can not select days before today and today
  361. return current && current < moment().startOf('day');
  362. };
  363. return <Modal
  364. visible={visible}
  365. title={type === 'add' ? '新建广告' : type === 'look' ? '广告详情' : '编辑广告'}
  366. onCancel={() => { PupFn({ visible: false, dataInfo: null, type: 'add' }) }}
  367. onOk={type === 'look' ? () => message.warning('详情无法改动内容') : handleOk}
  368. width={900}
  369. confirmLoading={createSysAdgroup?.loading}
  370. footer={<Space>
  371. <Button onClick={() => { PupFn({ visible: false, dataInfo: null, type: 'add' }) }}>取消</Button>
  372. <Button type='primary' onClick={handleOk}>确定</Button>
  373. {<Checkbox checked={template_checked} onChange={(e) => {
  374. let checked = e.target.checked
  375. settemplate_checked(checked)
  376. }}>存为模板</Checkbox>}
  377. </Space>}
  378. {...arg}
  379. >
  380. <Form
  381. form={form}
  382. labelCol={{ span: 5 }}
  383. className='ad_form_style'
  384. initialValues={
  385. {
  386. promotedObjectType: queryForm.promotedObjectType,
  387. siteSet: ['SITE_SET_MOMENTS', 'SITE_SET_WECHAT', 'SITE_SET_WECHAT_PLUGIN'],
  388. bidMode: 'BID_MODE_OCPM',
  389. automaticSiteEnabled: false,
  390. dateType: '2',
  391. bidStrategy: 'BID_STRATEGY_AVERAGE_COST',
  392. timeSeries: '1',
  393. smartBidType: 'SMART_BID_TYPE_CUSTOM',
  394. autoAcquisitionEnabled: false,
  395. wechatSceneType: '0',
  396. wechatPositionType: '0',
  397. // optimizationGoal: 'OPTIMIZATIONGOAL_ECOMMERCE_ORDER',
  398. configuredStatus: 'AD_STATUS_SUSPEND',
  399. siteSetPackage: [{ siteSet: ["SITE_SET_MOMENTS"], bidCoefficient: 1, deepBidCoefficient: 1 }, { siteSet: ["SITE_SET_WECHAT"], bidCoefficient: 1, deepBidCoefficient: 1 }]
  400. }
  401. }
  402. >
  403. {/* ============================================================基本信息============================================================= */}
  404. <Divider orientation='center'>基本信息</Divider>
  405. <Form.Item
  406. label={<strong>广告名称</strong>}
  407. name='adgroupName'
  408. tooltip="下标、日期时分秒、广告账户创建时默认自带"
  409. rules={[
  410. { required: true, message: '请输入广告名称!' },
  411. {
  412. required: true, message: '广告名称不能包含特殊字符:< > & ‘ ” / 以及TAB、换行、回车键,请修改', validator(rule, value, callback) {
  413. let reg = /[&‘’“”/\n\t\f]/ig
  414. if (value && reg.test(value)) {
  415. return Promise.reject()
  416. }
  417. return Promise.resolve()
  418. },
  419. },
  420. // {
  421. // required: true, message: '您可能使用了不支持的通配符,请您确认后再保存', validator(rule, value, callback) {
  422. // let reg = /(<账号备注>)|(<商品ID>)|(<账号ID>)|(<日期>)|(<时分秒>)|(<定向名>)|(<素材名>)|(<落地页名>|(<优化目标>)/ig
  423. // let reg1 = /[<>]/ig
  424. // if (value && (reg1.test(value))) {
  425. // if (reg.test(value)) {
  426. // return Promise.resolve()
  427. // }
  428. // return Promise.reject()
  429. // }
  430. // return Promise.resolve()
  431. // },
  432. // },
  433. {
  434. required: true, message: '请确保广告名称长度不超过60个字(1个汉字等于2个字符)', validator(rule, value, callback) {
  435. if (value && txtLength(value) > 60) {
  436. return Promise.reject()
  437. }
  438. return Promise.resolve()
  439. },
  440. }
  441. ]}
  442. >
  443. <InputName placeholder='广告名称' style={{ width: 400 }} length={60} />
  444. </Form.Item>
  445. <Form.Item label={<strong>广告版位</strong>}>
  446. <Form.Item name='automaticSiteEnabled'>
  447. <Radio.Group buttonStyle="solid">
  448. <Radio.Button value={true} disabled={queryForm.promotedObjectType === 'PROMOTED_OBJECT_TYPE_WECHAT_OFFICIAL_ACCOUNT'}>自动版位</Radio.Button>
  449. <Radio.Button value={false}>选择特定版位</Radio.Button>
  450. </Radio.Group>
  451. </Form.Item>
  452. {!automaticSiteEnabled && <Form.Item name='siteSet' noStyle rules={[{ required: true, message: '请输入选择广告版位!' }]}>
  453. <Checkbox.Group style={{ width: '100%' }} onChange={(e) => { bidModeChange({ bidMode: 'BID_MODE_OCPM' }); setSiteSetHandle(e as string[]) }}>
  454. <Row>
  455. {/* <Col span={4}>
  456. <Checkbox value="SITE_SET_CHANNELS">微信视频号</Checkbox>
  457. </Col> */}
  458. <Col span={4}>
  459. <Checkbox value="SITE_SET_MOMENTS">微信朋友圈</Checkbox>
  460. </Col>
  461. <Col span={4}>
  462. <Checkbox value="SITE_SET_MOBILE_UNION">优量汇</Checkbox>
  463. </Col>
  464. <Col span={4}>
  465. <Checkbox value="SITE_SET_TENCENT_NEWS">腾讯新闻</Checkbox>
  466. </Col>
  467. <Col span={4}>
  468. <Checkbox value="SITE_SET_TENCENT_VIDEO">腾讯视频</Checkbox>
  469. </Col>
  470. <Col span={4}>
  471. <Checkbox value="SITE_SET_KANDIAN">QQ 浏览器</Checkbox>
  472. </Col>
  473. <Col span={6}>
  474. <Checkbox value="SITE_SET_QQ_MUSIC_GAME">QQ、腾讯音乐及游戏</Checkbox>
  475. </Col>
  476. <Col span={6}>
  477. <Checkbox value="SITE_SET_WECHAT">微信公众号与小程序</Checkbox>
  478. </Col>
  479. <Col span={5}>
  480. <Checkbox value="SITE_SET_WECHAT_PLUGIN">微信新闻插件</Checkbox>
  481. </Col>
  482. </Row>
  483. </Checkbox.Group>
  484. </Form.Item>}
  485. </Form.Item>
  486. {
  487. siteSet?.some((s: string) => s === 'SITE_SET_WECHAT') && <>
  488. <Form.Item label={<strong>微信公众号与小程序定投</strong>} name='wechatPositionType' style={wechatPositionType === '1' ? { marginBottom: 5 } : {}}>
  489. <Radio.Group >
  490. <Radio.Button value="0">不限</Radio.Button>
  491. <Radio.Button value="1">自定义</Radio.Button>
  492. </Radio.Group>
  493. </Form.Item>
  494. {wechatPositionType === '1' && <Form.Item style={{ marginLeft: 177 }} name='wechatPosition'>
  495. <Checkbox.Group options={sceneTagsList?.data?.WECHAT_POSITION?.map((item: { description: any; id: any; }) => ({ label: item.description, value: item.id }))} />
  496. </Form.Item>}
  497. <Form.Item label={<strong>微信公众号与小程序场景</strong>} name='wechatSceneType' style={wechatSceneType === '1' ? { marginBottom: 5 } : {}} >
  498. <Radio.Group >
  499. <Radio.Button value="0">不限</Radio.Button>
  500. <Radio.Button value="1">自定义</Radio.Button>
  501. </Radio.Group>
  502. </Form.Item>
  503. {wechatSceneType === '1' && <>
  504. <p style={{ marginBottom: 5, marginLeft: 177 }}><strong style={{ marginRight: 20 }}>公众号媒体类型</strong></p>
  505. <Form.Item style={{ marginLeft: 177 }} name='officialAccountMediaCategory'>
  506. <Checkbox.Group options={sceneTagsList?.data?.OFFICIAL_ACCOUNT_MEDIA_CATEGORY?.filter((i: { description: string; }) => i.description !== '不限')?.map((item: { description: any; id: any; }) => ({ label: item.description, value: item.id }))} />
  507. </Form.Item>
  508. <p style={{ marginBottom: 5, marginLeft: 177 }}><strong style={{ marginRight: 20 }}>小程序小游戏流量类型</strong></p>
  509. <Form.Item style={{ marginLeft: 177 }} name='miniProgramAndMiniGame'>
  510. <Checkbox.Group options={sceneTagsList?.data?.MINI_PROGRAM_AND_MINI_GAME?.filter((i: { description: string; }) => i.description !== '不限')?.map((item: { description: any; id: any; }) => ({ label: item.description, value: item.id }))} />
  511. </Form.Item>
  512. <p style={{ marginBottom: 5, marginLeft: 177 }}><strong style={{ marginRight: 20 }}>订单详情页消费场景</strong></p>
  513. <Form.Item style={{ marginLeft: 177 }} name='payScene'>
  514. <Checkbox.Group options={sceneTagsList?.data?.PAY_SCENE?.filter((i: { description: string; }) => i.description !== '不限')?.map((item: { description: any; id: any; }) => ({ label: item.description, value: item.id }))} />
  515. </Form.Item>
  516. </>}
  517. </>
  518. }
  519. {/* ============================================================排期与出价============================================================= */}
  520. <Divider orientation='center'>排期与出价</Divider>
  521. <Form.Item label={<strong>投放日期</strong>} name='dateType'>
  522. <Radio.Group onChange={(e) => {
  523. if (e.target.value === "1") {
  524. form.setFieldsValue({ date: [moment().startOf('day').add(2, 'M'), moment().startOf('day').add(5, 'M')] })
  525. }
  526. if (e.target.value === "2") {
  527. form.setFieldsValue({ date: moment().startOf('day').add(2, 'M') })
  528. }
  529. }}>
  530. <Radio.Button value="1">选择开始与结束日期</Radio.Button>
  531. <Radio.Button value="2">长期投放</Radio.Button>
  532. </Radio.Group>
  533. </Form.Item>
  534. {/* 投放日期的不同展示不同的日期选择 */}
  535. {
  536. dateType === '1' ? <Form.Item name='date' rules={[{ required: true, message: '请选择日期' }]}>
  537. <RangePicker style={{ marginLeft: 177 }} disabledDate={disabledDate}></RangePicker>
  538. </Form.Item> : <Form.Item name='date' style={{ marginLeft: 177 }} rules={[{ required: true, message: '请选择日期' }]}>
  539. <DatePickers disabledDate={disabledDate} />
  540. </Form.Item>
  541. }
  542. <Form.Item label={<strong>投放时段</strong>}>
  543. <Space>
  544. <Radio.Group name='timeSeries' defaultValue='1'>
  545. <Radio.Button value={'1'}>全天投放</Radio.Button>
  546. </Radio.Group>
  547. <Checkbox.Group options={[{ label: '指定首日开始投放时间', value: '1' }]} onChange={(checkedValue) => {
  548. setState({ ...state, isShowTime: checkedValue })
  549. }
  550. } value={state.isShowTime} />
  551. </Space>
  552. {state?.isShowTime?.length > 0 && <Form.Item name='firstDayBeginTime' noStyle rules={[{ required: true, message: '请选择时间' }]}>
  553. <TimePicker />
  554. </Form.Item>}
  555. </Form.Item>
  556. <Form.Item label={<strong>出价方式<Tooltip title='出价方式不同将影响自定义人群,行为兴趣意向等某些功能无法使用'><ExclamationCircleOutlined style={{ color: '#e91e63', marginLeft: 5 }} /></Tooltip></strong>} name='bidMode' rules={[{ required: true, message: '请选择出价方式' }]}>
  557. <Radio.Group onChange={(e) => {
  558. console.log(e.target.value);
  559. if (e.target.value === "BID_MODE_CPM" || e.target.value === "BID_MODE_CPC") {
  560. form.setFieldsValue({
  561. optimizationGoal: null,
  562. smartBidType: null,
  563. // bidAmount:null,
  564. bidStrategy: null,
  565. autoAcquisitionEnabled: false,
  566. autoAcquisitionBudget: null,
  567. dailyBudget: null,
  568. })
  569. } else {
  570. form.setFieldsValue({
  571. optimizationGoal: "OPTIMIZATIONGOAL_ECOMMERCE_ORDER",
  572. smartBidType: "SMART_BID_TYPE_CUSTOM",
  573. bidAmount: '1000',
  574. bidStrategy: "BID_STRATEGY_TARGET_COST",
  575. autoAcquisitionEnabled: false,
  576. autoAcquisitionBudget: null,
  577. dailyBudget: null,
  578. })
  579. }
  580. }}>
  581. {
  582. Object.keys(BidModeEnum).filter(key => { if (siteSet?.some((name: string) => name === "SITE_SET_MOMENTS")) { return key === 'BID_MODE_OCPM' || key === 'BID_MODE_CPM' } else { return true } })?.map(key => {
  583. return <Radio.Button value={key} key={key} >{BidModeEnum[key]}</Radio.Button>
  584. })
  585. }
  586. </Radio.Group>
  587. </Form.Item>
  588. {/* 出价方式为OCPM才展示 */}
  589. {
  590. (bidMode === 'BID_MODE_OCPM' || bidMode === 'BID_MODE_OCPC') && <>
  591. <Form.Item label={<strong>优化目标</strong>} name='optimizationGoal' rules={[{ required: true, message: '请选择优化目标' }]}>
  592. <Select style={{ width: 300 }} showSearch filterOption={(input, option) =>
  593. (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
  594. } allowClear>
  595. {
  596. // Object.keys(OptimizationGoalEnum).map(key => {
  597. // return <Select.Option value={key} key={key}>{OptimizationGoalEnum[key]}</Select.Option>
  598. // })
  599. ajax?.data?.optimizationGoalPermissionList.map((key: string) => {
  600. return <Select.Option value={key} key={key}>{OptimizationGoalEnum[key]}</Select.Option>
  601. })
  602. }
  603. </Select>
  604. </Form.Item>
  605. <Form.Item label={<strong>出价类型</strong>} name='smartBidType' rules={[{ required: true, message: '请选择出价类型' }]}>
  606. <Radio.Group >
  607. <Radio.Button value="SMART_BID_TYPE_CUSTOM">手动出价</Radio.Button>
  608. <Radio.Button value="SMART_BID_TYPE_SYSTEMATIC">自动出价</Radio.Button>
  609. </Radio.Group>
  610. </Form.Item>
  611. <Form.Item label={<strong>出价策略</strong>} name='bidStrategy' rules={[{ required: true, message: '请选择出价策略' }]}>
  612. <Radio.Group >
  613. {
  614. Object.keys(BidStrategyEnum).map(key => {
  615. return <Radio.Button value={key} key={key} disabled={smartBidType === 'SMART_BID_TYPE_SYSTEMATIC' && key === 'BID_STRATEGY_PRIORITY_CAP_COST'}> {BidStrategyEnum[key]}</Radio.Button>
  616. })
  617. }
  618. </Radio.Group>
  619. </Form.Item>
  620. </>
  621. }
  622. {/* 出价类型为手动出价才展示 */}
  623. {smartBidType !== 'SMART_BID_TYPE_SYSTEMATIC' && <>
  624. <Form.Item label={<strong>出价</strong>} name='bidAmount' rules={[{ required: true, message: '请输入价格' }]}>
  625. <Input placeholder={`输入价格 元/${bidMode === 'BID_MODE_CPM' ? '千次曝光' : bidMode === 'BID_MODE_CPC' ? '点击' : OptimizationGoalEnum[optimizationGoal]}`} style={{ width: 300 }} />
  626. </Form.Item>
  627. {(bidMode === 'BID_MODE_OCPM' || bidMode === 'BID_MODE_OCPC') && <>
  628. {/* 当版位选择大于1时才出现 */}
  629. {siteSet?.length > 1 && <Form.Item label={<strong>分版位出价</strong>} name='bidAmountAdjustmentEnabled' valuePropName="checked">
  630. <Switch checkedChildren="开启" unCheckedChildren="关闭" />
  631. </Form.Item>}
  632. {bidAmountAdjustmentEnabled && <Form.Item
  633. label={<strong>分版位出价</strong>}
  634. name='siteSetPackage'
  635. rules={[{ required: bidAmountAdjustmentEnabled ? true : false, message: '请设置系数' }]}
  636. >
  637. <BidAdjustment bidAmount={bidAmount} deepConversionType='BID_MODE' goal={goal}>
  638. <AdPositionList value={siteSetPackage} onChange={(data) => {
  639. form.setFieldsValue({
  640. siteSetPackage: data
  641. })
  642. }} />
  643. </BidAdjustment>
  644. </Form.Item>}
  645. <Form.Item label={<strong>一键起量</strong>} name='autoAcquisitionEnabled' valuePropName="checked">
  646. <Switch checkedChildren="开启" unCheckedChildren="关闭" />
  647. </Form.Item>
  648. {/* 一键起量开启时才出现 */}
  649. {autoAcquisitionEnabled && <Form.Item label={<strong>起量预算</strong>} name='autoAcquisitionBudget' rules={[{ required: true, message: '请输入起量预算' }]}>
  650. <Input placeholder='起量预算' style={{ width: 300 }} />
  651. </Form.Item>}
  652. {/* 深度优化 */}
  653. {(behaviorList?.length > 0 || worthList?.length > 0) && <Form.Item label={<strong>深度转化优化</strong>} name='depthConversionEnabled' valuePropName="checked">
  654. <Switch checkedChildren="开启" unCheckedChildren="关闭" onChange={(e) => {
  655. if (e) {
  656. form.setFieldsValue({
  657. optimizationMode: 'DEEP_CONVERSION_TARGET',
  658. deepConversionType: behaviorList?.length > 0 ? 'DEEP_CONVERSION_BEHAVIOR' : worthList?.length > 0 ? 'DEEP_CONVERSION_WORTH' : ''
  659. })
  660. }
  661. }} />
  662. </Form.Item>}
  663. {depthConversionEnabled && <div style={{ backgroundColor: 'rgb(247, 248, 250)', padding: '18px 0 3px', marginBottom: 10, borderRadius: 8 }}>
  664. <Form.Item label={<strong>深度优化方式</strong>} name='optimizationMode' rules={[{ required: true, message: '请选择深度优化方式' }]}>
  665. <Radio.Group>
  666. <Radio.Button value="DEEP_CONVERSION_TARGET">深度目标优化</Radio.Button>
  667. </Radio.Group>
  668. </Form.Item>
  669. <Form.Item label={<strong>深度优化类型</strong>} name='deepConversionType' rules={[{ required: true, message: '请选择深度优化类型' }]}>
  670. <Radio.Group onChange={() => {
  671. form.setFieldsValue({
  672. goal: undefined,
  673. deepBidAmount: undefined
  674. })
  675. }}>
  676. {behaviorList?.length > 0 && <Radio.Button value="DEEP_CONVERSION_BEHAVIOR">优化转化行为</Radio.Button>}
  677. {worthList?.length > 0 && <Radio.Button value="DEEP_CONVERSION_WORTH">优化 ROI</Radio.Button>}
  678. </Radio.Group>
  679. </Form.Item>
  680. <Form.Item label={<strong>深度优化目标</strong>} name='goal' rules={[{ required: true, message: '请选择深度优化目标' }]}>
  681. <Select style={{ width: 380 }} placeholder='请选择'>
  682. {deepConversionType === 'DEEP_CONVERSION_BEHAVIOR' ? Object.keys(OptimizationGoalEnum).filter(key => behaviorList?.includes(key)).map(key => <Select.Option value={key} key={key}>{OptimizationGoalEnum[key]}</Select.Option>) : deepConversionType === 'DEEP_CONVERSION_WORTH' ?
  683. Object.keys(GoalRoasEnum).filter(key => worthList?.includes(key)).map(key => <Select.Option value={key} key={key}>{GoalRoasEnum[key]}</Select.Option>) : null}
  684. </Select>
  685. </Form.Item>
  686. {deepConversionType === 'DEEP_CONVERSION_BEHAVIOR' ? <>
  687. <Form.Item label={<strong>深度目标出价</strong>} name='deepBidAmount' rules={[{ required: true, message: '请输入深度目标出价' }]}>
  688. <Input style={{ width: 380 }} suffix={`元/${OptimizationGoalEnum[goal] || '优化目标'}`} placeholder={`请输入深度目标出价,范围0.1~10000`} />
  689. </Form.Item>
  690. </> :
  691. deepConversionType === 'DEEP_CONVERSION_WORTH' ? <>
  692. <Form.Item label={<strong>期望ROI</strong>} name='deepBidAmount' rules={[{ required: true, message: '请输入期望ROI' }]}>
  693. <Input style={{ width: 380 }} placeholder={`期望ROI目标范围0.001~1000,输入0.05,表示ROI目标为5%`} />
  694. </Form.Item>
  695. </> : null}
  696. {siteSet?.length > 1 && <Form.Item label={<strong>开启分版位深度目标出价</strong>} name='bidAdjustmentEnabled' valuePropName="checked">
  697. <Switch checkedChildren="开启" unCheckedChildren="关闭" />
  698. </Form.Item>}
  699. {bidAdjustmentEnabled && <Form.Item
  700. label={<strong>分版位深度目标出价</strong>}
  701. name='siteSetPackage'
  702. rules={[{ required: bidAdjustmentEnabled ? true : false, message: '请设置系数' }]}
  703. >
  704. <BidAdjustment bidAmount={deepBidAmount} deepConversionType={deepConversionType} goal={goal}>
  705. {!(bidAmountAdjustmentEnabled) ? <AdPositionList value={siteSetPackage} onChange={(data) => {
  706. form.setFieldsValue({
  707. siteSetPackage: data
  708. })
  709. }} /> : <></>}
  710. </BidAdjustment>
  711. </Form.Item>}
  712. </div>}
  713. </>}
  714. </>}
  715. <Form.Item label={<strong>广告日预算</strong>} name='dailyBudget'>
  716. <Input placeholder='不填默认为不限' style={{ width: 300 }} />
  717. </Form.Item>
  718. <Form.Item label={<strong>广告状态</strong>} name="configuredStatus" rules={[{ required: true, message: '请选择广告状态' }]}>
  719. <Select placeholder="选择广告状态" style={{ width: 300 }}>
  720. {Object.keys(AdStatus).map(key => {
  721. return <Select.Option value={key} key={key}>{AdStatus[key]}</Select.Option>
  722. })}
  723. </Select>
  724. </Form.Item>
  725. </Form>
  726. </Modal >
  727. }
  728. export default LeadAdModal