wechat.tsx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  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 } 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. const { RangePicker }: { RangePicker: any } = DatePicker;
  12. let DatePickers: any = DatePicker
  13. interface Props {
  14. queryForm: Partial<CreateAdProps>,
  15. visible: boolean,
  16. PupFn: (arg: ModalConfig) => void,
  17. callback: (params: any) => void,
  18. confirmLoading?: boolean,
  19. type?: 'add' | 'look' | 'edit',//新增,查看,编辑
  20. dataInfo?: any
  21. }
  22. /**微信公众号广告弹窗*/
  23. function WeChatAdModal(props: Props) {
  24. let { visible, confirmLoading, PupFn, callback, type, dataInfo, queryForm } = props
  25. const createSysAdgroup = useAjax((params) => createSysAdgroups(params))
  26. let [state, setState] = useState<any>({
  27. isShowTime: []
  28. })
  29. let [template_checked, settemplate_checked] = useState<boolean>(dataInfo?.isTemplate || false)
  30. let arg = type === 'look' ? { footer: null } : {}
  31. const sceneTagsList = useAjax((params) => getSceneTagsList(params))
  32. const [form] = Form.useForm();
  33. let dateType = Form.useWatch('dateType', form)
  34. let bidMode = Form.useWatch('bidMode', form)
  35. let smartBidType = Form.useWatch('smartBidType', form)
  36. let autoAcquisitionEnabled = Form.useWatch('autoAcquisitionEnabled', form)
  37. let siteSet = Form.useWatch('siteSet', form)
  38. let wechatPositionType = Form.useWatch('wechatPositionType', form)
  39. let wechatSceneType = Form.useWatch('wechatSceneType', form)
  40. let automaticSiteEnabled = Form.useWatch('automaticSiteEnabled', form)
  41. let optimizationGoal = Form.useWatch('optimizationGoal', form)
  42. // 确定事件
  43. const handleOk = useCallback(() => {
  44. form.validateFields().then(values => {
  45. let newValues = JSON.parse(JSON.stringify(values))
  46. newValues.sceneSpec = {}
  47. if (newValues.dateType === '2') {
  48. newValues['beginDate'] = moment(newValues.date).format('YYYY-MM-DD')
  49. } else {
  50. newValues['beginDate'] = moment(newValues.date[0]).format('YYYY-MM-DD')
  51. newValues['endDate'] = moment(newValues.date[1]).format('YYYY-MM-DD')
  52. }
  53. if (newValues.firstDayBeginTime) {
  54. newValues['firstDayBeginTime'] = moment(newValues.firstDayBeginTime).format('HH:mm:ss')
  55. }
  56. Object.keys(newValues).forEach(key => {
  57. switch (key) {
  58. case 'wechatPositionType':
  59. if (newValues[key] === '1') {
  60. newValues.sceneSpec = { ...newValues.sceneSpec, wechatPosition: newValues.wechatPosition }
  61. }
  62. break;
  63. case 'wechatSceneType':
  64. if (newValues[key] === '1') {
  65. newValues.sceneSpec = {
  66. ...newValues.sceneSpec, wechatScene: {
  67. officialAccountMediaCategory: newValues.officialAccountMediaCategory,
  68. miniProgramAndMiniGame: newValues.miniProgramAndMiniGame,
  69. payScene: newValues.payScene
  70. }
  71. }
  72. }
  73. break;
  74. }
  75. })
  76. if (newValues.sceneSpec.wechatPosition?.length === 0) {
  77. delete newValues.sceneSpec.wechatPosition
  78. }
  79. if (newValues.sceneSpec.wechatScene) {
  80. newValues.sceneSpec.wechatScene.officialAccountMediaCategory?.length === 0 && (delete newValues.sceneSpec.wechatScene.officialAccountMediaCategory)
  81. newValues.sceneSpec.wechatScene.miniProgramAndMiniGame?.length === 0 && (delete newValues.sceneSpec.wechatScene.miniProgramAndMiniGame)
  82. newValues.sceneSpec.wechatScene.payScene?.length === 0 && (delete newValues.sceneSpec.wechatScene.payScene)
  83. }
  84. if (!newValues.sceneSpec.wechatPosition && !newValues.sceneSpec.wechatScene) {
  85. delete newValues.sceneSpec
  86. }
  87. delete newValues.officialAccountMediaCategory
  88. delete newValues.miniProgramAndMiniGame
  89. delete newValues.payScene
  90. delete newValues.wechatPositionType
  91. delete newValues.wechatPosition
  92. delete newValues.wechatScene
  93. delete newValues['dateType']
  94. delete newValues['date']
  95. newValues['timeSeries'] = Array(336).fill(1).join('')
  96. newValues['promotedObjectType'] = queryForm.promotedObjectType
  97. newValues['isTemplate'] = template_checked
  98. console.log(newValues)
  99. // 开启存为模板开关执行
  100. // if (template_checked && type === 'add') {
  101. // createSysAdgroup.run(newValues).then(res => {
  102. // if (res) {
  103. // callback(newValues)
  104. // }
  105. // })
  106. // } else {
  107. callback(newValues)
  108. // }
  109. })
  110. }, [form, template_checked, queryForm, type])
  111. // 场景定向
  112. useEffect(() => {
  113. sceneTagsList.run({ typeList: ['WECHAT_POSITION', 'OFFICIAL_ACCOUNT_MEDIA_CATEGORY', 'MINI_PROGRAM_AND_MINI_GAME', 'PAY_SCENE'] })
  114. }, [])
  115. // 数据回填
  116. useEffect(() => {
  117. if (dataInfo) {
  118. form.setFieldsValue({
  119. adgroupName: dataInfo?.adgroupName,//广告名称
  120. promotedObjectType: dataInfo?.promotedObjectType,//推广目标
  121. siteSet: dataInfo?.siteSet,//广告版位
  122. autoAcquisitionEnabled: dataInfo?.autoAcquisitionEnabled,//一键起量
  123. bidAmount: dataInfo?.bidAmount,//出价
  124. smartBidType: dataInfo?.smartBidType,//出价类型
  125. bidStrategy: dataInfo?.bidStrategy,//出价策略
  126. bidMode: dataInfo?.bidMode,//出价方式
  127. optimizationGoal: dataInfo?.optimizationGoal,//优化目标
  128. dateType: dataInfo?.endDate ? '1' : '2',//投放日期
  129. dailyBudget: dataInfo?.dailyBudget,//广告日预算
  130. date: dataInfo?.endDate ? [moment(dataInfo?.beginDate), moment(dataInfo?.endDate)] : moment(dataInfo?.beginDate),//日期
  131. autoAcquisitionBudget: dataInfo?.autoAcquisitionBudget,//起量预算
  132. wechatPositionType: dataInfo?.sceneSpec?.wechatPosition ? '1' : '0',//微信公众号与小程序定投
  133. wechatPosition: dataInfo?.sceneSpec?.wechatPosition,//微信公众号与小程序定投
  134. wechatSceneType: dataInfo?.sceneSpec?.wechatScene?.officialAccountMediaCategory || dataInfo?.sceneSpec?.wechatScene?.miniProgramAndMiniGame || dataInfo?.sceneSpec?.wechatScene?.payScene ? '1' : '0',//微信公众号与小程序场景
  135. officialAccountMediaCategory: dataInfo?.sceneSpec?.wechatScene?.officialAccountMediaCategory,//公众号媒体类型
  136. miniProgramAndMiniGame: dataInfo?.sceneSpec?.wechatScene?.miniProgramAndMiniGame,//小程序小游戏流量类型
  137. payScene: dataInfo?.sceneSpec?.wechatScene?.payScene,//订单详情页消费场景
  138. firstDayBeginTime: dataInfo?.firstDayBeginTime ? moment(`${dataInfo?.beginDate} ${dataInfo?.firstDayBeginTime}`) : undefined,//首日开始时间
  139. configuredStatus: dataInfo?.configuredStatus || 'AD_STATUS_SUSPEND',//广告启停
  140. })
  141. if (dataInfo?.firstDayBeginTime) {//存在首日开始时间,选中开关
  142. setState({ ...state, isShowTime: ['1'] })
  143. }
  144. }
  145. }, [dataInfo])
  146. // 出价方式改变清空某些数据
  147. const bidModeChange = useCallback((props) => {
  148. form.setFieldsValue({
  149. ...props,
  150. optimizationGoal: null,
  151. smartBidType: null,
  152. // bidAmount:null,
  153. bidStrategy: null,
  154. autoAcquisitionEnabled: false,
  155. autoAcquisitionBudget: null,
  156. dailyBudget: null,
  157. })
  158. }, [])
  159. return <Modal
  160. visible={visible}
  161. title={type === 'add' ? '新建广告' : type === 'look' ? '广告详情' : '编辑广告'}
  162. onCancel={() => { PupFn({ visible: false, dataInfo: null, type: 'add' }) }}
  163. width={900}
  164. confirmLoading={createSysAdgroup?.loading}
  165. footer={<Space>
  166. <Button onClick={() => { PupFn({ visible: false, dataInfo: null, type: 'add' }) }}>取消</Button>
  167. <Button type='primary' onClick={handleOk}>确定</Button>
  168. {<Checkbox checked={template_checked} onChange={(e) => {
  169. let checked = e.target.checked
  170. settemplate_checked(checked)
  171. }}>存为模板</Checkbox>}
  172. </Space>}
  173. {...arg}
  174. >
  175. <Form
  176. form={form}
  177. labelCol={{ span: 5 }}
  178. className='ad_form_style'
  179. initialValues={
  180. {
  181. promotedObjectType: queryForm.promotedObjectType,
  182. siteSet: ['SITE_SET_MOMENTS', 'SITE_SET_WECHAT'],
  183. bidMode: 'BID_MODE_OCPM',
  184. automaticSiteEnabled: false,
  185. dateType: '2',
  186. bidStrategy: 'BID_STRATEGY_AVERAGE_COST',
  187. timeSeries: '1',
  188. smartBidType: 'SMART_BID_TYPE_CUSTOM',
  189. autoAcquisitionEnabled: false,
  190. wechatSceneType: '0',
  191. wechatPositionType: '0',
  192. optimizationGoal: 'OPTIMIZATIONGOAL_ECOMMERCE_ORDER',
  193. configuredStatus: 'AD_STATUS_SUSPEND'
  194. }
  195. }
  196. >
  197. {/* ============================================================基本信息============================================================= */}
  198. <Divider orientation='center'>基本信息</Divider>
  199. <Form.Item label={<strong>广告名称</strong>} name='adgroupName' rules={[{ required: true, message: '请输入广告名称!' }]}>
  200. <Input placeholder='广告名称' style={{ width: 300 }} />
  201. </Form.Item>
  202. <Form.Item label={<strong>广告版位</strong>}>
  203. <Form.Item name='automaticSiteEnabled'>
  204. <Radio.Group buttonStyle="solid">
  205. <Radio.Button value={true} disabled={queryForm.promotedObjectType === 'PROMOTED_OBJECT_TYPE_WECHAT_OFFICIAL_ACCOUNT'}>自动版位</Radio.Button>
  206. <Radio.Button value={false}>选择特定版位</Radio.Button>
  207. </Radio.Group>
  208. </Form.Item>
  209. {!automaticSiteEnabled && <Form.Item name='siteSet' noStyle rules={[{ required: true, message: '请输入选择广告版位!' }]}>
  210. <Checkbox.Group style={{ width: '100%' }} onChange={() => { bidModeChange({ bidMode: 'BID_MODE_OCPM' }) }}>
  211. <Row>
  212. <Col span={5}>
  213. <Checkbox value="SITE_SET_MOMENTS">微信朋友圈</Checkbox>
  214. </Col>
  215. <Col span={6}>
  216. <Checkbox value="SITE_SET_WECHAT">微信公众号与小程序</Checkbox>
  217. </Col>
  218. </Row>
  219. </Checkbox.Group>
  220. </Form.Item>}
  221. </Form.Item>
  222. {
  223. siteSet?.some((s: string) => s === 'SITE_SET_WECHAT') && <>
  224. <Form.Item label={<strong>微信公众号与小程序定投</strong>} name='wechatPositionType' style={wechatPositionType === '1' ? { marginBottom: 5 } : {}}>
  225. <Radio.Group >
  226. <Radio.Button value="0">不限</Radio.Button>
  227. <Radio.Button value="1">自定义</Radio.Button>
  228. </Radio.Group>
  229. </Form.Item>
  230. {wechatPositionType === '1' && <Form.Item style={{ marginLeft: 177 }} name='wechatPosition'>
  231. <Checkbox.Group options={sceneTagsList?.data?.WECHAT_POSITION?.map((item: { description: any; id: any; }) => ({ label: item.description, value: item.id }))} />
  232. </Form.Item>}
  233. <Form.Item label={<strong>微信公众号与小程序场景</strong>} name='wechatSceneType' style={wechatSceneType === '1' ? { marginBottom: 5 } : {}} >
  234. <Radio.Group >
  235. <Radio.Button value="0">不限</Radio.Button>
  236. <Radio.Button value="1">自定义</Radio.Button>
  237. </Radio.Group>
  238. </Form.Item>
  239. {wechatSceneType === '1' && <>
  240. <p style={{ marginBottom: 5, marginLeft: 177 }}><strong style={{ marginRight: 20 }}>公众号媒体类型</strong></p>
  241. <Form.Item style={{ marginLeft: 177 }} name='officialAccountMediaCategory'>
  242. <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 }))} />
  243. </Form.Item>
  244. <p style={{ marginBottom: 5, marginLeft: 177 }}><strong style={{ marginRight: 20 }}>小程序小游戏流量类型</strong></p>
  245. <Form.Item style={{ marginLeft: 177 }} name='miniProgramAndMiniGame'>
  246. <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 }))} />
  247. </Form.Item>
  248. <p style={{ marginBottom: 5, marginLeft: 177 }}><strong style={{ marginRight: 20 }}>订单详情页消费场景</strong></p>
  249. <Form.Item style={{ marginLeft: 177 }} name='payScene'>
  250. <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 }))} />
  251. </Form.Item>
  252. </>}
  253. </>
  254. }
  255. {/* ============================================================排期与出价============================================================= */}
  256. <Divider orientation='center'>排期与出价</Divider>
  257. <Form.Item label={<strong>投放日期</strong>} name='dateType'>
  258. <Radio.Group >
  259. <Radio.Button value="1">选择开始与结束日期</Radio.Button>
  260. <Radio.Button value="2">长期投放</Radio.Button>
  261. </Radio.Group>
  262. </Form.Item>
  263. {/* 投放日期的不同展示不同的日期选择 */}
  264. {
  265. dateType === '1' ? <Form.Item name='date' rules={[{ required: true, message: '请选择日期' }]}>
  266. <RangePicker style={{ marginLeft: 177 }}></RangePicker>
  267. </Form.Item> : <Form.Item name='date' style={{ marginLeft: 177 }} rules={[{ required: true, message: '请选择日期' }]}>
  268. <DatePickers />
  269. </Form.Item>
  270. }
  271. <Form.Item label={<strong>投放时段</strong>}>
  272. <Space>
  273. <Radio.Group name='timeSeries' defaultValue='1'>
  274. <Radio.Button value={'1'}>全天投放</Radio.Button>
  275. </Radio.Group>
  276. <Checkbox.Group options={[{ label: '指定首日开始投放时间', value: '1' }]} onChange={(checkedValue) => {
  277. setState({ ...state, isShowTime: checkedValue })
  278. }
  279. } value={state.isShowTime} />
  280. </Space>
  281. {state?.isShowTime?.length > 0 && <Form.Item name='firstDayBeginTime' noStyle rules={[{ required: true, message: '请选择时间' }]}>
  282. <TimePicker />
  283. </Form.Item>}
  284. </Form.Item>
  285. <Form.Item label={<strong>出价方式<Tooltip title='oCPC/oCPM出价,或开启自动扩量/智能扩量时不支持二方人群'><ExclamationCircleOutlined style={{ color: '#e91e63', marginLeft: 5 }} /></Tooltip></strong>} name='bidMode' rules={[{ required: true, message: '请选择出价方式' }]}>
  286. <Radio.Group onChange={bidModeChange}>
  287. {
  288. 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 => {
  289. return <Radio.Button value={key} key={key} >{BidModeEnum[key]}</Radio.Button>
  290. })
  291. }
  292. </Radio.Group>
  293. </Form.Item>
  294. {/* 出价方式为OCPM才展示 */}
  295. {
  296. bidMode === 'BID_MODE_OCPM' && <>
  297. <Form.Item label={<strong>优化目标</strong>} name='optimizationGoal' rules={[{ required: true, message: '请选择优化目标' }]}>
  298. <Select style={{ width: 300 }} showSearch filterOption={(input, option) =>
  299. (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
  300. } allowClear>
  301. {
  302. Object.keys(OptimizationGoalEnum).map(key => {
  303. return <Select.Option value={key} key={key}>{OptimizationGoalEnum[key]}</Select.Option>
  304. })
  305. }
  306. </Select>
  307. </Form.Item>
  308. <Form.Item label={<strong>出价类型</strong>} name='smartBidType' rules={[{ required: true, message: '请选择出价类型' }]}>
  309. <Radio.Group >
  310. <Radio.Button value="SMART_BID_TYPE_CUSTOM">手动出价</Radio.Button>
  311. <Radio.Button value="SMART_BID_TYPE_SYSTEMATIC">自动出价</Radio.Button>
  312. </Radio.Group>
  313. </Form.Item>
  314. <Form.Item label={<strong>出价策略</strong>} name='bidStrategy' rules={[{ required: true, message: '请选择出价策略' }]}>
  315. <Radio.Group >
  316. {
  317. Object.keys(BidStrategyEnum).map(key => {
  318. return <Radio.Button value={key} key={key} disabled={smartBidType === 'SMART_BID_TYPE_SYSTEMATIC' && key === 'BID_STRATEGY_PRIORITY_CAP_COST'}> {BidStrategyEnum[key]}</Radio.Button>
  319. })
  320. }
  321. </Radio.Group>
  322. </Form.Item>
  323. </>
  324. }
  325. {/* 出价类型为手动出价才展示 */}
  326. {
  327. smartBidType !== 'SMART_BID_TYPE_SYSTEMATIC' && <>
  328. <Form.Item label={<strong>出价</strong>} name='bidAmount' rules={[{ required: true, message: '请输入价格' }]}>
  329. <Input placeholder={`输入价格 元/${bidMode === 'BID_MODE_CPM' ? '千次曝光' : bidMode === 'BID_MODE_CPC' ? '点击' : OptimizationGoalEnum[optimizationGoal]}`} style={{ width: 300 }} />
  330. </Form.Item>
  331. {/* 当版位选择大于1时才出现 */}
  332. {/* {siteSet?.length > 1 &&<Form.Item label={<strong>分版位出价</strong>} name='bidAdjustment'>
  333. <Switch checkedChildren="开启" unCheckedChildren="关闭" />
  334. </Form.Item>} */}
  335. <Form.Item label={<strong>一键起量</strong>} name='autoAcquisitionEnabled' valuePropName="checked">
  336. <Switch checkedChildren="开启" unCheckedChildren="关闭" />
  337. </Form.Item>
  338. {/* 一键起量开启时才出现 */}
  339. {autoAcquisitionEnabled && <Form.Item label={<strong>起量预算</strong>} name='autoAcquisitionBudget' rules={[{ required: true, message: '请输入起量预算' }]}>
  340. <Input placeholder='起量预算' style={{ width: 300 }} />
  341. </Form.Item>}
  342. </>
  343. }
  344. <Form.Item label={<strong>广告日预算</strong>} name='dailyBudget'>
  345. <Input placeholder='不填默认为不限' style={{ width: 300 }} />
  346. </Form.Item>
  347. <Form.Item label={<strong>广告状态</strong>} name="configuredStatus" rules={[{ required: true, message: '请选择广告状态' }]}>
  348. <Select placeholder="选择广告状态" style={{ width: 300 }}>
  349. {Object.keys(AdStatus).map(key => {
  350. return <Select.Option value={key} key={key}>{AdStatus[key]}</Select.Option>
  351. })}
  352. </Select>
  353. </Form.Item>
  354. </Form>
  355. </Modal >
  356. }
  357. export default WeChatAdModal