leadAd.tsx 47 KB

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