creativeForm.tsx 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. import { useAjax } from "@/Hook/useAjax";
  2. import { AdcreativeTemplate, AdcreativeTemplateList } from "@/services/launchAdq";
  3. import { get_adcreative_template, get_adcreative_template_list } from "@/services/launchAdq/global";
  4. import { mySet } from "@/utils/arrFn";
  5. import { DeleteOutlined, DownOutlined, UpOutlined } from "@ant-design/icons";
  6. import { Button, Form, Popconfirm, Radio, Spin } from "antd"
  7. import React, { forwardRef, useEffect, useImperativeHandle, useState } from "react"
  8. import { useModel } from "umi";
  9. import SelectCloud from "../../components/selectCloud";
  10. import TextAideInput from "../../components/textAideInput";
  11. import { outAdcreativeTemplateIdFun } from "../localAd/adenum";
  12. import style from './index.less'
  13. interface Props {
  14. template: { siteSet: string[], promotedObjectType: string },
  15. index: number,
  16. isDel: boolean,
  17. delOri?: () => void
  18. data?: any
  19. }
  20. /**
  21. * 批量Form
  22. * @returns
  23. */
  24. const CreativeForm = forwardRef((props: Props, ref) => {
  25. /**************************/
  26. const { data, template, index, delOri, isDel } = props
  27. const [form] = Form.useForm();
  28. let adcreativeElementsType = Form.useWatch('adcreativeElementsType', form)
  29. let adcreativeTemplateId = Form.useWatch('adcreativeTemplateId', form)
  30. const [adcreative_template_list, set_adcreative_template_list] = useState<AdcreativeTemplateList[]>([])
  31. const [adcreative_template, set_adcreative_template] = useState<AdcreativeTemplate>()
  32. const [conversionList, setConversionList] = useState<any>(null)
  33. const [materialConfig, setMaterialConfig] = useState<{ adcreativeTemplateId?: number, type: string, cloudSize: { relation: string, width: number, height: number }[], list: any[], max: number }>({
  34. type: '',//类型
  35. cloudSize: [],//素材搜索条件
  36. list: [],//素材
  37. max: 1,//素材数量
  38. })//素材配置
  39. const [pupState, setPupState] = useState({
  40. kp_show: false,
  41. xd_show: false,
  42. sj_show: false,
  43. bq_show: false,
  44. sp_show: false
  45. })
  46. const [isOpen, setIsOpen] = useState<boolean>(true)
  47. const [isErr, setIsErr] = useState<boolean>(false)
  48. const [selectImgVisible, set_selectImgVisible] = useState(false)
  49. const { init } = useModel('useLaunchAdq.useBdMediaPup')
  50. const getAdcreativeTemplateList = useAjax((params) => get_adcreative_template_list(params))
  51. const getAdcreativeTemplate = useAjax((params) => get_adcreative_template(params))
  52. /**************************/
  53. //子组件暴露方法
  54. useImperativeHandle(ref, () => ({
  55. handleOk
  56. }));
  57. const handleOk = () => {
  58. return new Promise((resolve: (value: unknown) => void, reject: (reason?: any) => void) => {
  59. form.validateFields().then(values => {
  60. setIsErr(false)
  61. resolve(values)
  62. }).catch(err => {
  63. setIsErr(true)
  64. reject(err)
  65. })
  66. })
  67. }
  68. // 获取创意形式列表
  69. useEffect(() => {
  70. if (template && template?.siteSet?.length > 0 && template?.promotedObjectType) {
  71. getAdcreativeTemplateList.run({
  72. siteSet: template?.siteSet,
  73. promotedObjectType: template?.promotedObjectType,
  74. campaignType: 'CAMPAIGN_TYPE_NORMAL',
  75. }).then(res => {
  76. let newArr: any = []
  77. // 过滤掉相同的和即将下线的
  78. if (!res) {
  79. return
  80. }
  81. Object.values(res)?.forEach((arr: any) => {
  82. Array.isArray(arr) && arr?.forEach((item: any) => {
  83. if (newArr.length > 0) {
  84. if (outAdcreativeTemplateIdFun(item.adcreativeTemplateId) && newArr.every((i: { adcreativeTemplateId: any }) => i.adcreativeTemplateId !== item.adcreativeTemplateId)) {
  85. newArr.push(item)
  86. } else {
  87. // 找出通用创意
  88. newArr = newArr?.map((arr: { adcreativeTemplateId: any }) => {
  89. if (arr.adcreativeTemplateId === item.adcreativeTemplateId) {
  90. return { ...arr, isGeneral: true }
  91. }
  92. return arr
  93. })
  94. }
  95. } else {
  96. if (outAdcreativeTemplateIdFun(item.adcreativeTemplateId)) {
  97. newArr.push(item)
  98. }
  99. }
  100. })
  101. })
  102. set_adcreative_template_list(newArr)
  103. })
  104. }
  105. }, [template, form])
  106. // 获取创意形式详情
  107. useEffect(() => {
  108. // CAMPAIGN_TYPE_NORMAL
  109. if (template?.siteSet?.length > 0 && template?.promotedObjectType && adcreativeTemplateId) {
  110. if (adcreativeTemplateId) {
  111. getAdcreativeTemplate.run({
  112. siteSet: template?.siteSet,
  113. promotedObjectType: template?.promotedObjectType,
  114. adcreativeTemplateId
  115. }).then(res => {
  116. if (res?.length > 0) {
  117. set_adcreative_template(res[0])
  118. }
  119. })
  120. }
  121. }
  122. }, [template?.siteSet, template?.promotedObjectType, adcreativeTemplateId])
  123. //每次选中创意设置该展示的界面
  124. useEffect(() => {
  125. let states = {
  126. kp_show: false,
  127. xd_show: true,
  128. sj_show: false,
  129. bq_show: false,
  130. sp_show: false
  131. }
  132. let values: any = { pageType: 'PAGE_TYPE_CANVAS_WECHAT', }
  133. if (adcreative_template) {
  134. let pageList = adcreative_template?.landingPageConfig?.supportPageTypeList
  135. let pageType = pageList?.length ? pageList[0]?.pageType : null
  136. //数据展示组件
  137. if (adcreative_template.adcreativeAttributes.some(item => item.name === 'conversion_data_type' || item.name === 'conversion_target_type')) {
  138. let arr = adcreative_template.adcreativeAttributes?.filter((item: { name: string; }) => item.name === 'conversion_data_type' || item.name === 'conversion_target_type')
  139. let newObj: any = {}
  140. arr.forEach((item) => {
  141. let arr: any[] = mySet(item.propertyDetail.enumDetail.enumeration)
  142. newObj[item.name] = arr
  143. })
  144. setConversionList(newObj)
  145. states = { ...states, sj_show: true }
  146. if (newObj.conversion_data_type) {
  147. values = { ...values, conversionDataType: newObj.conversion_data_type[0].value }
  148. }
  149. if (newObj.conversion_target_type) {
  150. values = { ...values, conversionTargetType: newObj.conversion_target_type[0].value }
  151. }
  152. }
  153. //行动按钮组件存在
  154. if (states.xd_show) {
  155. let linkNameList = (pageList?.filter((item: { pageType: any; }) => item.pageType === pageType)[0] as any)?.supportLinkNameType?.list
  156. let linkPageList = (pageList?.filter((item: { pageType: any; }) => item.pageType === pageType)[0] as any)?.supportLinkPageType?.list
  157. if (linkNameList) {
  158. let linkNameType = linkNameList[0]?.linkNameType
  159. let linkPageType = linkPageList[0]?.linkPageType
  160. values = { ...values, linkNameType, linkPageType }
  161. } else {
  162. states = { ...states, xd_show: false }
  163. }
  164. }
  165. // 视频结束页 end_page
  166. if (adcreative_template.adcreativeElements.some(item => item.name === 'end_page')) {
  167. // let endPageType =adcreative_template?.adcreativeElements?.filter(item=>item.name === 'end_page_type')[0]?.enumProperty?.enumeration
  168. values = { ...values, endPageType: 'END_PAGE_AVATAR_NICKNAME_HIGHLIGHT' }
  169. states = { ...states, sp_show: true }
  170. }
  171. setPupState(states)
  172. form.setFieldsValue(values)
  173. }
  174. }, [adcreative_template])
  175. // 版位改变清空数据
  176. useEffect(() => {
  177. if (materialConfig.adcreativeTemplateId && adcreativeTemplateId !== materialConfig.adcreativeTemplateId) {
  178. setMaterialConfig({ ...materialConfig, adcreativeTemplateId: undefined, list: [] })
  179. }
  180. }, [adcreativeTemplateId, materialConfig])
  181. // 切换创意形式默认选中第一个
  182. useEffect(() => {
  183. // 设置默认选中第一个
  184. if (adcreativeElementsType && adcreative_template_list?.length > 0) {
  185. let adcreativeTemplateIdArr = adcreative_template_list?.filter(item => item.adcreativeTemplateStyle === adcreativeElementsType)
  186. form.setFieldsValue({ adcreativeTemplateId: adcreativeTemplateIdArr[0].adcreativeTemplateId })
  187. }
  188. }, [adcreativeElementsType, adcreative_template_list])
  189. return <div className={style.originality} key={index} style={isOpen ? { borderColor: isErr ? 'red' : '#e4e4e4' } : { height: 44, overflow: 'hidden', borderColor: isErr ? 'red' : '#e4e4e4' }}>
  190. <div className={style.head} onClick={() => { setIsOpen(!isOpen) }}>
  191. <div>创意{index}</div>
  192. <div>
  193. {isDel && <Popconfirm placement="top" title="是否放弃该创意" onConfirm={(e) => { e?.stopPropagation(); delOri && delOri() }} okText="Yes" cancelText="No">
  194. <Button type="link" size='small' className={style.clear} style={{ color: 'red' }} onClick={(e) => { e?.stopPropagation() }}><DeleteOutlined /></Button>
  195. </Popconfirm>}
  196. <Button
  197. type="link"
  198. size='small'
  199. style={{ color: '#000' }}
  200. >
  201. {isOpen ? <UpOutlined /> : <DownOutlined />}
  202. </Button>
  203. </div>
  204. </div>
  205. <div>
  206. <div style={{ height: 20 }}></div>
  207. <Form
  208. form={form}
  209. labelCol={{ span: 4 }}
  210. labelWrap={true}
  211. labelAlign="left"
  212. initialValues={
  213. {
  214. adcreativeElementsType: '视频'
  215. }
  216. }
  217. >
  218. <Form.Item label="创意形式" name='adcreativeElementsType'>
  219. <Radio.Group >
  220. <Radio.Button value="视频">视频</Radio.Button>
  221. <Radio.Button value="图片">图片</Radio.Button>
  222. </Radio.Group>
  223. </Form.Item>
  224. <Spin tip="Loading..." spinning={getAdcreativeTemplateList?.loading} style={{ width: '100%' }}>
  225. <Form.Item style={{ marginLeft: 155 }} name='adcreativeTemplateId'>
  226. <Radio.Group className={style.adcreative_template}>
  227. {adcreative_template_list?.filter(item => item.adcreativeTemplateStyle === adcreativeElementsType)?.map((item: any) => {
  228. return <Radio.Button value={item.adcreativeTemplateId} key={item.adcreativeTemplateId}>
  229. <div className={style.adcreative_template_item}>
  230. {item.isGeneral && <span style={{ color: '#4080ff', fontSize: 10 }}>所选版位通投</span>}
  231. <img src={item.adcreativeSampleImage} />
  232. <span style={{ fontSize: 12, height: 20, lineHeight: '20px' }}>{item.adcreativeTemplateAppellation}</span>
  233. <span style={{ fontSize: 12, height: 20, lineHeight: '20px' }}>{item.adcreativeTemplateId}</span>
  234. </div>
  235. </Radio.Button>
  236. })}
  237. </Radio.Group>
  238. </Form.Item>
  239. {/* 优先展示视频或图片 */}
  240. {adcreative_template?.adcreativeElements?.filter(item => item.required && item.name === 'image_list' || item.name === 'short_video1' || item.name === 'video' || item.name === 'image').map(item => {
  241. return <Form.Item label={item.description} rules={[{ required: true, message: '请选择素材!' }]} key={item.name} name={item.name}>
  242. {/* 视频 */}
  243. {
  244. (item.name === 'short_video1' || item.name === 'video') && <div className={`${style.box} ${style.video}`} onClick={() => {
  245. init({ mediaType: 'VIDEO', cloudSize: adcreativeTemplateId === 1708 ? [[{ relation: '=', width: 1280, height: 720 }]] : [[{ relation: '=', width: item.restriction.videoRestriction.minWidth, height: item.restriction.videoRestriction.minHeight }]], maxSize: item.restriction.videoRestriction.fileSize * 1024 })
  246. setTimeout(() => {
  247. set_selectImgVisible(true)
  248. setMaterialConfig({
  249. ...materialConfig,
  250. type: item.name,
  251. max: 1,
  252. adcreativeTemplateId
  253. })
  254. }, 100)
  255. }}>
  256. <p>
  257. {
  258. materialConfig?.list[0] ? <video src={materialConfig?.list[0].url} controls /> : <>
  259. <span>{`推荐尺寸(${adcreativeTemplateId === 1708 ? 1280 : item.restriction.videoRestriction.minWidth} x ${adcreativeTemplateId === 1708 ? 720 : item.restriction.videoRestriction.minHeight})`}</span>
  260. <span>{`${item.restriction.videoRestriction.fileFormat?.map(str => str?.replace('MEDIA_TYPE_', ''))};< ${item.restriction.videoRestriction.fileSize / 1024}M;时长 ≥ ${item.restriction.videoRestriction.minDuration}s,≤ ${item.restriction.videoRestriction.maxDuration}s,必须带有声音`}</span>
  261. </>
  262. }
  263. </p>
  264. </div>
  265. }
  266. {/* 单图 */}
  267. {
  268. item.name === 'image' && <div className={`${style.box} ${style.image}`} onClick={() => {
  269. init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: item.restriction.imageRestriction.width, height: item.restriction.imageRestriction.height }]], maxSize: item.restriction.imageRestriction.fileSize * 1024 })
  270. setTimeout(() => {
  271. set_selectImgVisible(true)
  272. setMaterialConfig({
  273. ...materialConfig,
  274. type: item.name,
  275. max: 1,
  276. adcreativeTemplateId
  277. })
  278. }, 100)
  279. }}>
  280. <p>
  281. {materialConfig?.list[0] ? <img src={materialConfig?.list[0].url} /> : <>
  282. <span>{`推荐尺寸(${item.restriction.imageRestriction.width} x ${item.restriction.imageRestriction.height})`}</span>
  283. <span>{`${item.restriction.imageRestriction.fileFormat?.map(str => str?.replace('IMAGE_TYPE_', ''))};小于 ${item.restriction.imageRestriction.fileSize}KB`}</span>
  284. </>
  285. }
  286. </p>
  287. </div>
  288. }
  289. {/* 多图 */}
  290. {
  291. item.name === 'image_list' && <div className={`${style.box} ${item.arrayProperty.maxNumber >= 3 ? style.image_list : style.image}`} onClick={() => {
  292. init({ mediaType: 'IMG', num: item.arrayProperty.maxNumber, cloudSize: [[{ relation: '=', width: item.restriction.imageRestriction.width, height: item.restriction.imageRestriction.height }]], maxSize: item.restriction.imageRestriction.fileSize * 1024 })
  293. setTimeout(() => {
  294. set_selectImgVisible(true)
  295. setMaterialConfig({
  296. ...materialConfig,
  297. type: item.name,
  298. max: item.arrayProperty.maxNumber,
  299. adcreativeTemplateId
  300. })
  301. }, 100)
  302. }}>
  303. {
  304. Array(item.arrayProperty.maxNumber).fill('').map((arr, index) => {
  305. return <p key={index}>
  306. {
  307. materialConfig?.list[index] ? <img src={materialConfig?.list[index].url} /> : <>
  308. <span>{`推荐尺寸(${item.restriction.imageRestriction.width} x ${item.restriction.imageRestriction.height})`}</span>
  309. <span>{`${item.restriction.imageRestriction.fileFormat?.map(str => str?.replace('IMAGE_TYPE_', ''))};小于 ${item.restriction.imageRestriction.fileSize}KB`}</span>
  310. </>
  311. }
  312. </p>
  313. })
  314. }
  315. </div>
  316. }
  317. </Form.Item>
  318. })}
  319. {/* 过滤了不必传和品牌名称,品牌标识图(外部传)短视频结构(组装使用) */}
  320. {adcreative_template?.adcreativeElements?.filter(item => item.required && item.name === 'description').map(item => {
  321. let maxNum = adcreativeTemplateId === 1708 ? pupState.xd_show ? 10 : item.restriction.textRestriction.maxLength : item.restriction.textRestriction.maxLength
  322. return <div key={item.fieldType}>
  323. <Form.Item label={item.description} name={item.name} rules={[{ required: true, pattern: RegExp(item.restriction.textRestriction.textPattern?.replace(/\+/ig, `{1,${maxNum}}`)), message: '请输入正确的' + item.description }]}>
  324. <TextAideInput placeholder={'请输入' + item.description} style={{ width: 500 }} maxTextLength={maxNum} />
  325. </Form.Item>
  326. </div>
  327. })}
  328. </Spin>
  329. </Form>
  330. </div>
  331. {/* 选择素材 */}
  332. {selectImgVisible && <SelectCloud
  333. visible={selectImgVisible}
  334. onClose={() => set_selectImgVisible(false)}
  335. sliderImgContent={materialConfig.list}
  336. onChange={(content) => {
  337. if (content.length > 0) {
  338. form.setFieldsValue({ [materialConfig.type]: materialConfig.type })
  339. }
  340. setMaterialConfig({ ...materialConfig, list: content })
  341. set_selectImgVisible(false)
  342. console.log(content)
  343. }} />
  344. }
  345. </div>
  346. })
  347. export default React.memo(CreativeForm)