index.tsx 149 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080
  1. import { Button, Col, Drawer, Form, Input, InputNumber, message, Modal, Radio, Row, Select, Slider, Space, Spin, Switch, Tooltip } from 'antd'
  2. import React, { useCallback, useEffect, useMemo, useState } from "react"
  3. import modal from "antd/lib/modal"
  4. import style from './index.less'
  5. import './index1.less'
  6. import { ReactComponent as Topimg } from '@/assets/topimg.svg'
  7. import { ReactComponent as Topslider } from '@/assets/topslider.svg'
  8. import { ReactComponent as Topvideo } from '@/assets/topvideo.svg'
  9. import { ReactComponent as Img } from '@/assets/img.svg'
  10. import { ReactComponent as MyText } from '@/assets/text.svg'
  11. import { ReactComponent as TopNullBack } from '@/assets/topNullBack.svg'
  12. import { ReactComponent as FollowAcc } from '@/assets/followAcc.svg'
  13. import { ReactComponent as EditSvg } from '@/assets/edit.svg'
  14. import { ReactComponent as SliderImgSvg } from '@/assets/sliderImgSvg.svg'
  15. import { ReactComponent as JumpLink } from '@/assets/jumpLink.svg'
  16. import { ReactComponent as ImgText } from '@/assets/imgText.svg'
  17. import { ReactComponent as FloatbuttonSvg } from '@/assets/floatbuttonSvg.svg'
  18. import { ReactComponent as WxAutoSvg } from '@/assets/wxAutoSvg.svg'
  19. import { AlignCenterOutlined, AlignLeftOutlined, AlignRightOutlined, DeleteOutlined, PlusOutlined, QuestionCircleOutlined, RetweetOutlined, SwapLeftOutlined, SwapRightOutlined } from '@ant-design/icons'
  20. import { useDrop, useDrag } from 'ahooks'
  21. import arrayMove from "array-move";
  22. import ColorPicker from '@/components/ColorPicker1'
  23. import TextArea from 'antd/lib/input/TextArea'
  24. import moment from 'moment'
  25. import { TopImg, TopVideo, Text, Img as ImgProps, GhButton, TopSlider, Floatbutton, ImageTextItem, ITItemGhSpec } from '../../req'
  26. import { imgContent, txtContent, ghContent, topvideoNewContent, topsliderContent, topimgContent, wxAutoContent, floatbuttonContent, floatbuttonBtTypeWxAuto, floatbuttonBtTypeGh, imageTextGh, ghITItemContent, wxAutoITItemContent, imageTextItem } from './content'
  27. import { landingPageReducer, Content, Props } from './landingPageReducer'
  28. import {
  29. SortableList,
  30. SortableItemText,
  31. SortableItemImg,
  32. SortableItemFollowAcc,
  33. SortableItemImgText,
  34. SortableUlList,
  35. SortableItemNoLi,
  36. SortableItemLi,
  37. SortableItemJumpLink,
  38. SortableItemWxAuto,
  39. SortableItemFloatbutton
  40. } from './sortable'
  41. import { useModel } from 'umi'
  42. import SelectCloud from '../selectCloud'
  43. import { getTypeKey, replaceSpecialTxt, txtLength } from '@/utils/utils'
  44. const { Option } = Select;
  45. function AddLandingPage(props: Props) {
  46. let { visible, hideModal, ajax, id } = props
  47. const { state, stateGlobal, dispatch, dispatchGlobal } = landingPageReducer()
  48. const { content, pageBackColor } = state
  49. const { componentItem } = stateGlobal
  50. const { currentUser }: any = useModel('@@initialState', model => ({ currentUser: model.initialState?.currentUser }))
  51. const { init, add, get } = useModel('useLaunchAdq.useBdMediaPup')
  52. const { state: { parentId, belongUser } } = useModel('useLaunchAdq.useBdMedia')
  53. /** 变量开始 */
  54. const [selectImgVisible, setSelectImgVisible] = useState<boolean>(false) // 选择图片弹窗
  55. const [lastVisible, setLastVisible] = useState<boolean>(false) // 最后保存设置
  56. const [shareTittle, setShareTittle] = useState<string>('') // 分享标题
  57. const [shareDesc, setShareDesc] = useState<string>('') // 分享描述
  58. const [pageName, setPageName] = useState<string>('') // 落地页名称
  59. const [sort, setSort] = useState<number>(0) // 排序
  60. const [scType, setCcType] = useState<1 | 2 | 3 | 4 | 5>(1) // 视频 单图片 多图片处理
  61. const [sliderImgContent, setSliderImgContent] = useState<{ url: string, width?: number, height?: number }[]>([]) // 保存回填数据
  62. const [imgTextButtonShow, setImgTextButtonShow] = useState<boolean>(false)
  63. const [goodsCount, setGoodsCount] = useState<number>(0)
  64. /** 变量结束 */
  65. console.log('content---->', content)
  66. // 回填
  67. useEffect(() => {
  68. if (id) {
  69. get.run({ sysMediaId: id, mediaType: 'PAGE' }).then(res => {
  70. if (res) {
  71. const { pageSpecsList, shareContentSpec, globalSpec } = res
  72. dispatch({ type: 'setPageBackColor', params: { pageBackColor: pageSpecsList[0]?.bgColor } })
  73. let pageElementsSpecList = pageSpecsList[0]?.pageElementsSpecList
  74. dispatch({
  75. type: 'setCon', params: {
  76. content: pageElementsSpecList?.map((item: any) => {
  77. let typeKey = getTypeKey(item?.elementType)
  78. if (typeKey) {
  79. let data = item[typeKey] || {}
  80. if (item?.elementType === "IMAGE_TEXT" && data?.imageTextItem) {
  81. data.imageTextItem = data.imageTextItem?.map((item: any) => {
  82. if (item?.subElemType === 'GH') {
  83. let { ghSpec, ...GHData } = item
  84. return { ...GHData, content: { ...ghSpec } }
  85. } else {
  86. let { enterpriseWxSpec, ...EnterpriseWxData } = item
  87. return { ...EnterpriseWxData, content: { ...enterpriseWxSpec } }
  88. }
  89. })
  90. }
  91. return {
  92. elementType: item?.elementType,
  93. ...data
  94. }
  95. }
  96. return item
  97. })
  98. }
  99. })
  100. setShareDesc(() => shareContentSpec?.shareTitle || '')
  101. setShareTittle(() => shareContentSpec?.shareDescription || '')
  102. if (globalSpec && Object.keys(globalSpec).length > 0) {
  103. let globalElementsSpecList = globalSpec.globalElementsSpecList
  104. let newComponentItem = globalElementsSpecList?.map((item: { elementType: string }) => {
  105. let typeKey = getTypeKey(item.elementType)
  106. let { elementType, ...data } = item[typeKey]
  107. let typeKey1 = getTypeKey(elementType)
  108. let componentItem: any = data[typeKey1]
  109. if (componentItem) {
  110. componentItem['elementType'] = elementType
  111. }
  112. data.componentItem = componentItem
  113. data.elementType = item.elementType
  114. delete data[typeKey1]
  115. return data
  116. })
  117. dispatchGlobal({ type: "setConItem", params: { componentItem: newComponentItem || [] } })
  118. }
  119. }
  120. })
  121. } else {
  122. // dispatch({ type: 'init', params: { elementType: 'empty' } })
  123. }
  124. }, [id])
  125. const config = {
  126. title: '警告!',
  127. cancelText: '取消',
  128. okText: '确定',
  129. onOk: () => { hideModal() },
  130. content: (
  131. <>
  132. <div>不会保存您所做的更改,确定关闭?</div>
  133. </>
  134. ),
  135. };
  136. /** 获取选中内容对应图片视频尺寸大小 */
  137. useEffect(() => {
  138. let selectData = content?.find((item: Content) => item.comptActive)
  139. if (!selectData) {
  140. selectData = componentItem?.find((item: { comptActive: boolean }) => item.comptActive)
  141. }
  142. if (selectData?.elementType === "TOP_IMAGE") {
  143. if (selectData?.adLocation === 'sns') { // 朋友圈信息流
  144. if (selectData?.outerStyle === 0) { // 常规广告
  145. init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: 800, height: 800 }]], maxSize: 300 * 1024 })
  146. } else { // 卡片广告
  147. init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: 800, height: 450 }]], maxSize: 300 * 1024 })
  148. }
  149. } else { // 公众号及其他
  150. init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: 800, height: 800 }], [{ relation: '=', width: 800, height: 450 }], [{ relation: '=', width: 800, height: 640 }], [{ relation: '=', width: 640, height: 800 }]], maxSize: 300 * 1024 })
  151. }
  152. } else if (selectData?.elementType === 'IMAGE') { // 内容图片
  153. init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: 750, height: null }, { relation: '<=', width: null, height: 1536 }]], maxSize: 300 * 1024 })
  154. } else if (selectData?.elementType === 'TOP_SLIDER') { // 轮播图
  155. init({ mediaType: 'IMG', num: selectData?.imageUrlList?.length || 3, cloudSize: [[{ relation: '=', width: 800, height: 800 }]], maxSize: 300 * 1024 })
  156. } else if (selectData?.elementType === 'TOP_VIDEO') { // 视频
  157. if (selectData?.adLocation === 'sns') { // 朋友圈信息流
  158. if (selectData?.outerStyle === 0) { // 常规广告
  159. init({ mediaType: 'VIDEO', cloudSize: [[{ relation: '=', width: 640, height: 480 }], [{ relation: '=', width: 640, height: 360 }], [{ relation: '=', width: 750, height: 1334 }], [{ relation: '=', width: 720, height: 1280 }]], maxSize: 20 * 1024 * 1024 })
  160. } else { // 卡片广告
  161. init({ mediaType: 'VIDEO', cloudSize: [[{ relation: '=', width: 750, height: null }, { relation: '<=', width: null, height: 1536 }]], maxSize: 20 * 1024 * 1024 })
  162. }
  163. } else { // 公众号及其它
  164. init({ mediaType: 'VIDEO', cloudSize: [[{ relation: '=', width: 750, height: null }, { relation: '<=', width: null, height: 1536 }]], maxSize: 20 * 1024 * 1024 })
  165. }
  166. } else if (selectData?.elementType === 'IMAGE_TEXT') { // 图文复合组件 1个
  167. if (selectData?.imageTextItem?.length === 1) {
  168. init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: 360, height: 360 }]], maxSize: 300 * 1024 })
  169. } else if (selectData?.imageTextItem?.length === 2) {
  170. init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: 480, height: 480 }]], maxSize: 300 * 1024 })
  171. }
  172. } else if (selectData?.elementType === 'FLOAT_BUTTON') {
  173. init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: 96, height: 96 }]], maxSize: 300 * 1024 })
  174. }
  175. }, [content])
  176. const [dragging, setDragging] = useState<string | null>(null);
  177. const getDragProps = useDrag({
  178. onDragStart: (data) => {
  179. setDragging(data);
  180. },
  181. onDragEnd: () => {
  182. setDragging(null);
  183. },
  184. });
  185. // 头部内容拖到接收区
  186. const [dropProps] = useDrop({
  187. onDom: (con: string, e) => { // 头部
  188. let newCon = content?.map((item: TopImg | TopVideo | TopSlider) => {
  189. return { ...item, comptActive: false }
  190. })
  191. let topCon = newCon[0]
  192. topCon.type = con
  193. topCon.comptActive = true
  194. let conContent: TopImg | TopVideo | TopSlider
  195. if (con === 'TOP_SLIDER') {
  196. conContent = { ...topsliderContent, comptActive: true } as TopSlider
  197. } else if (con === 'TOP_IMAGE') {
  198. conContent = { ...topimgContent, comptActive: true } as TopImg
  199. } else if (con === 'TOP_VIDEO') {
  200. conContent = { ...topvideoNewContent, comptActive: true } as TopVideo
  201. } else {
  202. return
  203. }
  204. topCon = { ...conContent }
  205. newCon[0] = topCon
  206. dispatch({ type: 'setCon', params: { content: newCon } })
  207. },
  208. });
  209. const [draggingCon, setDraggingCon] = useState<string | null>(null);
  210. const getDragPropsCon = useDrag({
  211. onDragStart: (data) => {
  212. setDraggingCon(data);
  213. },
  214. onDragEnd: () => {
  215. setDraggingCon(null);
  216. },
  217. });
  218. // 内容拖到接收区
  219. const [dropConProps, { isHovering: isHoveringCon }] = useDrop({
  220. onDom: (con: string, e) => { // 内容
  221. let newCon: Content[] = content?.map((item: Text | ImgProps | GhButton) => { // | LinkButton | WxAutoButton
  222. return { ...item, comptActive: false }
  223. })
  224. let newConItem = componentItem?.map((item: Floatbutton) => {
  225. return { ...item, comptActive: false }
  226. })
  227. if (con === 'TEXT') {
  228. newCon.push({ ...txtContent, comptActive: true } as any)
  229. } else if (con === 'IMAGE') {
  230. newCon.push({ ...imgContent, comptActive: true } as any)
  231. } else if (con === 'GH') {
  232. newCon.push({ ...ghContent, comptActive: true } as any)
  233. // } else if (con === 'JumpLink') {
  234. // newCon.push({ ...linkContent, comptActive: true } as any)
  235. } else if (con === 'IMAGE_TEXT') {
  236. newCon.push({ ...imageTextGh, comptActive: true } as any)
  237. } else if (con === 'ENTERPRISE_WX') {
  238. newCon.push({ ...wxAutoContent, comptActive: true } as any)
  239. } else if (con === 'FLOAT_BUTTON') {
  240. newConItem.push({ ...floatbuttonContent, comptActive: true })
  241. setDraggingCon(null);
  242. } else {
  243. return
  244. }
  245. dispatch({ type: 'setCon', params: { content: newCon } })
  246. dispatchGlobal({ type: "setConItem", params: { componentItem: newConItem } })
  247. },
  248. });
  249. /** 选中设置 */
  250. const installActive = useCallback((e, index: number) => {
  251. e.stopPropagation(); e.preventDefault();
  252. let newCon = content?.map((item: any) => {
  253. return { ...item, comptActive: false }
  254. })
  255. let newConItem = componentItem?.map((item: { elementType: string }) => {
  256. return { ...item, comptActive: false }
  257. })
  258. setImgTextButtonShow(false)
  259. if (index === 99999) {
  260. newConItem = newConItem?.map((item: { elementType: string }) => {
  261. if (item.elementType === 'FLOAT_BUTTON') {
  262. return { ...item, comptActive: true }
  263. }
  264. return item
  265. })
  266. } else {
  267. newCon[index].comptActive = true
  268. }
  269. dispatch({ type: 'setCon', params: { content: newCon } })
  270. dispatchGlobal({ type: "setConItem", params: { componentItem: newConItem } })
  271. }, [content, imgTextButtonShow, componentItem])
  272. /** 清除选中 */
  273. const installActiveNull = useCallback((e) => {
  274. e.stopPropagation(); e.preventDefault();
  275. let newCon = content?.map((item: any) => {
  276. return { ...item, comptActive: false }
  277. })
  278. setImgTextButtonShow(false)
  279. dispatch({ type: 'setCon', params: { content: newCon } })
  280. let newConItem = componentItem?.map((item: { widgetTypeV2: string }) => {
  281. return { ...item, comptActive: false }
  282. })
  283. dispatchGlobal({ type: "setConItem", params: { componentItem: newConItem } })
  284. }, [content])
  285. /** 内容功能按钮区 */
  286. const handleBtn = useCallback((type: string, index: number) => {
  287. let newContent = JSON.parse(JSON.stringify(content))
  288. switch (type) {
  289. case 'lower': // 下移动
  290. dispatch({ type: 'setCon', params: { content: arrayMove(content, index, index + 1) } })
  291. break;
  292. case 'upper': // 上移动
  293. dispatch({ type: 'setCon', params: { content: arrayMove(content, index, index - 1) } })
  294. break;
  295. case 'IMAGE': // 图片
  296. newContent.splice(index, 0, { ...imgContent });
  297. dispatch({ type: 'setCon', params: { content: newContent } })
  298. break;
  299. case 'TEXT': // 文本
  300. newContent.splice(index, 0, { ...txtContent });
  301. dispatch({ type: 'setCon', params: { content: newContent } })
  302. break;
  303. case 'GH': // 关注公众号按钮
  304. newContent.splice(index, 0, { ...ghContent });
  305. dispatch({ type: 'setCon', params: { content: newContent } })
  306. break;
  307. // case 'link': // 跳转链接按钮
  308. // newContent.splice(index, 0, { ...linkContent });
  309. // dispatch({ type: 'setCon', params: { content: newContent } })
  310. // break;
  311. case 'IMAGE_TEXT': // 图文复合组件
  312. newContent.splice(index, 0, JSON.parse(JSON.stringify(imageTextGh)));
  313. dispatch({ type: 'setCon', params: { content: newContent } })
  314. break;
  315. case 'ENTERPRISE_WX': // 图文复合组件
  316. newContent.splice(index, 0, { ...wxAutoContent });
  317. dispatch({ type: 'setCon', params: { content: newContent } })
  318. break;
  319. }
  320. }, [content])
  321. /** 头部删除 */
  322. const topDelType = useCallback(() => {
  323. setDragging(null);
  324. content[0] = {
  325. elementType: 'empty',
  326. comptActive: false
  327. }
  328. dispatch({ type: 'setCon', params: { content } })
  329. }, [content, dragging])
  330. /** 内容删除 */
  331. const delType = useCallback((e, index: number) => {
  332. e.stopPropagation(); e.preventDefault();
  333. setImgTextButtonShow(false)
  334. if (index === 99999) { // 删除悬浮组件
  335. let newContentItem = JSON.parse(JSON.stringify(componentItem))
  336. newContentItem = newContentItem?.filter((item: { elementType: string }) => item.elementType !== 'FLOAT_BUTTON')
  337. dispatchGlobal({ type: 'setConItem', params: { componentItem: JSON.parse(JSON.stringify(newContentItem)) } })
  338. } else {
  339. let newContent = JSON.parse(JSON.stringify(content))
  340. newContent?.splice(index, 1)
  341. dispatch({ type: 'setCon', params: { content: JSON.parse(JSON.stringify(newContent)) } })
  342. }
  343. }, [content, componentItem, imgTextButtonShow])
  344. const onSortEnd = ({ oldIndex, newIndex }: { oldIndex: number, newIndex: number }) => {
  345. dispatch({ type: 'setCon', params: { content: arrayMove(content, oldIndex, newIndex) } })
  346. }
  347. // 基础内容
  348. const comptCon = () => {
  349. if (content?.length === 0) {
  350. return null
  351. } else {
  352. return <SortableList axis='y' onSortEnd={onSortEnd} useDragHandle isFloatButton={componentItem?.some((item: { elementType: string }) => item.elementType === 'FLOAT_BUTTON')}>
  353. {content.map((value: Text | ImgProps | GhButton, index: number) => {
  354. if (value?.elementType === 'IMAGE') {
  355. return <SortableItemImg key={`item-${value.elementType}-${index}`} index={index} data={{ length: content?.length, index }} item={value} click={(e: any) => { installActive(e, index) }} del={(e: any) => { delType(e, index) }} upload={(num: number) => { clickUpdateImg(num) }} handleBtn={handleBtn} />
  356. } else if (value?.elementType === 'TEXT') {
  357. return <SortableItemText key={`item-${value.elementType}-${index}`} index={index} data={{ length: content?.length, index }} item={value} click={(e: any) => { installActive(e, index) }} del={(e: any) => { delType(e, index) }} pageBackColor={pageBackColor} handleBtn={handleBtn} />
  358. } else if (value?.elementType === 'GH') {
  359. return <SortableItemFollowAcc key={`item-${value.elementType}-${index}`} index={index} data={{ length: content?.length, index }} item={value} click={(e: any) => { installActive(e, index) }} del={(e: any) => { delType(e, index) }} handleBtn={handleBtn} />
  360. } else if (value?.elementType === 'ENTERPRISE_WX') {
  361. return <SortableItemWxAuto key={`item-${value.elementType}-${index}`} index={index} data={{ length: content?.length, index }} item={value} click={(e: any) => { installActive(e, index) }} del={(e: any) => { delType(e, index) }} handleBtn={handleBtn} />
  362. // } else if (value?.elementType === 'link') {
  363. // return <SortableItemJumpLink key={`item-${value.elementType}-${index}`} index={index} data={{ length: content?.length, index }} item={value} click={(e: any) => { installActive(e, index) }} del={(e: any) => { delType(e, index) }} handleBtn={handleBtn} />
  364. } else if (value?.elementType === 'IMAGE_TEXT') {
  365. return <SortableItemImgText key={`item-${value.elementType}-${index}`} index={index} data={{ length: content?.length, index }} item={value} click={(e: any) => { installActive(e, index) }} del={(e: any) => { delType(e, index) }} handleBtn={handleBtn} />
  366. } else {
  367. return null
  368. }
  369. })}
  370. <div className={`comptCon ${isHoveringCon && 'hovering'} ${draggingCon && 'draggingCon'}`} {...dropConProps}>{(isHoveringCon || draggingCon) && '请拖至此处'}</div>
  371. {componentItem?.some((item: { elementType: string }) => item.elementType === 'FLOAT_BUTTON') && <SortableItemFloatbutton index={99999} item={componentItem.find((item: { elementType: string }) => item.elementType === 'FLOAT_BUTTON')} click={(e: any) => { installActive(e, 99999) }} del={(e: any) => { delType(e, 99999) }} />}
  372. </SortableList>
  373. }
  374. }
  375. /** 设置start */
  376. const setCon = useCallback((key: string, value: any) => {
  377. let newContent = JSON.parse(JSON.stringify(content))
  378. let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
  379. let oldContent = newContent
  380. if (selectIndex !== -1) {
  381. let selectCon = oldContent[selectIndex]
  382. selectCon[key] = value
  383. if (selectCon?.elementType === 'TOP_VIDEO' && (key === 'adLocation' || key === 'outerStyle')) {
  384. selectCon['videoUrl'] = ''
  385. }
  386. let newSelectCon = { ...selectCon }
  387. oldContent[selectIndex] = newSelectCon
  388. dispatch({ type: 'setCon', params: { content: [...oldContent] } })
  389. }
  390. }, [content, goodsCount])
  391. /** 设置end */
  392. // 顶部组件
  393. const topCon = useMemo(() => {
  394. let { imageUrl, elementType, activeIndex, imageUrlList, videoUrl } = content[0]
  395. return <>
  396. {
  397. elementType === 'TOP_IMAGE' ? <>
  398. <div className={`compt componentType41 ${content[0]?.comptActive && 'comptActive'}`} onClick={(e) => { installActive(e, 0) }}>
  399. <div className={'componentWrap'}>
  400. <div className={'componentContent'}>
  401. {imageUrl ? <img src={imageUrl} style={{ display: 'block', width: '100%', margin: 0 }} /> : <div className={'default'} style={{ width: 375, height: 300, margin: 0 }}>
  402. <div className={'defaultIcon'} style={{ marginTop: 80 }}>
  403. <Topimg />
  404. </div>
  405. </div>}
  406. </div>
  407. </div>
  408. {!imageUrl && <div className={'comptUpload'} style={{ margin: 0 }}>
  409. <button style={{ marginTop: 150 }} className={'comptEditButton'} onClick={() => { clickUpdateImg(1) }}>上传图片</button>
  410. </div>}
  411. <section className={'comptEditBtns'}>
  412. <div className={'comptEditBtnsInner'}>
  413. {imageUrl && <button onClick={() => { editSelectImg(imageUrl) }}><EditSvg /></button>}
  414. <button onClick={topDelType}><DeleteOutlined /></button>
  415. </div>
  416. </section>
  417. </div>
  418. </> :
  419. elementType === 'TOP_SLIDER' ? <>
  420. <div className={`compt componentType101 ${content[0]?.comptActive && 'comptActive'}`} onClick={(e) => { installActive(e, 0) }}>
  421. <div className={'componentWrap'}>
  422. <div className={'componentContent'}>
  423. {imageUrlList?.length > 0 ? <div style={{ position: 'relative', width: 375, height: 375 }}>
  424. {imageUrlList?.map((imgUrl: any, index: number) => {
  425. if (imgUrl) {
  426. return <img src={imgUrl} key={index} style={{ maxWidth: '100%', position: 'absolute', display: 'block', zIndex: activeIndex === index ? 1 : 0 }} />
  427. } else {
  428. return <div className="default" key={index} style={{ width: 375, zIndex: activeIndex === index ? 1 : 0, height: '100%', position: 'absolute', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
  429. <div className="defaultIcon">
  430. <SliderImgSvg />
  431. </div>
  432. </div>
  433. }
  434. })}
  435. <div className={'sliderD'}>
  436. {imageUrlList.map((item: any, index: number) => <i key={index} style={activeIndex === index ? { backgroundColor: 'rgba(0, 0, 0, .4)' } : {}}></i>)}
  437. </div>
  438. </div> : <div style={{ position: 'relative', height: 222, width: 375 }} className={'sliderCon'}>
  439. <div className={'default'}>
  440. <div className={'defaultIcon'}>
  441. <Topslider />
  442. </div>
  443. </div>
  444. <div className={'sliderD'}>
  445. {imageUrlList.fill('').map((item: any, index: number) => <i key={index} style={activeIndex === index ? { backgroundColor: 'rgba(0, 0, 0, .4)' } : {}}></i>)}
  446. </div>
  447. </div>}
  448. </div>
  449. </div>
  450. <section className={'comptEditBtns'}>
  451. <div className={'comptEditBtnsInner'}>
  452. <button onClick={topDelType}><DeleteOutlined /></button>
  453. </div>
  454. </section>
  455. </div>
  456. </> :
  457. elementType === 'TOP_VIDEO' ? <>
  458. <div className={`compt componentType61 ${content[0]?.comptActive && 'comptActive'}`} onClick={(e) => { installActive(e, 0) }}>
  459. <div className={'componentWrap'}>
  460. <div className={'componentContent'}>
  461. {
  462. videoUrl ? <div className="videoPlay">
  463. <video src={videoUrl} style={{ display: 'block', width: '100%', margin: 0 }} />
  464. <span></span>
  465. </div>
  466. : <div className={'default'} style={{ width: 375, height: 300, margin: 0 }}>
  467. <div className={'defaultIcon'} style={{ marginTop: 80 }}>
  468. <Topvideo />
  469. </div>
  470. </div>
  471. }
  472. </div>
  473. </div>
  474. {!videoUrl && <div className={'comptUpload'} style={{ margin: 0 }}>
  475. <button style={{ marginTop: 150 }} className={'comptEditButton'} onClick={() => { clickUpdateVideo() }}>上传视频</button>
  476. </div>}
  477. <section className={'comptEditBtns'}>
  478. <div className={'comptEditBtnsInner'}>
  479. {videoUrl && <button onClick={() => { clickUpdateVideo() }}><EditSvg /></button>}
  480. <button onClick={topDelType}><DeleteOutlined /></button>
  481. </div>
  482. </section>
  483. </div>
  484. </> :
  485. <div className={`compt topComptArea ${dragging ? 'dragging' : ''}`} {...dropProps}>
  486. <TopNullBack />
  487. {dragging ? <div className="topAreaTitle" style={{ marginTop: 30 }}>
  488. 拖至此处
  489. </div> : <>
  490. <p className={'topAreaTitle'}>顶部组件区</p>
  491. <div className={'desc'}>在左上方,选择顶部组件添加到此处</div>
  492. </>}
  493. </div>
  494. }
  495. </>
  496. }, [content, state, dragging])
  497. /** 图标开启与关闭 */
  498. const iconHandle = (e: boolean) => {
  499. let newContent = JSON.parse(JSON.stringify(content))
  500. let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
  501. let oldContent = newContent
  502. if (selectIndex !== -1) {
  503. let selectCon = oldContent[selectIndex]
  504. selectCon['useIcon'] = e ? 1 : 0
  505. oldContent[selectIndex] = selectCon
  506. dispatch({ type: 'setCon', params: { content: [...oldContent] } })
  507. }
  508. }
  509. /** 视频切换广告位 */
  510. const changeAdLocation = (value: 'sns' | 'gh') => {
  511. let newContent = JSON.parse(JSON.stringify(content))
  512. let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
  513. let oldContent = newContent
  514. if (selectIndex !== -1) {
  515. let selectCon = oldContent[selectIndex]
  516. selectCon['adLocation'] = value
  517. if (value === 'gh') {
  518. selectCon['styleType'] = 1
  519. } else {
  520. selectCon['styleType'] = 0
  521. }
  522. selectCon['videoUrl'] = ''
  523. oldContent[selectIndex] = selectCon
  524. dispatch({ type: 'setCon', params: { content: [...oldContent] } })
  525. }
  526. }
  527. /** 视频切换外层样式 */
  528. const changeOuterLayout = (value: string) => {
  529. let newContent = JSON.parse(JSON.stringify(content))
  530. let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
  531. let oldContent = newContent
  532. if (selectIndex !== -1) {
  533. let selectCon = oldContent[selectIndex]
  534. selectCon['viewType'] = value
  535. let newSelectCon: any
  536. if (value === '0') {
  537. let { outerUseTopMaterial, streamDisplayWidth, streamDisplayHeight, streamVideoThumb, streamVideoUrl, streamDisplayType, streamThumbMd5, streamVideoMd5, displayType, ...clearSelectCon } = selectCon
  538. newSelectCon = {
  539. ...clearSelectCon
  540. }
  541. } else {
  542. newSelectCon = {
  543. ...selectCon,
  544. outerUseTopMaterial: '0',
  545. styleType: '0'
  546. }
  547. }
  548. oldContent[selectIndex] = newSelectCon
  549. dispatch({ type: 'setCon', params: { content: [...oldContent] } })
  550. }
  551. }
  552. /** 轮播图选择点击切换图片 多张图片 */
  553. const sliderSelect = (index: number, count: number) => {
  554. let newContent = JSON.parse(JSON.stringify(content))
  555. let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
  556. if (selectIndex !== -1) {
  557. let selectCon = newContent[selectIndex]
  558. let imgList: any[] = []
  559. selectCon?.imageUrlList?.forEach((item: string) => {
  560. if (item) {
  561. imgList.push({ url: item })
  562. }
  563. })
  564. setSliderImgContent(imgList)
  565. }
  566. setCon('activeIndex', index)
  567. setCcType(3)
  568. setSelectImgVisible(true)
  569. }
  570. /** 轮播图图片数量设置 */
  571. const sliderImgNum = useCallback((num: string) => {
  572. let newContent = JSON.parse(JSON.stringify(content))
  573. let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
  574. let oldContent = newContent
  575. if (selectIndex !== -1) {
  576. let selectCon = oldContent[selectIndex]
  577. let { imageUrlList } = selectCon
  578. if (imageUrlList?.length > Number(num)) { // 减少
  579. selectCon['imageUrlList'] = [...imageUrlList?.splice(0, Number(num))]
  580. } else { // 增加
  581. let newGroup = Array(Number(num) - imageUrlList?.length).fill('').map(() => "")
  582. imageUrlList = [...imageUrlList, ...newGroup]
  583. selectCon['imageUrlList'] = imageUrlList
  584. }
  585. oldContent[selectIndex] = { ...selectCon }
  586. dispatch({ type: 'setCon', params: { content: [...oldContent] } })
  587. }
  588. }, [content])
  589. /** 轮播图位置拖动切换顺序 */
  590. const onSortEndSlider = useCallback(({ oldIndex, newIndex }: { oldIndex: number, newIndex: number }) => {
  591. let newContent = JSON.parse(JSON.stringify(content))
  592. let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
  593. let oldContent = newContent
  594. if (selectIndex !== -1) {
  595. let selectCon = oldContent[selectIndex]
  596. let { imageUrlList } = selectCon
  597. imageUrlList = arrayMove(imageUrlList, oldIndex, newIndex)
  598. selectCon['imageUrlList'] = imageUrlList
  599. oldContent[selectIndex] = { ...selectCon }
  600. dispatch({ type: 'setCon', params: { content: [...oldContent] } })
  601. }
  602. }, [content])
  603. /** 图文单个设置 */
  604. const onShelfnewTxtCon = useCallback((value: string, parameter: string) => {
  605. let newContent = JSON.parse(JSON.stringify(content))
  606. let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
  607. let oldContent = newContent
  608. if (selectIndex !== -1) {
  609. let selectCon = oldContent[selectIndex]
  610. let length = selectCon?.imageTextItem?.length || 0;
  611. if (length === 2) {
  612. let imageTextItem = selectCon?.imageTextItem[goodsCount]
  613. imageTextItem[parameter] = value
  614. } else if (length === 1) {
  615. let imageTextItem = selectCon?.imageTextItem[0]
  616. imageTextItem[parameter] = value
  617. }
  618. oldContent[selectIndex] = { ...selectCon }
  619. dispatch({ type: 'setCon', params: { content: [...oldContent] } })
  620. }
  621. }, [content, goodsCount])
  622. // 设置图文跳转链接按钮
  623. const onSetShelfnewButton = useCallback((value: string) => {
  624. let newContent = JSON.parse(JSON.stringify(content))
  625. let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
  626. let oldContent = newContent
  627. if (selectIndex !== -1) {
  628. let selectCon = oldContent[selectIndex]
  629. let length = selectCon?.imageTextItem?.length || 0;
  630. if (length === 2) {
  631. let imageTextItem = selectCon?.imageTextItem[goodsCount]
  632. imageTextItem.subElemType = value
  633. switch(value) {
  634. case 'GH':
  635. imageTextItem.content = JSON.parse(JSON.stringify(ghITItemContent))
  636. break;
  637. case 'ENTERPRISE_WX':
  638. imageTextItem.content = JSON.parse(JSON.stringify(wxAutoITItemContent))
  639. break
  640. }
  641. } else if (length === 1) {
  642. let imageTextItem = selectCon?.imageTextItem[0]
  643. imageTextItem.subElemType = value
  644. switch(value) {
  645. case 'GH':
  646. imageTextItem.content = JSON.parse(JSON.stringify(ghITItemContent))
  647. break;
  648. case 'ENTERPRISE_WX':
  649. imageTextItem.content = JSON.parse(JSON.stringify(wxAutoITItemContent))
  650. break
  651. }
  652. }
  653. oldContent[selectIndex] = { ...selectCon }
  654. dispatch({ type: 'setCon', params: { content: [...oldContent] } })
  655. }
  656. }, [content, goodsCount])
  657. // 配置图文跳转链接按钮字段
  658. const onSetShelfnewButtonField = useCallback((field: string, value: string | number) => {
  659. let newContent = JSON.parse(JSON.stringify(content))
  660. let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
  661. let oldContent = newContent
  662. if (selectIndex !== -1) {
  663. let selectCon = oldContent[selectIndex]
  664. let length = selectCon.imageTextItem.length
  665. if (length === 2) {
  666. let content = selectCon.imageTextItem[goodsCount].content
  667. content[field] = value
  668. } else if (length === 1) {
  669. let content = selectCon.imageTextItem[0].content
  670. content[field] = value
  671. }
  672. oldContent[selectIndex] = { ...selectCon }
  673. dispatch({ type: 'setCon', params: { content: [...oldContent] } })
  674. }
  675. }, [content, goodsCount])
  676. // 设置图文复合组件类型
  677. const setShelfnewType = useCallback((value: string) => {
  678. let newContent = JSON.parse(JSON.stringify(content))
  679. let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
  680. if (selectIndex !== -1) {
  681. if (value === '2') {
  682. newContent[selectIndex].imageTextItem = [
  683. JSON.parse(JSON.stringify({
  684. ...imageTextItem,
  685. content: {
  686. ...ghITItemContent
  687. }
  688. })),
  689. JSON.parse(JSON.stringify({
  690. ...imageTextItem,
  691. content: {
  692. ...ghITItemContent
  693. }
  694. }))
  695. ]
  696. } else if (value === '1') {
  697. newContent[selectIndex].alignMode = 0
  698. let imageTextItem = newContent[selectIndex].imageTextItem
  699. let newImageTextItem = imageTextItem.splice(0, 1)
  700. newContent[selectIndex].imageTextItem = newImageTextItem
  701. }
  702. dispatch({ type: 'setCon', params: { content: [...newContent] } })
  703. }
  704. }, [content])
  705. // 设置悬浮弹窗
  706. const setGlobalComponentItem = (key: string, value: any, isDel = false) => {
  707. let newConItem = JSON.parse(JSON.stringify(componentItem))
  708. let selectIndex = newConItem?.findIndex((item: Content) => item.comptActive)
  709. let oldContent = newConItem
  710. if (selectIndex !== -1) {
  711. let selectCon = oldContent[selectIndex]
  712. // 设置悬浮窗 转化按钮
  713. if (key === 'componentItem' && (value === 'GH' || value === 'ENTERPRISE_WX')) {
  714. if (value === 'GH') {
  715. selectCon[key] = floatbuttonBtTypeGh
  716. } else if (value === 'ENTERPRISE_WX') {
  717. selectCon[key] = floatbuttonBtTypeWxAuto
  718. }
  719. } else {
  720. if (!isDel) {
  721. selectCon[key] = value
  722. if (key === 'styleType') {
  723. if (value === 0) {
  724. selectCon['imageUrl'] = ""
  725. selectCon['desc'] = selectCon?.desc || ""
  726. } else if (value === 1) {
  727. delete selectCon['imageUrl']
  728. selectCon['desc'] = selectCon?.desc || ""
  729. } else if (value === 2) {
  730. delete selectCon['imageUrl']
  731. delete selectCon['desc']
  732. }
  733. selectCon[key] = value
  734. }
  735. } else {
  736. delete selectCon[key]
  737. }
  738. }
  739. dispatchGlobal({ type: 'setConItem', params: { componentItem: JSON.parse(JSON.stringify(oldContent)) } })
  740. }
  741. }
  742. // 选中设置
  743. const rightCon = () => {
  744. let selectCon = content?.find((item: Content) => item.comptActive)
  745. if (!selectCon) {
  746. selectCon = componentItem?.find((item: { comptActive: boolean }) => item.comptActive)
  747. }
  748. if (selectCon) {
  749. let { elementType, adLocation, outerStyle, imageUrlList, imageUrl, text, fontColor, fontSize, fontStyle, textAlignment,
  750. paddingTop, paddingBottom, fastFollow, btnTitle, btnFontType, btnBorderColorTheme, btnBgColorTheme, useIcon,
  751. styleType, imageTextItem, initHeight, alignMode, activeIndex, title, desc, titleColor, descColor, componentItem, appearType, disappearType } = selectCon
  752. let imgTextData: ImageTextItem = {
  753. borderColor: '#e5e5e5',
  754. titleColor: '#353535',
  755. descColor: '#b2b2b2',
  756. bgColor: '#ffffff',
  757. jumpMode: 'btn_jump',
  758. imageUrl: '',
  759. title: '',
  760. desc: '',
  761. subElemType: 'GH',
  762. content: {
  763. btnTitle: '关注公众号',
  764. fontColor: '#FFFFFF',
  765. btnBgColorTheme: '#07C160',
  766. btnBorderColorTheme: '#FFFFFF',
  767. btnFontType: 0,
  768. useIcon: 0,
  769. fastFollow: 1,
  770. paddingTop: 28,
  771. paddingBottom: 28,
  772. }
  773. }
  774. let imageTextItemIndex = 1;
  775. let custorGroup = []
  776. if (elementType === 'enterprise_wx_auto') {
  777. custorGroup = selectCon?.custorData || []
  778. } else if (elementType === 'IMAGE_TEXT') {
  779. imageTextItemIndex = imageTextItem?.length || 1
  780. if (imageTextItemIndex === 1) { // 图文复合组件 单个
  781. imgTextData = imageTextItem[0]
  782. } else if (imageTextItemIndex === 2) {
  783. imgTextData = imageTextItem[goodsCount]
  784. }
  785. }
  786. return <>
  787. {
  788. elementType === 'TOP_IMAGE' ? <div className="widget">
  789. <div className="caption section">
  790. <div className="caption-title">顶部组件:图片</div>
  791. </div>
  792. <div className="form section">
  793. <div className="form-caption">广告位与样式</div>
  794. <div className="adui-form-item">
  795. <div className="adui-form-label">广告位</div>
  796. <div className="adui-form-control">
  797. <Radio.Group onChange={(e) => { setCon('adLocation', e.target.value) }} value={adLocation} size='small'>
  798. <Radio value='sns'>朋友圈信息流</Radio>
  799. <Radio value='gzh'>公众号及其他</Radio>
  800. </Radio.Group>
  801. </div>
  802. </div>
  803. {adLocation === 'sns' && <div className="adui-form-item">
  804. <div className="adui-form-label">外层样式</div>
  805. <div className="adui-form-control">
  806. <Select value={outerStyle} style={{ width: 100 }} onChange={(e) => { setCon('outerStyle', e) }}>
  807. <Option value={0}>常规广告</Option>
  808. <Option value={1}>卡片广告</Option>
  809. </Select>
  810. </div>
  811. </div>}
  812. </div>
  813. <div className="form section">
  814. <div className="form-caption">素材设置</div>
  815. <div className="adui-form-item" style={{ alignItems: 'flex-start' }}>
  816. <div className="adui-form-label">图片素材</div>
  817. <div className="adui-form-control">
  818. <div className={`upload-img-item ${imageUrl ? 'upload-img-item_uploaded' : ''}`}>
  819. {imageUrl ? <div className="upload-img-item-inner" style={{ backgroundImage: `url(${imageUrl})` }}>
  820. <div className='upload-img-item-action' onClick={() => { clickUpdateImg(1) }}>
  821. <RetweetOutlined />
  822. </div>
  823. </div> : <div className="upload-img-item-inner" onClick={() => { clickUpdateImg(1) }}>
  824. <PlusOutlined />
  825. </div>}
  826. </div>
  827. </div>
  828. </div>
  829. </div>
  830. </div>
  831. :
  832. elementType === 'TOP_SLIDER' ? <div className="widget">
  833. <div className="caption section">
  834. <div className="caption-title">顶部组件:轮播图</div>
  835. </div>
  836. <div className="form section">
  837. <div className="form-caption">素材设置</div>
  838. <div className="adui-form-item">
  839. <div className="adui-form-label">图片数量</div>
  840. <div className="adui-form-control">
  841. <Radio.Group onChange={(e) => { sliderImgNum(e.target.value) }} value={imageUrlList?.length || 3} size='small'>
  842. <Radio.Button value={3}>3张</Radio.Button>
  843. <Radio.Button value={4}>4张</Radio.Button>
  844. <Radio.Button value={6}>6张</Radio.Button>
  845. </Radio.Group>
  846. </div>
  847. </div>
  848. </div>
  849. <div className="form section">
  850. <div className="form-caption">上传素材</div>
  851. <SortableUlList axis='xy' onSortEnd={onSortEndSlider} useDragHandle>
  852. {imageUrlList?.map((item: any, index: number) => {
  853. if (item) {
  854. return <SortableItemLi key={`slider-${index}`} index={index} isActive={activeIndex === index} pureImageUrl={item} click={() => { sliderSelect(index, imageUrlList.length) }} setActiveIndex={() => { setCon('activeIndex', index) }}></SortableItemLi>
  855. } else {
  856. return <SortableItemNoLi key={`slider-${index}`} index={index} isActive={activeIndex === index} click={() => { sliderSelect(index, imageUrlList.length) }}></SortableItemNoLi>
  857. }
  858. })}
  859. </SortableUlList>
  860. </div>
  861. </div>
  862. :
  863. elementType === 'TOP_VIDEO' ? <div className="widget">
  864. <div className="caption section">
  865. <div className="caption-title">顶部组件:视频</div>
  866. </div>
  867. <div className="form section">
  868. <div className="form-caption">广告位与样式</div>
  869. <div className="adui-form-item">
  870. <div className="adui-form-label">广告位</div>
  871. <div className="adui-form-control">
  872. <Radio.Group onChange={(e) => { changeAdLocation(e.target.value) }} value={adLocation} size='small'>
  873. <Radio value='sns'>朋友圈信息流</Radio>
  874. <Radio value='gzh'>公众号及其他</Radio>
  875. </Radio.Group>
  876. </div>
  877. </div>
  878. {adLocation === 'sns' && <div className="adui-form-item">
  879. <div className="adui-form-label">外层样式</div>
  880. <div className="adui-form-control">
  881. <Select size="small" value={outerStyle} style={{ width: 100 }} onChange={(e) => { changeOuterLayout(e) }}>
  882. <Option value={0}>常规广告</Option>
  883. <Option value={1} disabled>卡片广告</Option>
  884. </Select>
  885. </div>
  886. </div>}
  887. {/* {
  888. adLocation === 'sns' && outerStyle === 0 && <div className="adui-form-item">
  889. <div className="adui-form-label">视频类型</div>
  890. <div className="adui-form-control">
  891. <Radio.Group onChange={(e) => { viewTypeChange(e.target.value, 1) }} value={styleType} size='small'>
  892. <Radio value='0'>横板视频</Radio>
  893. <Radio value='1'>竖版视频</Radio>
  894. </Radio.Group>
  895. </div>
  896. </div>
  897. }
  898. {
  899. adLocation === 'sns' && outerStyle === 1 && <div className="adui-form-item">
  900. <div className="adui-form-label">外层素材</div>
  901. <div className="adui-form-control">
  902. <Checkbox onChange={(e: CheckboxChangeEvent) => { outerUseTopMaterialHandle(e.target.checked) }} checked={outerUseTopMaterial === '0' ? false : true}>顶部素材不用于广告外层</Checkbox>
  903. </div>
  904. </div>
  905. } */}
  906. {/* {
  907. outerUseTopMaterial === '1' && <div className="adui-form-item">
  908. <div className="adui-form-label">视频类型</div>
  909. <div className="adui-form-control">
  910. <Radio.Group onChange={(e) => { viewTypeChange(e.target.value, 2) }} value={type} size='small'>
  911. <Radio value='61'>短视频</Radio>
  912. <Radio value='62'>长视频</Radio>
  913. </Radio.Group>
  914. </div>
  915. </div>
  916. } */}
  917. {/* {
  918. adLocation === 'gh' && <div className="adui-form-item">
  919. <div className="adui-form-label">视频类型</div>
  920. <div className="adui-form-control">
  921. <Radio.Group onChange={(e) => { viewTypeChange(e.target.value, 2) }} value={type} size='small'>
  922. <Radio value='61'>短视频</Radio>
  923. <Radio value='62'>长视频</Radio>
  924. </Radio.Group>
  925. </div>
  926. </div>
  927. } */}
  928. {adLocation === 'sns' && styleType === '1' && <div className="adui-form-item" style={{ alignItems: 'flex-start' }}>
  929. <div className="adui-form-label" style={{ marginTop: 2 }}>视频尺寸</div>
  930. <div className="adui-form-control">
  931. <Radio.Group value={initHeight} onChange={(e) => { setCon('initHeight', e.target.value) }}>
  932. <Radio value={1536}>750像素 x 1536像素</Radio>
  933. <Radio value={1334}>750像素 x 1334像素</Radio>
  934. <Radio value={1280}>720像素 x 1280像素</Radio>
  935. </Radio.Group>
  936. </div>
  937. </div>}
  938. </div>
  939. </div>
  940. :
  941. elementType === 'TEXT' ? <div className="widget">
  942. <div className="caption section">
  943. <div className="caption-title">文本</div>
  944. </div>
  945. <div className="form section">
  946. <div className="form-caption">推广文案</div>
  947. <TextArea
  948. placeholder={`请输入(不包含<>&'"/\以及TAB、换行、回车键)`}
  949. autoSize={{ minRows: 4, maxRows: 6 }}
  950. value={text}
  951. onChange={(e) => { setCon('text', replaceSpecialTxt(e.target.value)) }}
  952. />
  953. </div>
  954. <div className="form section">
  955. <div className="form-caption">字符与段落</div>
  956. <div className="adui-form-item">
  957. <div className="adui-form-label">字符样式</div>
  958. <div className="adui-form-control">
  959. <div className="fl-sb">
  960. <div style={{ display: 'flex', justifyContent: 'flex-start', alignItems: 'center' }}>
  961. <Space>
  962. <Select value={fontSize} style={{ width: 60 }} onChange={(e) => { setCon('fontSize', e) }}>
  963. {[14, 15, 16, 18, 20, 24, 36].map((item: number) => (<Option value={item} key={item}>{item}</Option>))}
  964. </Select>
  965. <ColorPicker onColor={(color: string) => { setCon('fontColor', color) }} color={fontColor}></ColorPicker>
  966. </Space>
  967. </div>
  968. <Radio.Group onChange={(e) => { setCon('fontStyle', e.target.value) }} value={fontStyle}>
  969. <Radio.Button value={0}>常规</Radio.Button>
  970. <Radio.Button value={1}>加粗</Radio.Button>
  971. </Radio.Group>
  972. </div>
  973. </div>
  974. </div>
  975. <div className="adui-form-item">
  976. <div className="adui-form-label">对齐方式</div>
  977. <div className="adui-form-control">
  978. <Radio.Group onChange={(e) => { setCon('textAlignment', e.target.value) }} value={textAlignment}>
  979. <Radio.Button value={0}><AlignLeftOutlined /></Radio.Button>
  980. <Radio.Button value={1}><AlignCenterOutlined /></Radio.Button>
  981. <Radio.Button value={2}><AlignRightOutlined /></Radio.Button>
  982. </Radio.Group>
  983. </div>
  984. </div>
  985. </div>
  986. <div className="form section">
  987. <div className="form-caption">边距</div>
  988. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  989. <div className="adui-form-label">上边距</div>
  990. <div className="adui-form-control">
  991. <div style={{ display: 'flex', justifyContent: 'space-between' }}>
  992. <div style={{ flexGrow: 1 }}><Slider value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} /></div>
  993. <InputNumber min={0} max={100} step={1} value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} style={{ width: 80, marginLeft: 20 }} />
  994. </div>
  995. </div>
  996. </div>
  997. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  998. <div className="adui-form-label">下边距</div>
  999. <div className="adui-form-control">
  1000. <div style={{ display: 'flex', justifyContent: 'space-between' }}>
  1001. <div style={{ flexGrow: 1 }}><Slider value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} /></div>
  1002. <InputNumber min={0} max={100} step={1} value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} style={{ width: 80, marginLeft: 20 }} />
  1003. </div>
  1004. </div>
  1005. </div>
  1006. </div>
  1007. </div>
  1008. :
  1009. elementType === 'IMAGE' ? <div className="widget">
  1010. <div className="caption section">
  1011. <div className="caption-title">图片</div>
  1012. </div>
  1013. <div className="form section">
  1014. <div className="form-caption">素材设置</div>
  1015. <div className="adui-form-item" style={{ alignItems: 'flex-start' }}>
  1016. <div className="adui-form-label">图片素材</div>
  1017. <div className="adui-form-control">
  1018. <div className={`upload-img-item ${imageUrl ? 'upload-img-item_uploaded' : ''}`}>
  1019. {
  1020. imageUrl ? <div className="upload-img-item-inner" style={{ backgroundImage: `url(${imageUrl ? imageUrl : ""})` }}>
  1021. <div className='upload-img-item-action' onClick={() => { clickUpdateImg(1) }}>
  1022. <RetweetOutlined />
  1023. </div>
  1024. </div>
  1025. :
  1026. <div className="upload-img-item-inner" onClick={() => { clickUpdateImg(1) }}>
  1027. <PlusOutlined />
  1028. </div>
  1029. }
  1030. </div>
  1031. </div>
  1032. </div>
  1033. </div>
  1034. <div className="form section">
  1035. <div className="form-caption">边距</div>
  1036. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1037. <div className="adui-form-label">上边距</div>
  1038. <div className="adui-form-control">
  1039. <div style={{ display: 'flex', justifyContent: 'space-between' }}>
  1040. <div style={{ flexGrow: 1 }}><Slider value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} /></div>
  1041. <InputNumber min={0} max={100} step={1} value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} style={{ width: 80, marginLeft: 20 }} />
  1042. </div>
  1043. </div>
  1044. </div>
  1045. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1046. <div className="adui-form-label">下边距</div>
  1047. <div className="adui-form-control">
  1048. <div style={{ display: 'flex', justifyContent: 'space-between' }}>
  1049. <div style={{ flexGrow: 1 }}><Slider value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} /></div>
  1050. <InputNumber min={0} max={100} step={1} value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} style={{ width: 80, marginLeft: 20 }} />
  1051. </div>
  1052. </div>
  1053. </div>
  1054. </div>
  1055. </div>
  1056. :
  1057. elementType === 'GH' ? <div className="widget">
  1058. <div className="caption section">
  1059. <div className="caption-title">关注公众号</div>
  1060. </div>
  1061. <div className="form section">
  1062. <Space>
  1063. <Switch size="small" checked={fastFollow === 1 ? true : false} onChange={(e) => { setCon('fastFollow', e ? 1 : 0) }} />
  1064. 一键关注
  1065. <Tooltip placement="top" title={'唤起公众号简介的半屏面板,点击其中按钮直接关注公众号'}>
  1066. <QuestionCircleOutlined />
  1067. </Tooltip>
  1068. </Space>
  1069. </div>
  1070. <div className="form section">
  1071. <div className="form-caption">按钮外观</div>
  1072. <div className="adui-form-item">
  1073. <div className="adui-form-label">按钮文案</div>
  1074. <div className="adui-form-control">
  1075. <div className="fl-sb">
  1076. <div style={{ display: 'flex', justifyContent: 'flex-start', alignItems: 'center' }}>
  1077. <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', border: '1px solid #d9d9d9', borderRadius: 2, paddingRight: 8 }}>
  1078. <Input maxLength={useIcon === 1 ? 8 : 10} style={{ width: 90 }} bordered={false} value={btnTitle} onChange={(e) => { setCon('btnTitle', e.target.value) }} /> <span>{btnTitle?.length}/{useIcon === 1 ? 8 : 10}</span>
  1079. </div>
  1080. </div>
  1081. <Radio.Group onChange={(e) => { setCon('btnFontType', e.target.value) }} value={btnFontType}>
  1082. <Radio.Button value={0}>常规</Radio.Button>
  1083. <Radio.Button value={1}>加粗</Radio.Button>
  1084. </Radio.Group>
  1085. </div>
  1086. </div>
  1087. </div>
  1088. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1089. <div className="adui-form-label">字体色</div>
  1090. <div className="adui-form-control">
  1091. <Space><ColorPicker onColor={(color: string) => { setCon('fontColor', color) }} color={fontColor}></ColorPicker><div className="colorShow">{fontColor}</div></Space>
  1092. </div>
  1093. </div>
  1094. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1095. <div className="adui-form-label">图标</div>
  1096. <div className="adui-form-control">
  1097. <Switch size="small" checked={useIcon === 1 ? true : false} onChange={(e) => { iconHandle(e) }} /> <span>{useIcon === 1 ? '已启用' : '未启用'}</span>
  1098. </div>
  1099. </div>
  1100. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1101. <div className="adui-form-label">边框色</div>
  1102. <div className="adui-form-control">
  1103. <Space><ColorPicker onColor={(color: string) => { setCon('btnBorderColorTheme', color) }} color={btnBorderColorTheme}></ColorPicker><div className="colorShow">{btnBorderColorTheme}</div></Space>
  1104. </div>
  1105. </div>
  1106. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1107. <div className="adui-form-label">背景色</div>
  1108. <div className="adui-form-control">
  1109. <Space><ColorPicker onColor={(color: string) => { setCon('btnBgColorTheme', color) }} color={btnBgColorTheme}></ColorPicker><div className="colorShow">{btnBgColorTheme}</div></Space>
  1110. </div>
  1111. </div>
  1112. </div>
  1113. <div className="form section">
  1114. <div className="form-caption">边距</div>
  1115. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1116. <div className="adui-form-label">上边距</div>
  1117. <div className="adui-form-control">
  1118. <div style={{ display: 'flex', justifyContent: 'space-between' }}>
  1119. <div style={{ flexGrow: 1 }}><Slider value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} /></div>
  1120. <InputNumber min={0} max={100} step={1} value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} style={{ width: 80, marginLeft: 20 }} />
  1121. </div>
  1122. </div>
  1123. </div>
  1124. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1125. <div className="adui-form-label">下边距</div>
  1126. <div className="adui-form-control">
  1127. <div style={{ display: 'flex', justifyContent: 'space-between' }}>
  1128. <div style={{ flexGrow: 1 }}><Slider value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} /></div>
  1129. <InputNumber min={0} max={100} step={1} value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} style={{ width: 80, marginLeft: 20 }} />
  1130. </div>
  1131. </div>
  1132. </div>
  1133. </div>
  1134. </div> :
  1135. elementType === 'ENTERPRISE_WX' ? <div className="widget">
  1136. <div className="caption section">
  1137. <div className="caption-title">添加商家微信</div>
  1138. </div>
  1139. <div className="form section">
  1140. <div className="form-caption">按钮外观</div>
  1141. <div className="adui-form-item">
  1142. <div className="adui-form-label">按钮文案</div>
  1143. <div className="adui-form-control">
  1144. <div className="fl-sb">
  1145. <div style={{ display: 'flex', justifyContent: 'flex-start', alignItems: 'center' }}>
  1146. <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', border: '1px solid #d9d9d9', borderRadius: 2, paddingRight: 8 }}>
  1147. <Input maxLength={useIcon === 1 ? 8 : 10} style={{ width: 90 }} bordered={false} value={btnTitle} onChange={(e) => { setCon('btnTitle', e.target.value) }} /> <span>{btnTitle?.length}/{useIcon === 1 ? 8 : 10}</span>
  1148. </div>
  1149. </div>
  1150. <Radio.Group onChange={(e) => { setCon('btnFontType', e.target.value) }} value={btnFontType}>
  1151. <Radio.Button value={0}>常规</Radio.Button>
  1152. <Radio.Button value={1}>加粗</Radio.Button>
  1153. </Radio.Group>
  1154. </div>
  1155. </div>
  1156. </div>
  1157. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1158. <div className="adui-form-label">字体色</div>
  1159. <div className="adui-form-control">
  1160. <Space><ColorPicker onColor={(color: string) => { setCon('fontColor', color) }} color={fontColor}></ColorPicker> <div className="colorShow">{fontColor}</div></Space>
  1161. </div>
  1162. </div>
  1163. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1164. <div className="adui-form-label">图标</div>
  1165. <div className="adui-form-control">
  1166. <Switch size="small" checked={useIcon === 1 ? true : false} onChange={(e) => { iconHandle(e) }} /> <span>{useIcon === 1 ? '已启用' : '未启用'}</span>
  1167. </div>
  1168. </div>
  1169. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1170. <div className="adui-form-label">边框色</div>
  1171. <div className="adui-form-control">
  1172. <Space><ColorPicker onColor={(color: string) => { setCon('btnBorderColorTheme', color) }} color={btnBorderColorTheme}></ColorPicker> <div className="colorShow">{btnBorderColorTheme}</div></Space>
  1173. </div>
  1174. </div>
  1175. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1176. <div className="adui-form-label">背景色</div>
  1177. <div className="adui-form-control">
  1178. <Space><ColorPicker onColor={(color: string) => { setCon('btnBgColorTheme', color) }} color={btnBgColorTheme}></ColorPicker> <div className="colorShow">{btnBgColorTheme}</div></Space>
  1179. </div>
  1180. </div>
  1181. </div>
  1182. <div className="form section">
  1183. <div className="form-caption">边距</div>
  1184. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1185. <div className="adui-form-label">上边距</div>
  1186. <div className="adui-form-control">
  1187. <div style={{ display: 'flex', justifyContent: 'space-between' }}>
  1188. <div style={{ flexGrow: 1 }}><Slider value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} /></div>
  1189. <InputNumber min={0} max={100} step={1} value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} style={{ width: 80, marginLeft: 20 }} />
  1190. </div>
  1191. </div>
  1192. </div>
  1193. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1194. <div className="adui-form-label">下边距</div>
  1195. <div className="adui-form-control">
  1196. <div style={{ display: 'flex', justifyContent: 'space-between' }}>
  1197. <div style={{ flexGrow: 1 }}><Slider value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} /></div>
  1198. <InputNumber min={0} max={100} step={1} value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} style={{ width: 80, marginLeft: 20 }} />
  1199. </div>
  1200. </div>
  1201. </div>
  1202. </div>
  1203. </div>
  1204. :
  1205. elementType === 'link' ? <div className="widget">
  1206. {/* <div className="caption section">
  1207. <div className="caption-title">跳转链接</div>
  1208. </div>
  1209. <div className="form section">
  1210. <div className="form-caption">链接设置</div>
  1211. <Input placeholder="以 http:// 或 https:// 开头" value={origBtnJumpUrl} onChange={(e) => { setCon('origBtnJumpUrl', e.target.value) }} />
  1212. </div>
  1213. <div className="form section">
  1214. <div className="form-caption">按钮外观</div>
  1215. <div className="adui-form-item">
  1216. <div className="adui-form-label">按钮文案</div>
  1217. <div className="adui-form-control">
  1218. <div className="fl-sb">
  1219. <div style={{ display: 'flex', justifyContent: 'flex-start', alignItems: 'center' }}>
  1220. <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', border: '1px solid #d9d9d9', borderRadius: 2, paddingRight: 8 }}>
  1221. <Input maxLength={10} style={{ width: 90 }} bordered={false} value={btnTitle} onChange={(e) => { setCon('btnTitle', e.target.value) }} /> <span>{btnTitle?.length}/10</span>
  1222. </div>
  1223. </div>
  1224. <Radio.Group onChange={(e) => { setCon('btnFontType', e.target.value) }} value={btnFontType}>
  1225. <Radio.Button value="0">常规</Radio.Button>
  1226. <Radio.Button value="1">加粗</Radio.Button>
  1227. </Radio.Group>
  1228. </div>
  1229. </div>
  1230. </div>
  1231. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1232. <div className="adui-form-label">字体色</div>
  1233. <div className="adui-form-control">
  1234. <Space><ColorPicker onColor={(color: string) => { setCon('fontColor', color) }} color={fontColor}></ColorPicker><div className="colorShow">{fontColor}</div></Space>
  1235. </div>
  1236. </div>
  1237. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1238. <div className="adui-form-label">图标</div>
  1239. <div className="adui-form-control">
  1240. <Switch size="small" checked={useIcon === '1' ? true : false} onChange={(e) => { iconHandle(e) }} /> <span>{useIcon === '1' ? '已启用' : '未启用'}</span>
  1241. </div>
  1242. </div>
  1243. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1244. <div className="adui-form-label">边框色</div>
  1245. <div className="adui-form-control">
  1246. <Space><ColorPicker onColor={(color: string) => { setCon('btnBorderColorTheme', color) }} color={btnBorderColorTheme}></ColorPicker><div className="colorShow">{btnBorderColorTheme}</div></Space>
  1247. </div>
  1248. </div>
  1249. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1250. <div className="adui-form-label">背景色</div>
  1251. <div className="adui-form-control">
  1252. <Space><ColorPicker onColor={(color: string) => { setCon('btnBgColorTheme', color) }} color={btnBgColorTheme}></ColorPicker><div className="colorShow">{btnBgColorTheme}</div></Space>
  1253. </div>
  1254. </div>
  1255. </div>
  1256. <div className="form section">
  1257. <div className="form-caption">边距</div>
  1258. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1259. <div className="adui-form-label">上边距</div>
  1260. <div className="adui-form-control">
  1261. <div style={{ display: 'flex', justifyContent: 'space-between' }}>
  1262. <div style={{ flexGrow: 1 }}><Slider value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} /></div>
  1263. <InputNumber min={0} max={100} step={1} value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} style={{ width: 80, marginLeft: 20 }} />
  1264. </div>
  1265. </div>
  1266. </div>
  1267. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1268. <div className="adui-form-label">下边距</div>
  1269. <div className="adui-form-control">
  1270. <div style={{ display: 'flex', justifyContent: 'space-between' }}>
  1271. <div style={{ flexGrow: 1 }}><Slider value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} /></div>
  1272. <InputNumber min={0} max={100} step={1} value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} style={{ width: 80, marginLeft: 20 }} />
  1273. </div>
  1274. </div>
  1275. </div>
  1276. </div> */}
  1277. </div>
  1278. :
  1279. elementType === 'IMAGE_TEXT' ? <div className="widget">
  1280. {imgTextButtonShow ? <>
  1281. <div className="caption section goBack" onClick={() => { setImgTextButtonShow(false) }}>
  1282. <SwapLeftOutlined />
  1283. <span>返回</span>
  1284. </div>
  1285. {imgTextData.subElemType === 'GH' || imgTextData.subElemType === 'ENTERPRISE_WX' ? <>
  1286. {imgTextData.subElemType === 'GH' && <div className="form section">
  1287. <Space>
  1288. <Switch size="small" checked={(imgTextData?.content as ITItemGhSpec)?.fastFollow === 1 ? true : false} onChange={(e) => { onSetShelfnewButtonField('fastFollow', e ? 1 : 0) }} />
  1289. 一键关注
  1290. <Tooltip placement="top" title={'唤起公众号简介的半屏面板,点击其中按钮直接关注公众号'}>
  1291. <QuestionCircleOutlined />
  1292. </Tooltip>
  1293. </Space>
  1294. </div>}
  1295. <div className="form section">
  1296. <div className="form-caption">按钮外观</div>
  1297. <div className="adui-form-item">
  1298. <div className="adui-form-label">按钮文案</div>
  1299. <div className="adui-form-control">
  1300. <div className="fl-sb">
  1301. <div style={{ display: 'flex', justifyContent: 'flex-start', alignItems: 'center' }}>
  1302. <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', border: '1px solid #d9d9d9', borderRadius: 2, paddingRight: 8 }}>
  1303. <Input maxLength={5} style={{ width: 90 }} bordered={false} value={imgTextData?.content?.btnTitle} onChange={(e) => { onSetShelfnewButtonField('btnTitle', e.target.value) }} /> <span>{imgTextData?.content?.btnTitle?.length}/5</span>
  1304. </div>
  1305. </div>
  1306. <Radio.Group onChange={(e) => { onSetShelfnewButtonField('btnFontType', e.target.value) }} value={imgTextData?.content?.btnFontType}>
  1307. <Radio.Button value={0}>常规</Radio.Button>
  1308. <Radio.Button value={1}>加粗</Radio.Button>
  1309. </Radio.Group>
  1310. </div>
  1311. </div>
  1312. </div>
  1313. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1314. <div className="adui-form-label">字体色</div>
  1315. <div className="adui-form-control">
  1316. <Space><ColorPicker onColor={(color: string) => { onSetShelfnewButtonField('fontColor', color) }} color={imgTextData?.content?.fontColor}></ColorPicker><div className="colorShow">{imgTextData?.content?.fontColor}</div></Space>
  1317. </div>
  1318. </div>
  1319. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1320. <div className="adui-form-label">边框色</div>
  1321. <div className="adui-form-control">
  1322. <Space><ColorPicker onColor={(color: string) => { onSetShelfnewButtonField('btnBorderColorTheme', color) }} color={imgTextData?.content?.btnBorderColorTheme}></ColorPicker><div className="colorShow">{imgTextData?.content?.btnBorderColorTheme}</div></Space>
  1323. </div>
  1324. </div>
  1325. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1326. <div className="adui-form-label">背景色</div>
  1327. <div className="adui-form-control">
  1328. <Space><ColorPicker onColor={(color: string) => { onSetShelfnewButtonField('btnBgColorTheme', color) }} color={imgTextData?.content?.btnBgColorTheme}></ColorPicker><div className="colorShow">{imgTextData?.content?.btnBgColorTheme}</div></Space>
  1329. </div>
  1330. </div>
  1331. </div>
  1332. </> : <></>}
  1333. </> : <>
  1334. <div className="caption section">
  1335. <div className="caption-title">图文复合组件</div>
  1336. </div>
  1337. <div className="form section">
  1338. <div className="adui-form-item">
  1339. <div className="adui-form-label">类型</div>
  1340. <div className="adui-form-control">
  1341. <>
  1342. <Radio.Group onChange={(e) => { setShelfnewType(e.target.value) }} value={imageTextItemIndex.toString()}>
  1343. <Radio value='1'>一行1个</Radio>
  1344. <Radio value='2'>一行2个</Radio>
  1345. </Radio.Group>
  1346. {imageTextItemIndex === 2 && <Radio.Group onChange={(e) => { setGoodsCount(e.target.value) }} value={goodsCount} size='small' style={{ marginTop: 10 }}>
  1347. <Radio.Button value={0}>商品1</Radio.Button>
  1348. <Radio.Button value={1}>商品2</Radio.Button>
  1349. </Radio.Group>}
  1350. </>
  1351. </div>
  1352. </div>
  1353. </div>
  1354. <div className="form section">
  1355. <div className="adui-form-item">
  1356. <div className="adui-form-label">配图</div>
  1357. <div className="adui-form-control">
  1358. <div>
  1359. <Button onClick={() => { setCcType(4); setSelectImgVisible(true); }}>上传图片</Button>
  1360. <div style={{ marginTop: 4, fontSize: 12, color: '#636363' }}>{imageTextItemIndex === 2 ? '尺寸:480像素*480像素' : '尺寸:360像素*360像素'}</div>
  1361. <div style={{ marginTop: 4, fontSize: 12, color: '#636363' }}>格式:不超过300k</div>
  1362. </div>
  1363. </div>
  1364. </div>
  1365. <div className="adui-form-item" style={{ alignItems: 'flex-start' }}>
  1366. <div className="adui-form-label" style={{ marginTop: 6 }}>标题</div>
  1367. <div className="adui-form-control">
  1368. <Input.TextArea placeholder="请输入标题" onChange={(e) => { onShelfnewTxtCon(e.target.value?.replace(/\r|\n/ig, ""), 'title') }} value={imgTextData?.title} showCount maxLength={imageTextItemIndex === 1 ? 8 : 12} autoSize />
  1369. </div>
  1370. </div>
  1371. <div className="adui-form-item" style={{ alignItems: 'flex-start' }}>
  1372. <div className="adui-form-label">描述</div>
  1373. <div className="adui-form-control">
  1374. <Space direction="vertical" style={{ width: '100%' }}>
  1375. <Radio.Group defaultValue='text'>
  1376. <Radio value="text">文字</Radio>
  1377. <Radio value="price" disabled>价格</Radio>
  1378. </Radio.Group>
  1379. <Input.TextArea placeholder="请输入描述" onChange={(e) => { onShelfnewTxtCon(e.target.value?.replace(/\r|\n/ig, ""), 'desc') }} value={imgTextData?.desc} showCount maxLength={imageTextItemIndex === 1 ? 10 : 15} autoSize={{ minRows: 2, maxRows: 2 }} />
  1380. </Space>
  1381. </div>
  1382. </div>
  1383. {imageTextItemIndex === 2 && <div className="adui-form-item">
  1384. <div className="adui-form-label">对齐</div>
  1385. <div className="adui-form-control">
  1386. <Radio.Group onChange={(e) => { setCon('alignMode', e.target.value) }} value={alignMode}>
  1387. <Radio value={0}>左对齐</Radio>
  1388. <Radio value={1}>居中对齐</Radio>
  1389. </Radio.Group>
  1390. </div>
  1391. </div>}
  1392. <div className="adui-form-item" style={{ alignItems: 'flex-start' }}>
  1393. <div className="adui-form-label" style={{ marginTop: 14 }}>字体颜色</div>
  1394. <div className="adui-form-control">
  1395. <Space style={{ width: '100%' }} size="large" className="shelfnewColor">
  1396. <div>
  1397. <Space><ColorPicker onColor={(color: string) => { onShelfnewTxtCon(color, 'titleColor') }} color={imgTextData.titleColor}></ColorPicker> <div className="colorShow">{imgTextData.titleColor}</div></Space>
  1398. <div className="colorName">标题</div>
  1399. </div>
  1400. <div>
  1401. <Space><ColorPicker onColor={(color: string) => { onShelfnewTxtCon(color, 'descColor') }} color={imgTextData.descColor}></ColorPicker> <div className="colorShow">{imgTextData.descColor}</div></Space>
  1402. <div className="colorName">{/*shelfnewDescData?.name*/'描述'}</div>
  1403. </div>
  1404. </Space>
  1405. </div>
  1406. </div>
  1407. <div className="adui-form-item" style={{ alignItems: 'flex-start' }}>
  1408. <div className="adui-form-label" style={{ marginTop: 14 }}>其它颜色</div>
  1409. <div className="adui-form-control">
  1410. <Space style={{ width: '100%' }} size="large" className="shelfnewColor">
  1411. <div>
  1412. <Space><ColorPicker onColor={(color: string) => { onShelfnewTxtCon(color, 'borderColor') }} color={imgTextData.borderColor}></ColorPicker> <div className="colorShow">{imgTextData.borderColor}</div></Space>
  1413. <div className="colorName">边框</div>
  1414. </div>
  1415. <div>
  1416. <Space><ColorPicker onColor={(color: string) => { onShelfnewTxtCon(color, 'bgColor') }} color={imgTextData.bgColor}></ColorPicker> <div className="colorShow">{imgTextData.bgColor}</div></Space>
  1417. <div className="colorName">背景</div>
  1418. </div>
  1419. </Space>
  1420. </div>
  1421. </div>
  1422. </div>
  1423. <div className="form section">
  1424. <div className="form-caption">边距</div>
  1425. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1426. <div className="adui-form-label">上边距</div>
  1427. <div className="adui-form-control">
  1428. <div style={{ display: 'flex', justifyContent: 'space-between' }}>
  1429. <div style={{ flexGrow: 1 }}><Slider value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} /></div>
  1430. <InputNumber min={0} max={100} step={1} value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} style={{ width: 80, marginLeft: 20 }} />
  1431. </div>
  1432. </div>
  1433. </div>
  1434. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1435. <div className="adui-form-label">下边距</div>
  1436. <div className="adui-form-control">
  1437. <div style={{ display: 'flex', justifyContent: 'space-between' }}>
  1438. <div style={{ flexGrow: 1 }}><Slider value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} /></div>
  1439. <InputNumber min={0} max={100} step={1} value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} style={{ width: 80, marginLeft: 20 }} />
  1440. </div>
  1441. </div>
  1442. </div>
  1443. </div>
  1444. <div className="form section">
  1445. <div className="form-caption">按钮</div>
  1446. <div className="adui-form-item">
  1447. <div className="adui-form-label">跳转方式</div>
  1448. <div className="adui-form-control">
  1449. <Radio.Group onChange={(e) => onShelfnewTxtCon(e.target.value, 'jumpMode')} value={imgTextData.jumpMode}>
  1450. <Radio value="btn_jump">按钮跳转</Radio>
  1451. <Radio value="total_jump">全局跳转</Radio>
  1452. </Radio.Group>
  1453. </div>
  1454. </div>
  1455. <div className="adui-form-item">
  1456. <div className="adui-form-label">按钮类型</div>
  1457. <div className="adui-form-control">
  1458. <Space>
  1459. <Select style={{ width: 120 }} value={imgTextData.subElemType} onChange={(e) => { onSetShelfnewButton(e) }}>
  1460. {/* <Option value="link">跳转链接</Option> */}
  1461. <Option value="GH">关注公众号</Option>
  1462. <Option value="ENTERPRISE_WX" disabled>添加商家微信</Option>
  1463. </Select>
  1464. <Button type="link" size="small" onClick={() => { setImgTextButtonShow(true) }}>配置</Button>
  1465. </Space>
  1466. </div>
  1467. </div>
  1468. </div>
  1469. </>}
  1470. </div>
  1471. :
  1472. elementType === 'FLOAT_BUTTON' ? <div style={{ height: '100%' }}>
  1473. <div className={`widget ${imgTextButtonShow ? 'widget_back' : ''}`}>
  1474. <div className="caption section">
  1475. <div className="caption-title">悬浮按钮</div>
  1476. </div>
  1477. <div className="form section">
  1478. <div className="form-caption">卡片设置</div>
  1479. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1480. <div className="adui-form-label">转化按钮</div>
  1481. <div className="adui-form-control">
  1482. <div className='form-result-text'><span>{componentItem?.name}</span> <Button type="link" size='small' style={{ color: '#6b6b6b', fontSize: 12 }} onClick={() => { setImgTextButtonShow(true) }}>设置</Button> </div>
  1483. </div>
  1484. </div>
  1485. <div className="adui-form-item">
  1486. <div className="adui-form-label">卡片样式</div>
  1487. <div className="adui-form-control">
  1488. <Radio.Group onChange={(e) => { setGlobalComponentItem('styleType', e.target.value) }} className="floatType" value={styleType}>
  1489. <Radio.Button value={0}>
  1490. <div className='floatTypeInner'>
  1491. <i className="floatTypeAvatar"></i>
  1492. <i className="floatTypeText_two"></i>
  1493. <i className="floatTypeButton"></i>
  1494. </div>
  1495. </Radio.Button>
  1496. <Radio.Button value={1}>
  1497. <div className='floatTypeInner'>
  1498. <i style={{ display: 'inline-block', height: 12 }}></i>
  1499. <i className="floatTypeText_two"></i>
  1500. <i className="floatTypeButton"></i>
  1501. </div>
  1502. </Radio.Button>
  1503. <Radio.Button value={2}>
  1504. <div className='floatTypeInner'>
  1505. <i style={{ display: 'inline-block', height: 12 }}></i>
  1506. <i className="floatTypeText_one"></i>
  1507. <i className="floatTypeButton"></i>
  1508. </div>
  1509. </Radio.Button>
  1510. </Radio.Group>
  1511. </div>
  1512. </div>
  1513. </div>
  1514. <div className="form section">
  1515. <div className="form-caption">内容设置</div>
  1516. {styleType === 0 && <div className="adui-form-item" style={{ alignItems: 'flex-start' }}>
  1517. <div className="adui-form-label">图片</div>
  1518. <div className="adui-form-control">
  1519. <div className={`upload-img-item ${imageUrl ? 'upload-img-item_uploaded' : ''}`}>
  1520. {
  1521. imageUrl ? <div className="upload-img-item-inner" style={{ backgroundImage: `url(${imageUrl ? imageUrl : ""})` }}>
  1522. <div className='upload-img-item-action' onClick={() => { setCcType(5); setSelectImgVisible(true); }}>
  1523. <RetweetOutlined />
  1524. </div>
  1525. </div>
  1526. :
  1527. <div className="upload-img-item-inner" onClick={() => { setCcType(5); setSelectImgVisible(true); }}>
  1528. <PlusOutlined />
  1529. </div>
  1530. }
  1531. </div>
  1532. <div style={{ marginTop: 4, fontSize: 12, color: '#A3A3A3' }}>尺寸:96像素*96像素</div>
  1533. <div style={{ marginTop: 4, fontSize: 12, color: '#A3A3A3' }}>格式:不超过300k</div>
  1534. </div>
  1535. </div>}
  1536. <div className="adui-form-item" style={{ alignItems: 'flex-start', marginBottom: 10 }}>
  1537. <div className="adui-form-label" style={{ marginTop: 6 }}>标题</div>
  1538. <div className="adui-form-control">
  1539. <Input.TextArea placeholder="请输入标题" onChange={(e) => { setGlobalComponentItem('title', e.target.value?.replace(/\r|\n/ig, "")) }} value={title} showCount maxLength={10} autoSize />
  1540. </div>
  1541. </div>
  1542. {(styleType === 1 || styleType === 0) && <div className="adui-form-item" style={{ alignItems: 'flex-start', marginBottom: 10 }}>
  1543. <div className="adui-form-label" style={{ marginTop: 6 }}>描述</div>
  1544. <div className="adui-form-control">
  1545. <Input.TextArea placeholder="请输入描述" onChange={(e) => { setGlobalComponentItem('desc', e.target.value?.replace(/\r|\n/ig, "")) }} value={desc} showCount maxLength={14} autoSize />
  1546. </div>
  1547. </div>}
  1548. <div className="adui-form-item" style={{ alignItems: 'center', marginBottom: 10 }}>
  1549. <div className="adui-form-label" style={{ marginTop: 6 }}>标题字色</div>
  1550. <div className="adui-form-control">
  1551. <Space><ColorPicker onColor={(color: string) => { setGlobalComponentItem('titleColor', color) }} color={titleColor}></ColorPicker> <div className="colorShow">{titleColor}</div></Space>
  1552. </div>
  1553. </div>
  1554. {(styleType === 1 || styleType === 0) && <div className="adui-form-item" style={{ alignItems: 'center', marginBottom: 10 }}>
  1555. <div className="adui-form-label" style={{ marginTop: 6 }}>描述字色</div>
  1556. <div className="adui-form-control">
  1557. <Space><ColorPicker onColor={(color: string) => { setGlobalComponentItem('descColor', color) }} color={descColor}></ColorPicker> <div className="colorShow">{descColor}</div></Space>
  1558. </div>
  1559. </div>}
  1560. </div>
  1561. <div className="form section">
  1562. <div className="form-caption">更多设置</div>
  1563. <div className='adui-form-tip adui-form-tip_normal'>如果落地页只有一页,悬浮组件的更多设置必须为“进入页面时出现”、且“不消失”</div>
  1564. <div className="adui-form-item">
  1565. <div className="adui-form-label">出现方式</div>
  1566. <div className="adui-form-control">
  1567. <Radio.Group onChange={(e) => { setGlobalComponentItem('appearType', e.target.value) }} value={appearType}>
  1568. <Radio value={0}>进入页面时出现</Radio>
  1569. <Radio value={1}>滑动页面时出现</Radio>
  1570. </Radio.Group>
  1571. </div>
  1572. </div>
  1573. <div className="adui-form-item">
  1574. <div className="adui-form-label">消失方式</div>
  1575. <div className="adui-form-control">
  1576. <Radio.Group onChange={(e) => { setGlobalComponentItem('disappearType', e.target.value) }} value={disappearType}>
  1577. <Radio value={0}>不消失</Radio>
  1578. <Radio value={1}>滑至页面底部时消失</Radio>
  1579. </Radio.Group>
  1580. </div>
  1581. </div>
  1582. </div>
  1583. </div>
  1584. {/* 设置转化按钮 imgTextButtonShow */}
  1585. <div className='aside' style={{ transform: imgTextButtonShow ? 'translate3d(0px, 0px, 0px)' : 'translate3d(100%, 0px, 0px)', transition: 'all 0.3s cubic-bezier(0, 0, 0.2, 1) 0s' }}>
  1586. <div className='aside-nav'>
  1587. <Button type='link' icon={<SwapLeftOutlined />} onClick={() => { setImgTextButtonShow(false) }}>返回</Button>
  1588. </div>
  1589. <div className="form section">
  1590. <div className="form-caption">按钮类型</div>
  1591. <div className="adui-form-item" style={{ alignItems: 'center' }}>
  1592. <Select style={{ width: 180 }} className="aside-select" dropdownClassName="aside-select" onChange={(e) => { setGlobalComponentItem('componentItem', e) }} value={componentItem?.elementType} size="small">
  1593. <Option value="GH"><FollowAcc />关注公众号</Option>
  1594. <Option value="ENTERPRISE_WX"><WxAutoSvg />添加商家微信</Option>
  1595. </Select>
  1596. </div>
  1597. </div>
  1598. {componentItem?.elementType === 'GH' ? <>
  1599. <div className="form section">
  1600. <Space>
  1601. <Switch size="small" checked={componentItem?.fastFollow} onChange={(e) => { setGlobalComponentItem('componentItem', { ...componentItem, fastFollow: e ? 1 : 0 }) }} />
  1602. 一键关注
  1603. <Tooltip placement="top" title={'唤起公众号简介的半屏面板,点击其中按钮直接关注公众号'}>
  1604. <QuestionCircleOutlined />
  1605. </Tooltip>
  1606. </Space>
  1607. </div>
  1608. </> : null}
  1609. <div className="form section">
  1610. <div className="form-caption">按钮外观</div>
  1611. <div className="adui-form-item" style={{ marginBottom: 10 }}>
  1612. <div className="adui-form-label">按钮文案</div>
  1613. <div className="adui-form-control">
  1614. <div className="fl-sb">
  1615. <div style={{ display: 'flex', justifyContent: 'flex-start', alignItems: 'center' }}>
  1616. <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', border: '1px solid #d9d9d9', borderRadius: 2, paddingRight: 8 }}>
  1617. <Input maxLength={5} style={{ width: 90 }} bordered={false} value={componentItem?.btnTitle} onChange={(e) => { setGlobalComponentItem('componentItem', { ...componentItem, btnTitle: e.target.value }) }} /> <span>{componentItem?.btnTitle?.length}/5</span>
  1618. </div>
  1619. </div>
  1620. <Radio.Group onChange={(e) => { setGlobalComponentItem('componentItem', { ...componentItem, btnFontType: e.target.value }) }} value={componentItem?.btnFontType}>
  1621. <Radio.Button value={0}>常规</Radio.Button>
  1622. <Radio.Button value={1}>加粗</Radio.Button>
  1623. </Radio.Group>
  1624. </div>
  1625. </div>
  1626. </div>
  1627. <div className="adui-form-item" style={{ alignItems: 'center', marginBottom: 10 }}>
  1628. <div className="adui-form-label">字体色</div>
  1629. <div className="adui-form-control">
  1630. <Space><ColorPicker onColor={(color: string) => { setGlobalComponentItem('componentItem', { ...componentItem, fontColor: color }) }} color={componentItem?.fontColor}></ColorPicker><div className="colorShow">{componentItem?.fontColor}</div></Space>
  1631. </div>
  1632. </div>
  1633. <div className="adui-form-item" style={{ alignItems: 'center', marginBottom: 10 }}>
  1634. <div className="adui-form-label">填充色</div>
  1635. <div className="adui-form-control">
  1636. <Space><ColorPicker onColor={(color: string) => { setGlobalComponentItem('componentItem', { ...componentItem, btnBgColorTheme: color }) }} color={componentItem?.btnBgColorTheme}></ColorPicker><div className="colorShow">{componentItem?.btnBgColorTheme}</div></Space>
  1637. </div>
  1638. </div>
  1639. </div>
  1640. </div>
  1641. </div> :
  1642. null
  1643. }
  1644. </>
  1645. } else {
  1646. return null
  1647. }
  1648. }
  1649. /** 选择单张图片 */
  1650. const clickUpdateImg = useCallback((num: number) => {
  1651. setSliderImgContent([])
  1652. setCcType(1)
  1653. setSelectImgVisible(true)
  1654. }, [scType, content, sliderImgContent])
  1655. /** 选择视频 */
  1656. const clickUpdateVideo = useCallback(() => {
  1657. setCcType(2)
  1658. setSelectImgVisible(true)
  1659. }, [scType])
  1660. /** 弹窗返回设置图片 */
  1661. const setImg = useCallback((value: any[]) => {
  1662. setSelectImgVisible(false)
  1663. let newContent = JSON.parse(JSON.stringify(content))
  1664. let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
  1665. let oldContent = newContent
  1666. if (scType === 1) { // 图片
  1667. if (selectIndex !== -1) {
  1668. let selectCon = oldContent[selectIndex]
  1669. selectCon['imageUrl'] = value[0]?.url
  1670. selectCon['width'] = Number(value[0]?.width)
  1671. selectCon['height'] = Number(value[0]?.height)
  1672. oldContent[selectIndex] = { ...selectCon }
  1673. dispatch({ type: 'setCon', params: { content: [...oldContent] } })
  1674. }
  1675. } else if (scType === 2) { // 视频要判断是否是长视频 还是短视频
  1676. if (selectIndex !== -1) {
  1677. let selectCon = oldContent[selectIndex]
  1678. selectCon['width'] = value[0]?.width
  1679. selectCon['height'] = value[0]?.height
  1680. selectCon['videoUrl'] = value[0]?.url
  1681. oldContent[selectIndex] = { ...selectCon }
  1682. dispatch({ type: 'setCon', params: { content: [...oldContent] } })
  1683. }
  1684. } else if (scType === 3) { // 轮播
  1685. if (selectIndex !== -1) {
  1686. let urlList = value?.map(item => item?.url)
  1687. let selectCon = oldContent[selectIndex]
  1688. selectCon.imageUrlList = selectCon.imageUrlList?.reduce((prev: any[], cur: any, index: number) => {
  1689. prev.push(urlList[index] || "")
  1690. return prev
  1691. }, [])
  1692. oldContent[selectIndex] = { ...selectCon }
  1693. dispatch({ type: 'setCon', params: { content: [...oldContent] } })
  1694. }
  1695. } else if (scType === 4) { // 设置图文复合组件图片
  1696. if (selectIndex !== -1) {
  1697. let selectCon = oldContent[selectIndex]
  1698. let length = selectCon?.imageTextItem?.length || 0
  1699. if (length === 1) {
  1700. let imageTextItem = selectCon?.imageTextItem[0]
  1701. imageTextItem.imageUrl = value[0]?.url
  1702. oldContent[selectIndex] = { ...selectCon }
  1703. dispatch({ type: 'setCon', params: { content: [...oldContent] } })
  1704. } else if (length === 2) {
  1705. let imageTextItem = selectCon?.imageTextItem[goodsCount]
  1706. imageTextItem.imageUrl = value[0]?.url
  1707. oldContent[selectIndex] = { ...selectCon }
  1708. dispatch({ type: 'setCon', params: { content: [...oldContent] } })
  1709. }
  1710. }
  1711. } else if (scType === 5) { // 设置悬浮组件
  1712. let newConItem = JSON.parse(JSON.stringify(componentItem))
  1713. let selectIndex = newConItem?.findIndex((item: Content) => item.comptActive)
  1714. let oldContent = newConItem
  1715. if (selectIndex !== -1) {
  1716. let selectCon = oldContent[selectIndex]
  1717. selectCon['imageUrl'] = value[0]?.url
  1718. dispatchGlobal({ type: 'setConItem', params: { componentItem: JSON.parse(JSON.stringify(oldContent)) } })
  1719. }
  1720. }
  1721. }, [content, scType, goodsCount])
  1722. /** 回填数据 */
  1723. const editSelectImg = useCallback((url: any[]) => {
  1724. setSelectImgVisible(true)
  1725. }, [selectImgVisible])
  1726. /** 下一步 */
  1727. const nextHandle = useCallback(() => {
  1728. if (content.length === 1) {
  1729. message.error('请完善内容')
  1730. return
  1731. }
  1732. if (((content[0]?.elementType === "TOP_IMAGE") && !content[0].imageUrl) || content[0]?.elementType === "empty") {
  1733. message.error('请完善顶部组件内容')
  1734. return
  1735. }
  1736. if (content[0]?.elementType === "TOP_SLIDER" && !content[0].imageUrlList?.every((item: string) => item)) {
  1737. message.error('请完善轮播图组件内容~~')
  1738. return
  1739. }
  1740. if (content[0]?.elementType === "TOP_VIDEO" && !content[0].videoUrl) {
  1741. message.error('请完善顶部视频组件内容~~')
  1742. return
  1743. }
  1744. let reg = /(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?/;
  1745. let a = content?.every((item: any) => {
  1746. if (item?.elementType === "GH") {
  1747. if (item?.btnTitle) {
  1748. return true
  1749. } else {
  1750. message.error('关注公众号按钮按钮文案未填写')
  1751. return false
  1752. }
  1753. } else if (item?.elementType === "link") {
  1754. if (!reg.test(item?.origBtnJumpUrl)) {
  1755. message.error('跳转链接按钮请输入正确链接')
  1756. return false
  1757. }
  1758. if (item?.btnTitle) {
  1759. return true
  1760. } else {
  1761. message.error('关注公众号按钮按钮文案未填写')
  1762. return false
  1763. }
  1764. } else if (item?.elementType === "IMAGE") {
  1765. if (item.imageUrl) {
  1766. return true
  1767. } else {
  1768. message.error('请选择图片')
  1769. return false
  1770. }
  1771. } else if (item?.elementType === "TEXT") {
  1772. if (item.text) {
  1773. return true
  1774. } else {
  1775. message.error('请完善文本内容')
  1776. return false
  1777. }
  1778. } else if (item?.elementType === "ENTERPRISE_WX") {
  1779. if (item?.btnTitle) {
  1780. return true
  1781. } else {
  1782. message.error('客服组按钮按钮文案未填写')
  1783. return false
  1784. }
  1785. } else if (item?.elementType === "IMAGE_TEXT") {
  1786. return item?.imageTextItem?.every((imageTextItem: ImageTextItem) => {
  1787. if (imageTextItem?.imageUrl && imageTextItem?.title && imageTextItem?.desc) {
  1788. return true
  1789. }
  1790. message.error('请完善图文复合组件内容')
  1791. return false
  1792. })
  1793. } else {
  1794. return true
  1795. }
  1796. })
  1797. if (componentItem?.length > 0) {
  1798. let b = componentItem?.every((item: any) => {
  1799. if (item?.elementType === "FLOAT_BUTTON") {
  1800. if (item?.styleType === 0) {
  1801. if (!item?.imageUrl) {
  1802. message.error('悬浮组件请上传图片')
  1803. return false
  1804. }
  1805. if (!item?.title) {
  1806. message.error('悬浮组件请输入标题')
  1807. return false
  1808. }
  1809. if (!item?.desc) {
  1810. message.error('悬浮组件请输入描述')
  1811. return false
  1812. }
  1813. } else if (item?.wxad_styleType === 1) {
  1814. if (!item?.title) {
  1815. message.error('悬浮组件请输入标题')
  1816. return false
  1817. }
  1818. if (!item?.desc) {
  1819. message.error('悬浮组件请输入描述')
  1820. return false
  1821. }
  1822. } else if (item?.wxad_styleType === 2) {
  1823. if (!item?.title) {
  1824. message.error('悬浮组件请输入标题')
  1825. return false
  1826. }
  1827. }
  1828. }
  1829. return true
  1830. })
  1831. if (b) {
  1832. } else {
  1833. return
  1834. }
  1835. }
  1836. if (a) {
  1837. setLastVisible(true)
  1838. }
  1839. }, [lastVisible, state, componentItem])
  1840. /** 保存 */
  1841. const saveHandle = useCallback(() => {
  1842. let { content, pageBackColor } = state
  1843. if (!shareTittle || !shareDesc) {
  1844. message.error('请填写分享标题或者描述')
  1845. return
  1846. }
  1847. let layoutName: string = pageName
  1848. if (!pageName) {
  1849. layoutName = `${id ? '复制' : ''}原生推广页` + moment().format("YYYYMMDDHHmmss") + '_' + currentUser?.userId
  1850. }
  1851. let pageContextList: Array<ImgProps | TopImg | TopVideo | TopSlider | Text | GhButton> = content?.map((item: { elementType: string, comptActive: boolean, activeIndex: boolean }) => {
  1852. let { elementType, comptActive, activeIndex, ...data } = item
  1853. let typeKey = getTypeKey(elementType)
  1854. let newItem = { elementType }
  1855. if (elementType === 'IMAGE_TEXT') {
  1856. let { imageTextItem, ...spec } = data as any
  1857. let newImageTextItem = imageTextItem?.map((item: any) => {
  1858. let { content, ...newItem } = item
  1859. let key = getTypeKey(newItem.subElemType)
  1860. newItem[key] = content
  1861. return newItem
  1862. })
  1863. newItem[typeKey] = {
  1864. ...spec,
  1865. imageTextItem: newImageTextItem
  1866. }
  1867. } else {
  1868. newItem[typeKey] = data
  1869. }
  1870. return newItem
  1871. })
  1872. let globalSpec: any = {}
  1873. if (componentItem?.length > 0) {
  1874. let globalElementsSpecList = componentItem?.map((item: { elementType: string, comptActive: boolean, componentItem: { elementType: string } }) => {
  1875. let { elementType, comptActive, componentItem, ...data } = item
  1876. let typeKey = getTypeKey(elementType);
  1877. let newItem = { elementType };
  1878. (data as any).elementType = componentItem.elementType
  1879. if (componentItem?.elementType) {
  1880. let { elementType, ...data1 } = componentItem
  1881. let type1Key = getTypeKey(elementType);
  1882. data[type1Key] = data1
  1883. }
  1884. newItem[typeKey] = data
  1885. return newItem
  1886. })
  1887. globalSpec.globalElementsSpecList = globalElementsSpecList
  1888. } else {
  1889. globalSpec = null
  1890. }
  1891. let pageSpecs = {
  1892. bgColor: pageBackColor,
  1893. pageElementsSpecList: pageContextList
  1894. }
  1895. let params = {
  1896. mediaType: 'PAGE',
  1897. folder: false,
  1898. parentId,
  1899. title: layoutName,
  1900. pageName: layoutName,
  1901. belongUser: belongUser === '0' ? false : true,
  1902. sort,
  1903. pageSpecsList: [pageSpecs],
  1904. globalSpec,
  1905. shareContentSpec: {
  1906. shareTitle: shareTittle,
  1907. shareDescription: shareDesc
  1908. }
  1909. }
  1910. add.run(params).then(res => {
  1911. if (res) {
  1912. ajax.refresh()
  1913. hideModal && hideModal()
  1914. }
  1915. })
  1916. }, [state, shareTittle, pageName, parentId, sort, shareDesc, belongUser, currentUser, ajax, id, componentItem])
  1917. return <Drawer
  1918. title={
  1919. <div className={style.drawerTitle}>
  1920. <div>
  1921. <Space>
  1922. <span>{id ? '复制' : '创建'}推广页</span>
  1923. </Space>
  1924. </div>
  1925. <Button type='primary' size='small' className={style.next} onClick={() => { nextHandle() }}>下一步 <SwapRightOutlined /></Button>
  1926. </div>
  1927. }
  1928. placement="right"
  1929. onClose={() => {
  1930. modal.confirm(config);
  1931. }}
  1932. visible={visible}
  1933. width='90%'
  1934. className={`addDraw ${style.drawer}`}
  1935. >
  1936. {/* 选择素材 */}
  1937. {selectImgVisible && <SelectCloud visible={selectImgVisible} sliderImgContent={sliderImgContent} onClose={() => setSelectImgVisible(false)} onChange={setImg} />}
  1938. <Modal
  1939. title={<>
  1940. <div style={{ marginBottom: 2, color: '#1f1f1f' }}>分享设置</div>
  1941. <div style={{ color: '#a3a3a3', fontSize: 12 }}>设置推广页的分享样式</div>
  1942. </>}
  1943. visible={lastVisible}
  1944. confirmLoading={add.loading}
  1945. onOk={saveHandle}
  1946. onCancel={() => { setLastVisible(false) }}
  1947. >
  1948. <Form labelCol={{ span: 4 }}>
  1949. <Form.Item label="落地页名称">
  1950. <Space><Input placeholder='落地页名称(不填写,创建时间+创建者ID)' value={pageName} onChange={(e) => { setPageName(e.target.value) }} style={txtLength(pageName) > 60 ? { width: 300, borderColor: 'red' } : { width: 300 }} /><span style={txtLength(pageName) > 60 ? { color: 'red' } : {}}>{txtLength(pageName)}/60</span></Space>
  1951. </Form.Item>
  1952. <Form.Item label="分享标题">
  1953. <Space><Input placeholder='建议与详情页面主题相符' value={shareTittle} onChange={(e) => { setShareTittle(e.target.value) }} style={txtLength(shareTittle) > 20 ? { width: 300, borderColor: 'red' } : { width: 300 }} /><span style={txtLength(shareTittle) > 20 ? { color: 'red' } : {}}>{txtLength(shareTittle)}/20</span></Space>
  1954. </Form.Item>
  1955. <Form.Item label="分享描述">
  1956. <Space><Input placeholder='对标题的简要解读' style={txtLength(shareDesc) > 30 ? { width: 300, borderColor: 'red' } : { width: 300 }} value={shareDesc} onChange={(e) => { setShareDesc(e.target.value) }} /><span style={txtLength(shareTittle) > 30 ? { color: 'red' } : {}}>{txtLength(shareDesc)}/30</span></Space>
  1957. </Form.Item>
  1958. <Form.Item label="排序" tooltip="值越大越靠前">
  1959. <InputNumber placeholder='输入排序' min={0} value={sort} onChange={(e) => { setSort(e) }} />
  1960. </Form.Item>
  1961. </Form>
  1962. </Modal>
  1963. <Spin spinning={get.loading}>
  1964. <div className={style.boxCont}>
  1965. <Row className={style.row}>
  1966. <Col flex="320px" className={style.right}>
  1967. <div className={style.title}>顶部组件</div>
  1968. <div className={style.assembly}>
  1969. {
  1970. content[0].elementType === 'empty' ? <>
  1971. <div {...getDragProps(`TOP_IMAGE`)}> <Topimg /> <span>图片</span> </div>
  1972. <div {...getDragProps(`TOP_SLIDER`)}> <Topslider /> <span>轮播图</span> </div>
  1973. <div {...getDragProps(`TOP_VIDEO`)}> <Topvideo /> <span>视频</span></div>
  1974. </>
  1975. :
  1976. <>
  1977. <div className={style.disabled}> <Topimg /> <span>图片</span> </div>
  1978. <div className={style.disabled}> <Topslider /> <span>轮播图</span> </div>
  1979. <div className={style.disabled}> <Topvideo /> <span>视频</span></div>
  1980. </>
  1981. }
  1982. </div>
  1983. <div className={style.title}>基础组件</div>
  1984. <div className={style.assembly}>
  1985. <div {...getDragPropsCon(`IMAGE`)}> <Img /> <span className="my">图片</span> </div>
  1986. <div {...getDragPropsCon(`TEXT`)}> <MyText /> <span>文字</span> </div>
  1987. </div>
  1988. <div className={style.title}>转化按钮</div>
  1989. <div className={style.assembly}>
  1990. <div {...getDragPropsCon(`GH`)}> <FollowAcc /> <span className="my">关注公众号</span> </div>
  1991. {/* <div {...getDragPropsCon(`JumpLink`)}> <JumpLink /> <span className="my">跳转链接</span> </div> */}
  1992. <div {...getDragPropsCon(`ENTERPRISE_WX`)}> <WxAutoSvg /> <span className="my">添加商家微信</span> </div>
  1993. </div>
  1994. <div className={style.title}>营销组件</div>
  1995. <div className={style.assembly}>
  1996. <div {...getDragPropsCon(`IMAGE_TEXT`)}> <ImgText /> <span className="my">图文复合组件</span> </div>
  1997. {componentItem?.some((item: { elementType: string }) => item.elementType === 'FLOAT_BUTTON') ?
  1998. <div className={style.disabled}> <FloatbuttonSvg /> <span>悬浮组件</span></div> : <div {...getDragPropsCon(`FLOAT_BUTTON`)}> <FloatbuttonSvg /> <span className="my">悬浮组件</span></div>
  1999. }
  2000. </div>
  2001. </Col>
  2002. <Col flex="auto" className={style.center} onClick={installActiveNull}>
  2003. <div className={style.page} style={{ backgroundColor: pageBackColor || '#FFFFFF' }}>
  2004. {/* 头部 */}
  2005. <div>{topCon}</div>
  2006. {/* 内容*/}
  2007. <div className={`comptPlaceholder lastChild`} id="comptCon">
  2008. {comptCon()}
  2009. </div>
  2010. <div className={style.sidebar}>
  2011. <div>
  2012. <ColorPicker onColor={(color: string) => { dispatch({ type: 'setPageBackColor', params: { pageBackColor: color } }) }} color={pageBackColor}></ColorPicker>
  2013. <div style={{ marginTop: 4 }}>背景</div>
  2014. </div>
  2015. </div>
  2016. </div>
  2017. </Col>
  2018. <Col flex="380px" className={style.left}>{rightCon()}</Col>
  2019. </Row>
  2020. </div>
  2021. </Spin>
  2022. </Drawer>
  2023. }
  2024. export default React.memo(AddLandingPage)