wechat.tsx 43 KB

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