leadAd.tsx 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761
  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. useEffect(() => {
  193. if (depthConversionEnabled) {
  194. form.setFieldsValue({
  195. optimizationMode: 'DEEP_CONVERSION_TARGET',
  196. deepConversionType: behaviorList?.length > 0 ? 'DEEP_CONVERSION_BEHAVIOR' : worthList?.length > 0 ? 'DEEP_CONVERSION_WORTH' : ''
  197. })
  198. }
  199. }, [depthConversionEnabled, behaviorList, worthList])
  200. // 数据回填
  201. useEffect(() => {
  202. if (dataInfo) {
  203. let formData: any = {
  204. adgroupName: dataInfo?.adgroupName,//广告名称
  205. promotedObjectType: dataInfo?.promotedObjectType,//推广目标
  206. siteSet: dataInfo?.siteSet,//广告版位
  207. autoAcquisitionEnabled: dataInfo?.autoAcquisitionEnabled,//一键起量
  208. bidAmount: dataInfo?.bidAmount,//出价
  209. smartBidType: dataInfo?.smartBidType,//出价类型
  210. bidStrategy: dataInfo?.bidStrategy,//出价策略
  211. bidMode: dataInfo?.bidMode,//出价方式
  212. optimizationGoal: dataInfo?.optimizationGoal,//优化目标
  213. dateType: dataInfo?.endDate ? '1' : '2',//投放日期
  214. dailyBudget: dataInfo?.dailyBudget,//广告日预算
  215. date: dataInfo?.endDate ? [moment(dataInfo?.beginDate), moment(dataInfo?.endDate)] : moment(dataInfo?.beginDate),//日期
  216. autoAcquisitionBudget: dataInfo?.autoAcquisitionBudget,//起量预算
  217. wechatPositionType: dataInfo?.sceneSpec?.wechatPosition ? '1' : '0',//微信公众号与小程序定投
  218. wechatPosition: dataInfo?.sceneSpec?.wechatPosition,//微信公众号与小程序定投
  219. wechatSceneType: dataInfo?.sceneSpec?.wechatScene?.officialAccountMediaCategory || dataInfo?.sceneSpec?.wechatScene?.miniProgramAndMiniGame || dataInfo?.sceneSpec?.wechatScene?.payScene ? '1' : '0',//微信公众号与小程序场景
  220. officialAccountMediaCategory: dataInfo?.sceneSpec?.wechatScene?.officialAccountMediaCategory,//公众号媒体类型
  221. miniProgramAndMiniGame: dataInfo?.sceneSpec?.wechatScene?.miniProgramAndMiniGame,//小程序小游戏流量类型
  222. payScene: dataInfo?.sceneSpec?.wechatScene?.payScene,//订单详情页消费场景
  223. firstDayBeginTime: dataInfo?.firstDayBeginTime ? moment(`${dataInfo?.beginDate} ${dataInfo?.firstDayBeginTime}`) : undefined,//首日开始时间
  224. configuredStatus: dataInfo?.configuredStatus || 'AD_STATUS_SUSPEND',//广告启停
  225. }
  226. Object.keys(dataInfo).forEach(key => {
  227. switch (key) {
  228. case 'bidAdjustment': // 处理分版位出价
  229. if (dataInfo[key]?.siteSetPackage && dataInfo[key]?.siteSetPackage?.length > 0) {
  230. let siteSetPackage: SiteSetPackageDataProps[] = dataInfo[key].siteSetPackage
  231. if (siteSetPackage.some(item => item.bidCoefficient)) { // 判断出价是否开启了分版位
  232. formData.bidAmountAdjustmentEnabled = true
  233. } else {
  234. siteSetPackage = siteSetPackage.map(item => ({ ...item, bidCoefficient: 1 }))
  235. }
  236. if (siteSetPackage.some(item => item.deepBidCoefficient)) { // 判断深度出价是否开启了分版位
  237. formData.bidAdjustmentEnabled = true
  238. } else {
  239. siteSetPackage = siteSetPackage.map(item => ({ ...item, deepBidCoefficient: 1 }))
  240. }
  241. formData.siteSetPackage = siteSetPackage
  242. }
  243. break
  244. case 'deepConversionSpec': // 处理深度优化
  245. if (dataInfo[key]?.deepConversionType === 'DEEP_CONVERSION_WORTH') {
  246. formData = {
  247. ...formData,
  248. optimizationMode: 'DEEP_CONVERSION_TARGET',
  249. depthConversionEnabled: true,
  250. deepConversionType: dataInfo[key]?.deepConversionType,
  251. goal: dataInfo[key]?.deepConversionWorthSpec?.goal,
  252. deepBidAmount: dataInfo[key]?.deepConversionWorthSpec?.expectedRoi,
  253. }
  254. } else if (dataInfo[key]?.deepConversionType === 'DEEP_CONVERSION_BEHAVIOR') {
  255. formData = {
  256. ...formData,
  257. optimizationMode: 'DEEP_CONVERSION_TARGET',
  258. depthConversionEnabled: true,
  259. deepConversionType: dataInfo[key]?.deepConversionType,
  260. goal: dataInfo[key]?.deepConversionBehaviorSpec?.goal,
  261. deepBidAmount: dataInfo[key]?.deepConversionBehaviorSpec?.bidAmount / 100,
  262. }
  263. }
  264. break
  265. }
  266. })
  267. form.setFieldsValue(formData)
  268. if (dataInfo?.firstDayBeginTime) {//存在首日开始时间,选中开关
  269. setState({ ...state, isShowTime: ['1'] })
  270. }
  271. } else {
  272. form.setFieldsValue({
  273. adgroupName: '广告_销售线索',
  274. date: moment().startOf('day').add(2, 'M'),
  275. optimizationGoal: "OPTIMIZATIONGOAL_ECOMMERCE_ORDER",
  276. bidStrategy: "BID_STRATEGY_TARGET_COST",
  277. bidAmount: '1000'
  278. })
  279. }
  280. }, [dataInfo])
  281. // 出价方式改变清空某些数据
  282. const bidModeChange = useCallback((props) => {
  283. form.setFieldsValue({
  284. ...props,
  285. optimizationGoal: 'OPTIMIZATIONGOAL_ECOMMERCE_ORDER',
  286. smartBidType: 'SMART_BID_TYPE_CUSTOM',
  287. // bidAmount:null,
  288. bidStrategy: 'BID_STRATEGY_TARGET_COST',
  289. autoAcquisitionEnabled: false,
  290. autoAcquisitionBudget: null,
  291. dailyBudget: null,
  292. })
  293. }, [])
  294. // 出价和版位改变时查询
  295. useEffect(() => {
  296. if (bidMode && siteSet && siteSet?.length > 0) {
  297. let obj: any = { siteSet, promotedObjectType: queryForm.promotedObjectType }
  298. if (bidMode === 'BID_MODE_OCPC' || bidMode === 'BID_MODE_OCPM') {
  299. obj = { ...obj, bidMode }
  300. }
  301. ajax.run(obj).then((res: any) => {
  302. console.log(res)
  303. })
  304. }
  305. }, [bidMode, siteSet])
  306. // 处理深度转化优化
  307. useEffect(() => {
  308. if (optimizationGoal && ajax?.data) {
  309. let { deepBehaviorOptimizationGoalPermissionList, deepWorthOptimizationGoalPermissionList } = ajax?.data
  310. // deepBehaviorOptimizationGoalPermissionList 优化转化行为
  311. // deepWorthOptimizationGoalPermissionList 优化ROI
  312. let behavior = deepBehaviorOptimizationGoalPermissionList?.find((item: { optimizationGoal: string }) => item.optimizationGoal === optimizationGoal)
  313. let worth = deepWorthOptimizationGoalPermissionList?.find((item: { optimizationGoal: string }) => item.optimizationGoal === optimizationGoal)
  314. setBehaviorList(behavior?.deepBehaviorOptimizationGoalList || [])
  315. setWorthList(worth?.deepWorthOptimizationGoalList || [])
  316. }
  317. }, [optimizationGoal, ajax?.data])
  318. /**处理分版位数据 */
  319. const setSiteSetHandle = (siteSet: string[]) => {
  320. let data: SiteSetPackageDataProps[] = []
  321. if (siteSet && siteSet?.length > 0) {
  322. let data1: string[] = []
  323. let data2: string[] = []
  324. let data3: string[] = []
  325. let data4: string[] = []
  326. siteSet.forEach((item: string) => {
  327. let siteData = siteSetData?.find(item1 => item1.siteSet === item)
  328. if (siteData) {
  329. switch (siteData.type) {
  330. case 1:
  331. data1.push(item)
  332. break
  333. case 2:
  334. data2.push(item)
  335. break
  336. case 3:
  337. data3.push(item)
  338. break
  339. case 4:
  340. data4.push(item)
  341. break
  342. }
  343. }
  344. });
  345. let newSiteSetPackageData: SiteSetPackageDataProps[] = []
  346. if (data1?.length > 0) {
  347. newSiteSetPackageData.push({ siteSet: data1, bidCoefficient: 1, deepBidCoefficient: 1 })
  348. }
  349. if (data2?.length > 0) {
  350. newSiteSetPackageData.push({ siteSet: data2, bidCoefficient: 1, deepBidCoefficient: 1 })
  351. }
  352. if (data3?.length > 0) {
  353. newSiteSetPackageData.push({ siteSet: data3, bidCoefficient: 1, deepBidCoefficient: 1 })
  354. }
  355. if (data4?.length > 0) {
  356. newSiteSetPackageData.push({ siteSet: data4, bidCoefficient: 1, deepBidCoefficient: 1 })
  357. }
  358. data = newSiteSetPackageData
  359. } else {
  360. data = []
  361. }
  362. form.setFieldsValue({
  363. siteSetPackage: data
  364. })
  365. }
  366. /** 禁止选择以前时间 */
  367. const disabledDate: RangePickerProps['disabledDate'] = current => {
  368. // Can not select days before today and today
  369. return current && current < moment().startOf('day');
  370. };
  371. return <Modal
  372. visible={visible}
  373. title={type === 'add' ? '新建广告' : type === 'look' ? '广告详情' : '编辑广告'}
  374. onCancel={() => { PupFn({ visible: false, dataInfo: null, type: 'add' }) }}
  375. onOk={type === 'look' ? () => message.warning('详情无法改动内容') : handleOk}
  376. width={900}
  377. confirmLoading={createSysAdgroup?.loading}
  378. footer={<Space>
  379. <Button onClick={() => { PupFn({ visible: false, dataInfo: null, type: 'add' }) }}>取消</Button>
  380. <Button type='primary' onClick={handleOk}>确定</Button>
  381. {<Checkbox checked={template_checked} onChange={(e) => {
  382. let checked = e.target.checked
  383. settemplate_checked(checked)
  384. }}>存为模板</Checkbox>}
  385. </Space>}
  386. {...arg}
  387. >
  388. <Form
  389. form={form}
  390. labelCol={{ span: 5 }}
  391. className='ad_form_style'
  392. initialValues={
  393. {
  394. promotedObjectType: queryForm.promotedObjectType,
  395. siteSet: ['SITE_SET_MOMENTS', 'SITE_SET_WECHAT', 'SITE_SET_WECHAT_PLUGIN'],
  396. bidMode: 'BID_MODE_OCPM',
  397. automaticSiteEnabled: false,
  398. dateType: '2',
  399. bidStrategy: 'BID_STRATEGY_AVERAGE_COST',
  400. timeSeries: '1',
  401. smartBidType: 'SMART_BID_TYPE_CUSTOM',
  402. autoAcquisitionEnabled: false,
  403. wechatSceneType: '0',
  404. wechatPositionType: '0',
  405. // optimizationGoal: 'OPTIMIZATIONGOAL_ECOMMERCE_ORDER',
  406. configuredStatus: 'AD_STATUS_SUSPEND',
  407. siteSetPackage: [{ siteSet: ["SITE_SET_MOMENTS"], bidCoefficient: 1, deepBidCoefficient: 1 }, { siteSet: ["SITE_SET_WECHAT"], bidCoefficient: 1, deepBidCoefficient: 1 }]
  408. }
  409. }
  410. >
  411. {/* ============================================================基本信息============================================================= */}
  412. <Divider orientation='center'>基本信息</Divider>
  413. <Form.Item
  414. label={<strong>广告名称</strong>}
  415. name='adgroupName'
  416. tooltip="下标、日期时分秒、广告账户创建时默认自带"
  417. rules={[
  418. { required: true, message: '请输入广告名称!' },
  419. {
  420. required: true, message: '广告名称不能包含特殊字符:< > & ‘ ” / 以及TAB、换行、回车键,请修改', validator(rule, value, callback) {
  421. let reg = /[&‘’“”/\n\t\f]/ig
  422. if (value && reg.test(value)) {
  423. return Promise.reject()
  424. }
  425. return Promise.resolve()
  426. },
  427. },
  428. // {
  429. // required: true, message: '您可能使用了不支持的通配符,请您确认后再保存', validator(rule, value, callback) {
  430. // let reg = /(<账号备注>)|(<商品ID>)|(<账号ID>)|(<日期>)|(<时分秒>)|(<定向名>)|(<素材名>)|(<落地页名>|(<优化目标>)/ig
  431. // let reg1 = /[<>]/ig
  432. // if (value && (reg1.test(value))) {
  433. // if (reg.test(value)) {
  434. // return Promise.resolve()
  435. // }
  436. // return Promise.reject()
  437. // }
  438. // return Promise.resolve()
  439. // },
  440. // },
  441. {
  442. required: true, message: '请确保广告名称长度不超过60个字(1个汉字等于2个字符)', validator(rule, value, callback) {
  443. if (value && txtLength(value) > 60) {
  444. return Promise.reject()
  445. }
  446. return Promise.resolve()
  447. },
  448. }
  449. ]}
  450. >
  451. <InputName placeholder='广告名称' style={{ width: 400 }} length={60} />
  452. </Form.Item>
  453. <Form.Item label={<strong>广告版位</strong>}>
  454. <Form.Item name='automaticSiteEnabled'>
  455. <Radio.Group buttonStyle="solid">
  456. <Radio.Button value={true} disabled={queryForm.promotedObjectType === 'PROMOTED_OBJECT_TYPE_WECHAT_OFFICIAL_ACCOUNT'}>自动版位</Radio.Button>
  457. <Radio.Button value={false}>选择特定版位</Radio.Button>
  458. </Radio.Group>
  459. </Form.Item>
  460. {!automaticSiteEnabled && <Form.Item name='siteSet' noStyle rules={[{ required: true, message: '请输入选择广告版位!' }]}>
  461. <Checkbox.Group style={{ width: '100%' }} onChange={(e) => { bidModeChange({ bidMode: 'BID_MODE_OCPM' }); setSiteSetHandle(e as string[]) }}>
  462. <Row>
  463. {/* <Col span={4}>
  464. <Checkbox value="SITE_SET_CHANNELS">微信视频号</Checkbox>
  465. </Col> */}
  466. <Col span={4}>
  467. <Checkbox value="SITE_SET_MOMENTS">微信朋友圈</Checkbox>
  468. </Col>
  469. <Col span={4}>
  470. <Checkbox value="SITE_SET_MOBILE_UNION">优量汇</Checkbox>
  471. </Col>
  472. <Col span={4}>
  473. <Checkbox value="SITE_SET_TENCENT_NEWS">腾讯新闻</Checkbox>
  474. </Col>
  475. <Col span={4}>
  476. <Checkbox value="SITE_SET_TENCENT_VIDEO">腾讯视频</Checkbox>
  477. </Col>
  478. <Col span={4}>
  479. <Checkbox value="SITE_SET_KANDIAN">QQ 浏览器</Checkbox>
  480. </Col>
  481. <Col span={6}>
  482. <Checkbox value="SITE_SET_QQ_MUSIC_GAME">QQ、腾讯音乐及游戏</Checkbox>
  483. </Col>
  484. <Col span={6}>
  485. <Checkbox value="SITE_SET_WECHAT">微信公众号与小程序</Checkbox>
  486. </Col>
  487. <Col span={5}>
  488. <Checkbox value="SITE_SET_WECHAT_PLUGIN">微信新闻插件</Checkbox>
  489. </Col>
  490. </Row>
  491. </Checkbox.Group>
  492. </Form.Item>}
  493. </Form.Item>
  494. {
  495. siteSet?.some((s: string) => s === 'SITE_SET_WECHAT') && <>
  496. <Form.Item label={<strong>微信公众号与小程序定投</strong>} name='wechatPositionType' style={wechatPositionType === '1' ? { marginBottom: 5 } : {}}>
  497. <Radio.Group >
  498. <Radio.Button value="0">不限</Radio.Button>
  499. <Radio.Button value="1">自定义</Radio.Button>
  500. </Radio.Group>
  501. </Form.Item>
  502. {wechatPositionType === '1' && <Form.Item style={{ marginLeft: 177 }} name='wechatPosition'>
  503. <Checkbox.Group options={sceneTagsList?.data?.WECHAT_POSITION?.map((item: { description: any; id: any; }) => ({ label: item.description, value: item.id }))} />
  504. </Form.Item>}
  505. <Form.Item label={<strong>微信公众号与小程序场景</strong>} name='wechatSceneType' style={wechatSceneType === '1' ? { marginBottom: 5 } : {}} >
  506. <Radio.Group >
  507. <Radio.Button value="0">不限</Radio.Button>
  508. <Radio.Button value="1">自定义</Radio.Button>
  509. </Radio.Group>
  510. </Form.Item>
  511. {wechatSceneType === '1' && <>
  512. <p style={{ marginBottom: 5, marginLeft: 177 }}><strong style={{ marginRight: 20 }}>公众号媒体类型</strong></p>
  513. <Form.Item style={{ marginLeft: 177 }} name='officialAccountMediaCategory'>
  514. <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 }))} />
  515. </Form.Item>
  516. <p style={{ marginBottom: 5, marginLeft: 177 }}><strong style={{ marginRight: 20 }}>小程序小游戏流量类型</strong></p>
  517. <Form.Item style={{ marginLeft: 177 }} name='miniProgramAndMiniGame'>
  518. <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 }))} />
  519. </Form.Item>
  520. <p style={{ marginBottom: 5, marginLeft: 177 }}><strong style={{ marginRight: 20 }}>订单详情页消费场景</strong></p>
  521. <Form.Item style={{ marginLeft: 177 }} name='payScene'>
  522. <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 }))} />
  523. </Form.Item>
  524. </>}
  525. </>
  526. }
  527. {/* ============================================================排期与出价============================================================= */}
  528. <Divider orientation='center'>排期与出价</Divider>
  529. <Form.Item label={<strong>投放日期</strong>} name='dateType'>
  530. <Radio.Group onChange={(e) => {
  531. if (e.target.value === "1") {
  532. form.setFieldsValue({ date: [moment().startOf('day').add(2, 'M'), moment().startOf('day').add(5, 'M')] })
  533. }
  534. if (e.target.value === "2") {
  535. form.setFieldsValue({ date: moment().startOf('day').add(2, 'M') })
  536. }
  537. }}>
  538. <Radio.Button value="1">选择开始与结束日期</Radio.Button>
  539. <Radio.Button value="2">长期投放</Radio.Button>
  540. </Radio.Group>
  541. </Form.Item>
  542. {/* 投放日期的不同展示不同的日期选择 */}
  543. {
  544. dateType === '1' ? <Form.Item name='date' rules={[{ required: true, message: '请选择日期' }]}>
  545. <RangePicker style={{ marginLeft: 177 }} disabledDate={disabledDate}></RangePicker>
  546. </Form.Item> : <Form.Item name='date' style={{ marginLeft: 177 }} rules={[{ required: true, message: '请选择日期' }]}>
  547. <DatePickers disabledDate={disabledDate} />
  548. </Form.Item>
  549. }
  550. <Form.Item label={<strong>投放时段</strong>}>
  551. <Space>
  552. <Radio.Group name='timeSeries' defaultValue='1'>
  553. <Radio.Button value={'1'}>全天投放</Radio.Button>
  554. </Radio.Group>
  555. <Checkbox.Group options={[{ label: '指定首日开始投放时间', value: '1' }]} onChange={(checkedValue) => {
  556. setState({ ...state, isShowTime: checkedValue })
  557. }
  558. } value={state.isShowTime} />
  559. </Space>
  560. {state?.isShowTime?.length > 0 && <Form.Item name='firstDayBeginTime' noStyle rules={[{ required: true, message: '请选择时间' }]}>
  561. <TimePicker />
  562. </Form.Item>}
  563. </Form.Item>
  564. <Form.Item label={<strong>出价方式<Tooltip title='出价方式不同将影响自定义人群,行为兴趣意向等某些功能无法使用'><ExclamationCircleOutlined style={{ color: '#e91e63', marginLeft: 5 }} /></Tooltip></strong>} name='bidMode' rules={[{ required: true, message: '请选择出价方式' }]}>
  565. <Radio.Group onChange={(e) => {
  566. console.log(e.target.value);
  567. if (e.target.value === "BID_MODE_CPM" || e.target.value === "BID_MODE_CPC") {
  568. form.setFieldsValue({
  569. optimizationGoal: null,
  570. smartBidType: null,
  571. // bidAmount:null,
  572. bidStrategy: null,
  573. autoAcquisitionEnabled: false,
  574. autoAcquisitionBudget: null,
  575. dailyBudget: null,
  576. })
  577. } else {
  578. form.setFieldsValue({
  579. optimizationGoal: "OPTIMIZATIONGOAL_ECOMMERCE_ORDER",
  580. smartBidType: "SMART_BID_TYPE_CUSTOM",
  581. bidAmount: '1000',
  582. bidStrategy: "BID_STRATEGY_TARGET_COST",
  583. autoAcquisitionEnabled: false,
  584. autoAcquisitionBudget: null,
  585. dailyBudget: null,
  586. })
  587. }
  588. }}>
  589. {
  590. 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 => {
  591. return <Radio.Button value={key} key={key} >{BidModeEnum[key]}</Radio.Button>
  592. })
  593. }
  594. </Radio.Group>
  595. </Form.Item>
  596. {/* 出价方式为OCPM才展示 */}
  597. {
  598. (bidMode === 'BID_MODE_OCPM' || bidMode === 'BID_MODE_OCPC') && <>
  599. <Form.Item label={<strong>优化目标</strong>} name='optimizationGoal' rules={[{ required: true, message: '请选择优化目标' }]}>
  600. <Select
  601. style={{ width: 300 }}
  602. showSearch
  603. filterOption={(input, option) =>
  604. (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
  605. }
  606. onChange={(e) => {
  607. if (e === 'OPTIMIZATIONGOAL_PAGE_SCAN_CODE') {
  608. form.setFieldsValue({ depthConversionEnabled: true })
  609. }
  610. }}
  611. allowClear
  612. >
  613. {
  614. // Object.keys(OptimizationGoalEnum).map(key => {
  615. // return <Select.Option value={key} key={key}>{OptimizationGoalEnum[key]}</Select.Option>
  616. // })
  617. ajax?.data?.optimizationGoalPermissionList.map((key: string) => {
  618. return <Select.Option value={key} key={key}>{OptimizationGoalEnum[key]}</Select.Option>
  619. })
  620. }
  621. </Select>
  622. </Form.Item>
  623. <Form.Item label={<strong>出价类型</strong>} name='smartBidType' rules={[{ required: true, message: '请选择出价类型' }]}>
  624. <Radio.Group >
  625. <Radio.Button value="SMART_BID_TYPE_CUSTOM">手动出价</Radio.Button>
  626. <Radio.Button value="SMART_BID_TYPE_SYSTEMATIC">自动出价</Radio.Button>
  627. </Radio.Group>
  628. </Form.Item>
  629. <Form.Item label={<strong>出价策略</strong>} name='bidStrategy' rules={[{ required: true, message: '请选择出价策略' }]}>
  630. <Radio.Group >
  631. {
  632. Object.keys(BidStrategyEnum).map(key => {
  633. return <Radio.Button value={key} key={key} disabled={smartBidType === 'SMART_BID_TYPE_SYSTEMATIC' && key === 'BID_STRATEGY_PRIORITY_CAP_COST'}> {BidStrategyEnum[key]}</Radio.Button>
  634. })
  635. }
  636. </Radio.Group>
  637. </Form.Item>
  638. </>
  639. }
  640. {/* 出价类型为手动出价才展示 */}
  641. {smartBidType !== 'SMART_BID_TYPE_SYSTEMATIC' && <>
  642. <Form.Item label={<strong>出价</strong>} name='bidAmount' rules={[{ required: true, message: '请输入价格' }]}>
  643. <Input placeholder={`输入价格 元/${bidMode === 'BID_MODE_CPM' ? '千次曝光' : bidMode === 'BID_MODE_CPC' ? '点击' : OptimizationGoalEnum[optimizationGoal]}`} style={{ width: 300 }} />
  644. </Form.Item>
  645. {(bidMode === 'BID_MODE_OCPM' || bidMode === 'BID_MODE_OCPC') && <>
  646. {/* 当版位选择大于1时才出现 */}
  647. {siteSet?.length > 1 && <Form.Item label={<strong>分版位出价</strong>} name='bidAmountAdjustmentEnabled' valuePropName="checked">
  648. <Switch checkedChildren="开启" unCheckedChildren="关闭" />
  649. </Form.Item>}
  650. {bidAmountAdjustmentEnabled && <Form.Item
  651. label={<strong>分版位出价</strong>}
  652. name='siteSetPackage'
  653. rules={[{ required: bidAmountAdjustmentEnabled ? true : false, message: '请设置系数' }]}
  654. >
  655. <BidAdjustment bidAmount={bidAmount} deepConversionType='BID_MODE' goal={goal}>
  656. <AdPositionList value={siteSetPackage} onChange={(data) => {
  657. form.setFieldsValue({
  658. siteSetPackage: data
  659. })
  660. }} />
  661. </BidAdjustment>
  662. </Form.Item>}
  663. <Form.Item label={<strong>一键起量</strong>} name='autoAcquisitionEnabled' valuePropName="checked">
  664. <Switch checkedChildren="开启" unCheckedChildren="关闭" />
  665. </Form.Item>
  666. {/* 一键起量开启时才出现 */}
  667. {autoAcquisitionEnabled && <Form.Item label={<strong>起量预算</strong>} name='autoAcquisitionBudget' rules={[{ required: true, message: '请输入起量预算' }]}>
  668. <Input placeholder='起量预算' style={{ width: 300 }} />
  669. </Form.Item>}
  670. {/* 深度优化 */}
  671. {(behaviorList?.length > 0 || worthList?.length > 0) && <Form.Item label={<strong>深度转化优化</strong>} name='depthConversionEnabled' valuePropName="checked">
  672. <Switch checkedChildren="开启" unCheckedChildren="关闭" onChange={(e) => {
  673. if (e) {
  674. form.setFieldsValue({
  675. optimizationMode: 'DEEP_CONVERSION_TARGET',
  676. deepConversionType: behaviorList?.length > 0 ? 'DEEP_CONVERSION_BEHAVIOR' : worthList?.length > 0 ? 'DEEP_CONVERSION_WORTH' : ''
  677. })
  678. }
  679. }} />
  680. </Form.Item>}
  681. {depthConversionEnabled && <div style={{ backgroundColor: 'rgb(247, 248, 250)', padding: '18px 0 3px', marginBottom: 10, borderRadius: 8 }}>
  682. <Form.Item label={<strong>深度优化方式</strong>} name='optimizationMode' rules={[{ required: true, message: '请选择深度优化方式' }]}>
  683. <Radio.Group>
  684. <Radio.Button value="DEEP_CONVERSION_TARGET">深度目标优化</Radio.Button>
  685. </Radio.Group>
  686. </Form.Item>
  687. <Form.Item label={<strong>深度优化类型</strong>} name='deepConversionType' rules={[{ required: true, message: '请选择深度优化类型' }]}>
  688. <Radio.Group onChange={() => {
  689. form.setFieldsValue({
  690. goal: undefined,
  691. deepBidAmount: undefined
  692. })
  693. }}>
  694. {behaviorList?.length > 0 && <Radio.Button value="DEEP_CONVERSION_BEHAVIOR">优化转化行为</Radio.Button>}
  695. {worthList?.length > 0 && <Radio.Button value="DEEP_CONVERSION_WORTH">优化 ROI</Radio.Button>}
  696. </Radio.Group>
  697. </Form.Item>
  698. <Form.Item label={<strong>深度优化目标</strong>} name='goal' rules={[{ required: true, message: '请选择深度优化目标' }]}>
  699. <Select style={{ width: 380 }} placeholder='请选择'>
  700. {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' ?
  701. Object.keys(GoalRoasEnum).filter(key => worthList?.includes(key)).map(key => <Select.Option value={key} key={key}>{GoalRoasEnum[key]}</Select.Option>) : null}
  702. </Select>
  703. </Form.Item>
  704. {deepConversionType === 'DEEP_CONVERSION_BEHAVIOR' ? <>
  705. <Form.Item label={<strong>深度目标出价</strong>} name='deepBidAmount' rules={[{ required: true, message: '请输入深度目标出价' }]}>
  706. <Input style={{ width: 380 }} suffix={`元/${OptimizationGoalEnum[goal] || '优化目标'}`} placeholder={`请输入深度目标出价,范围0.1~10000`} />
  707. </Form.Item>
  708. </> :
  709. deepConversionType === 'DEEP_CONVERSION_WORTH' ? <>
  710. <Form.Item label={<strong>期望ROI</strong>} name='deepBidAmount' rules={[{ required: true, message: '请输入期望ROI' }]}>
  711. <Input style={{ width: 380 }} placeholder={`期望ROI目标范围0.001~1000,输入0.05,表示ROI目标为5%`} />
  712. </Form.Item>
  713. </> : null}
  714. {siteSet?.length > 1 && <Form.Item label={<strong>开启分版位深度目标出价</strong>} name='bidAdjustmentEnabled' valuePropName="checked">
  715. <Switch checkedChildren="开启" unCheckedChildren="关闭" />
  716. </Form.Item>}
  717. {bidAdjustmentEnabled && <Form.Item
  718. label={<strong>分版位深度目标出价</strong>}
  719. name='siteSetPackage'
  720. rules={[{ required: bidAdjustmentEnabled ? true : false, message: '请设置系数' }]}
  721. >
  722. <BidAdjustment bidAmount={deepBidAmount} deepConversionType={deepConversionType} goal={goal}>
  723. {!(bidAmountAdjustmentEnabled) ? <AdPositionList value={siteSetPackage} onChange={(data) => {
  724. form.setFieldsValue({
  725. siteSetPackage: data
  726. })
  727. }} /> : <></>}
  728. </BidAdjustment>
  729. </Form.Item>}
  730. </div>}
  731. </>}
  732. </>}
  733. <Form.Item label={<strong>广告日预算</strong>} name='dailyBudget'>
  734. <Input placeholder='不填默认为不限' style={{ width: 300 }} />
  735. </Form.Item>
  736. <Form.Item label={<strong>广告状态</strong>} name="configuredStatus" rules={[{ required: true, message: '请选择广告状态' }]}>
  737. <Select placeholder="选择广告状态" style={{ width: 300 }}>
  738. {Object.keys(AdStatus).map(key => {
  739. return <Select.Option value={key} key={key}>{AdStatus[key]}</Select.Option>
  740. })}
  741. </Select>
  742. </Form.Item>
  743. </Form>
  744. </Modal >
  745. }
  746. export default LeadAdModal