Forráskód Böngészése

Merge branch 'wangjianxin' of http://git.zanxiangnet.com/wjx/ad-manage into shenwu

shenwu 2 éve
szülő
commit
3d6acc44fc
26 módosított fájl, 7155 hozzáadás és 267 törlés
  1. 1 0
      src/components/ColorPicker1/index.less
  2. 7 6
      src/components/ColorPicker1/index.tsx
  3. 4 1
      src/components/FileBoxAD/index.less
  4. 76 56
      src/components/FileBoxAD/index.tsx
  5. 23 62
      src/models/useLaunchAdq/useBdMedia.ts
  6. 53 95
      src/models/useLaunchAdq/useBdMediaPup.ts
  7. 970 0
      src/pages/launchSystemNew/components/addLandingPage/content.ts
  8. 61 0
      src/pages/launchSystemNew/components/addLandingPage/dropCon.tsx
  9. 159 0
      src/pages/launchSystemNew/components/addLandingPage/index.less
  10. 2269 0
      src/pages/launchSystemNew/components/addLandingPage/index.tsx
  11. 988 0
      src/pages/launchSystemNew/components/addLandingPage/index1.less
  12. 69 0
      src/pages/launchSystemNew/components/addLandingPage/inputAcc.tsx
  13. 91 0
      src/pages/launchSystemNew/components/addLandingPage/landingPageReducer.ts
  14. 369 0
      src/pages/launchSystemNew/components/addLandingPage/selectAccount.tsx
  15. 320 0
      src/pages/launchSystemNew/components/addLandingPage/sortable.tsx
  16. 104 0
      src/pages/launchSystemNew/components/addLandingPage/tableConfigAd.tsx
  17. 92 0
      src/pages/launchSystemNew/components/bathLauCopy/index.less
  18. 543 0
      src/pages/launchSystemNew/components/bathLauCopy/index.tsx
  19. 8 0
      src/pages/launchSystemNew/components/lookLanding/index.less
  20. 320 0
      src/pages/launchSystemNew/components/lookLanding/index.tsx
  21. 22 0
      src/pages/launchSystemNew/components/selectCloud/index.less
  22. 95 0
      src/pages/launchSystemNew/components/selectCloud/index.tsx
  23. 30 4
      src/pages/launchSystemNew/material/cloud/index.tsx
  24. 379 0
      src/pages/launchSystemNew/req.ts
  25. 81 43
      src/services/launchAdq/material.ts
  26. 21 0
      src/utils/utils.ts

+ 1 - 0
src/components/ColorPicker1/index.less

@@ -15,6 +15,7 @@
     }
 } 
 .sketchPicker{
+    width: 240px !important;
     border-radius: 0 !important;
     box-shadow: none !important;
     padding: 0 !important;

+ 7 - 6
src/components/ColorPicker1/index.tsx

@@ -8,19 +8,20 @@ type props = {
 }
 
 const ColorPicker = (props: props) => {
-    let {onColor, color = "#000000" } = props
+    let { onColor, color = "#000000" } = props
 
     return <Tooltip
         color="#FFF"
-        title={<SketchPicker 
-            color={color} 
-            presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#BD10E0', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#CBEDBE', '#FFF4D9']} 
-            onChange={(e: any)=>{onColor(e?.hex)}} 
+        title={<SketchPicker
+            color={color}
+            presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#BD10E0', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#CBEDBE', '#FFF4D9']}
+            onChange={(e: any) => { onColor(e?.hex) }}
             className={style.sketchPicker}
         />}
+        overlayInnerStyle={{ width: 255 }}
         trigger='click'>
         <div className={style.swatch}>
-            <div className={style.color} style={{backgroundColor: color}}/>
+            <div className={style.color} style={{ backgroundColor: color }} />
         </div>
     </Tooltip>
 }

+ 4 - 1
src/components/FileBoxAD/index.less

@@ -17,6 +17,7 @@
   align-items: center;
   padding: 10px 0;
   position: relative;
+  font-size: 12px;
   &:hover {
     background-color: #c5d8f0;
     border-radius: 5px;
@@ -29,7 +30,7 @@
   }
 }
 .flex_box_name {
-  width: 85%;
+  width: 100%;
   overflow: hidden;
   text-overflow: ellipsis;
   white-space: nowrap;
@@ -121,6 +122,7 @@
   align-items: center;
   padding: 10px 20px;
   position: relative;
+  font-size: 12px;
   img {
     height: 100px;
     width: 130px;
@@ -340,6 +342,7 @@
   align-items: center;
   justify-content: center;
   margin-bottom: 10px;
+  margin-top: 5px;
 }
 .wxSelect{
   position: absolute;

+ 76 - 56
src/components/FileBoxAD/index.tsx

@@ -1,6 +1,6 @@
 import useCopy from "@/Hook/useCopy"
 import React, { useCallback, useEffect, useState } from "react"
-import { Image, message, Pagination, Popconfirm, Spin } from 'antd'
+import { Carousel, Image, message, Pagination, Popconfirm, Spin } from 'antd'
 import style from './index.less'
 import FileModal from './components/fileModal'
 import { useModel } from "umi"
@@ -25,7 +25,7 @@ interface News {
 interface Item {
     id: number,
     folder: boolean,//是否是文件夹
-    mediaType: 'VIDEO' | 'IMG',//类型
+    mediaType: 'VIDEO' | 'IMG' | 'PAGE',//类型
     number: string,//编号
     title: string,//名称
     url: string,//链接
@@ -39,34 +39,31 @@ interface Item {
     videoTitle: string,//视频标题
     width: number, // 宽
     height: number,  // 高
+    pageSpecsList?: any[],
 }
 interface Props {
     isAll?: boolean,//是否允许全选默认开启
     height?: any,//当使用为弹窗组件时设置高度以免太高
     noFile?: boolean,//是否禁止选择文件夹
-    isBd?: boolean,//是否强制使用本地数据
+    setPage?: (type: 0 | 1 | 2 | 3, data?: any) => void, // 0 创建 1 查看 2 复制
+    isBack?: boolean,  // 是否需要回填
 }
 function FlieBox(props: Props) {
 
-    const { isAll = true, height, noFile = false, isBd } = props
-    const { state, set, dels, listImg, listVideo, get, onFile, allFile, delPupOn, delPupOff, changeClickFile, editFile, fileClick, treeClick, pathClick, getList, edit_media_folder, get_folder_tree_img, get_folder_tree_video } = useModel(isAll || isBd ? 'useLaunchAdq.useBdMedia' : 'useLaunchAdq.useBdMediaPup')
-    const { fileVisible, belongUser, selectFile, delPupId, xy, rightClickPup, path, publicPath, parentId, imgVisrible, mediaType, sortVisible } = state
+    const { isAll = true, height, noFile = false, setPage, isBack = false } = props
+    const { state, set, dels, list, get, onFile, allFile, delPupOn, delPupOff, changeClickFile, editFile, fileClick, treeClick, pathClick, getList, edit_media_folder, get_folder_tree } = useModel(isAll ? 'useLaunchAdq.useBdMedia' : 'useLaunchAdq.useBdMediaPup')
+    const { fileVisible, belongUser, selectFile, selectItem, delPupId, xy, rightClickPup, path, publicPath, parentId, imgVisrible, mediaType, sortVisible } = state
     const { copy } = useCopy()
     const fileImg = require('../../../public/file.png')
     const [moveId, setMoveId] = useState<any>('')//移动的素材ID
-    const [treeEl, item, folderId, setActionId, setHoverId] = TreeBox({ data: mediaType === 'IMG' ? get_folder_tree_img.data : get_folder_tree_video.data, belongUser })
+    const [treeEl, item, folderId, setActionId, setHoverId] = TreeBox({ data: get_folder_tree.data, belongUser })
     const [listData, setListData] = useState<any>({})
+    const [imgVisible, setImgVisible] = useState<boolean>(false)
 
     // 获取数据
     useEffect(() => {
-        if (mediaType === 'IMG') {
-            setListData(listImg?.data)
-        } else if (mediaType === 'VIDEO') {
-            setListData(listVideo?.data)
-        } else {
-            setListData({})
-        }
-    }, [listImg, listVideo, mediaType])
+        setListData(list?.data)
+    }, [list, mediaType])
     /**复制编号 */
     const copyId = useCallback((e: React.MouseEvent<HTMLSpanElement, MouseEvent>, value: string) => {
         e.stopPropagation()//阻止冒泡传递到文件夹被点击事件
@@ -79,13 +76,19 @@ function FlieBox(props: Props) {
                 {
                     isAll && <li onClick={allFile}>全选/反选</li>
                 }
-                <li onClick={(e) => { delPupOn(rightClickPup.id); onFile(e, rightClickPup, isAll, true) }}> 删除</li>
-                <li onClick={() => { editFile() }}>编辑</li>
+                {mediaType === 'PAGE' ? <>
+                    <li onClick={() => { setPage && setPage(1, rightClickPup.id) }}>查看</li>
+                    <li onClick={() => { setPage && setPage(2, rightClickPup.id) }}>复制</li>
+                    <li onClick={() => { setPage && setPage(3, rightClickPup.id) }}>批量复制</li>
+                </> : <>
+                    <li onClick={() => { editFile() }}>编辑</li>
+                </>}
                 <li onClick={() => { set({ actionItem: rightClickPup, sortVisible: true }) }}>编辑排序</li>
                 <li onClick={() => { set({ fileVisible: true }) }}>新建文件夹</li>
                 <li onClick={() => { set({ imgVisrible: true }) }}>新建素材</li>
+                <li onClick={(e) => { delPupOn(rightClickPup.id); onFile(e, rightClickPup, isAll, true) }} style={{ color: 'red' }}> 删除</li>
                 {
-                    isAll && <li onClick={dels}>删除选中文件</li>
+                    isAll && <li onClick={dels} style={{ color: 'red' }}>删除选中文件</li>
                 }
             </ul>
         }
@@ -94,13 +97,13 @@ function FlieBox(props: Props) {
                 isAll && <li onClick={allFile}>全选/反选</li>
             }
             {//防止K图文无限嵌套创建判断
-                (isAll !== false) ? <li onClick={() => { set({ fileVisible: true }) }}>新建文件夹</li> : <li>此处无法新建操作</li>
+                (isAll !== false) && <li onClick={() => { set({ fileVisible: true }) }}>新建文件夹</li> //: <li>此处无法新建操作</li>
             }
             {
-                (isAll !== false) && <li onClick={() => { set({ imgVisrible: true }) }}>新建素材</li>
+                mediaType === 'PAGE' ? <li onClick={() => { setPage && setPage(0) }}>新建素材</li> : <li onClick={() => { set({ imgVisrible: true }) }}>新建素材</li>
             }
             {
-                isAll && <li onClick={dels}>删除选中文件</li>
+                isAll && <li onClick={dels} style={{ color: 'red' }}>删除选中文件</li>
             }
         </ul>
     }, [xy, rightClickPup, allFile, mediaType])
@@ -130,7 +133,9 @@ function FlieBox(props: Props) {
     }, [])
     // /**切换个人|公共|父目录ID变化清空选中的文件*/
     useEffect(() => {
-        set({ selectFile: [] })
+        if (!isBack) {
+            set({ selectFile: [], selectItem: [] })
+        }
         if (belongUser == '1' && path) {//处理切换个人|公共时保留路径层级的请求
             set({ parentId: path[path?.length - 1]?.id || null })
         }
@@ -146,14 +151,8 @@ function FlieBox(props: Props) {
     }, [])
     /**获取目录树*/
     useEffect(() => {
-        // if (isBd) {
-        if (mediaType === 'IMG') {
-            get_folder_tree_img.run({ belongUser })
-        } else if (mediaType === 'VIDEO') {
-            get_folder_tree_video.run({ belongUser })
-        }
-        // }
-    }, [isAll, isBd, mediaType, belongUser])
+        get_folder_tree.run({ belongUser, mediaType })
+    }, [isAll, mediaType, belongUser])
     // 点击目录树进入对应目录
     useEffect(() => {
         if (item) {
@@ -175,11 +174,7 @@ function FlieBox(props: Props) {
                 if (folderId !== '' && moveId !== '') {
                     edit_media_folder.run({ sysMediaId: moveId, folderId, mediaType }).then(res => {
                         message.success('操作成功')
-                        if (mediaType === 'IMG') {
-                            listImg.refresh()
-                        } else if (mediaType === 'VIDEO') {
-                            listVideo.refresh()
-                        }
+                        list.refresh()
                     })
                 }
                 setActionId('')
@@ -189,20 +184,15 @@ function FlieBox(props: Props) {
         }
     }, [folderId, moveId, mediaType])
 
-
     return <div style={{ display: 'flex', flexFlow: 'row' }}>
-        {
-            mediaType === 'IMG' ?  get_folder_tree_img?.data?.length > 0 ? <div style={{ flexShrink: 0 }}>
-                {treeEl}
-            </div> : null : get_folder_tree_video?.data?.length > 0 ? <div style={{ flexShrink: 0 }}>
-                {treeEl}
-            </div> : null
-        }
+        {get_folder_tree?.data?.length > 0 && <div style={{ flexShrink: 0 }}>
+            {treeEl}
+        </div>}
         <div style={{ flexShrink: 1 }}>
             <div className={style.files} onContextMenu={rightMenu} style={height ? { height } : {}}>
                 {/* 关联公众号筛选 */}
                 <div className={style.wxSelect}>
-                    {/* <WxSelect mode={1} isAll={isAll} /> */}
+
                 </div>
                 {/* 层级路径 */}
                 <div className={style.path} >
@@ -247,9 +237,9 @@ function FlieBox(props: Props) {
                                                 e.preventDefault()
                                             }}
                                         >
-                                            <span className={`${style.select}`} onClick={(e) => { changeClickFile(e, item, isAll, noFile) }}>
+                                            {/* {isAll && <span className={`${style.select}`} onClick={(e) => { changeClickFile(e, item, isAll, noFile) }}>
                                                 <span role="img" aria-label="check" style={{ color: '#fff' }}><svg viewBox="64 64 896 896" focusable="false" data-icon="check" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 00-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"></path></svg></span>
-                                            </span>
+                                            </span>} */}
                                             <img src={fileImg}
                                                 className={style.flex_box_img}
                                                 onClick={(e: any) => { e.stopPropagation(); fileClick(item) }}
@@ -269,13 +259,43 @@ function FlieBox(props: Props) {
                                 </Popconfirm>
                             } else {
                                 {/* 图片模板 ,视频模板,音频模板*/ }
-                                let El = item.mediaType === 'IMG' ?
-                                    <Image src={item.url} onClick={(e) => { e.stopPropagation() }}
-                                    /> :
-                                    item.mediaType === 'VIDEO' ?
-                                        <video src={item.url} style={{ width: 130, height: 100 }} controls />
-                                        :
-                                        <audio src={item.url} controls style={{ width: 150 }} />
+                                let topPageElements: any
+                                let topName: string = ""
+                                if (mediaType === 'PAGE' && item?.pageSpecsList) {
+                                    topPageElements = item?.pageSpecsList[0]?.pageElementsSpecList[0]
+                                }
+                                let El = null
+                                if (mediaType === 'IMG') {
+                                    El = <Image src={item.url} onClick={(e) => { e.stopPropagation() }} />
+                                } else if (mediaType === 'VIDEO') {
+                                    El = <video src={item.url} style={{ width: 130, height: 100 }} controls />
+                                } else if (mediaType === 'PAGE') {
+                                    switch (topPageElements?.elementType) {
+                                        case 'TOP_IMAGE':
+                                            topName = "顶部图片"
+                                            El = <Image src={topPageElements?.topImageSpec?.imageUrl} onClick={(e) => { e.stopPropagation() }} />
+                                            break
+                                        case 'TOP_SLIDER':
+                                            topName = "顶部轮播图"
+                                            El = <>
+                                                <Carousel autoplay style={{ width: 150, textAlign: 'center' }}>
+                                                    {topPageElements?.topSliderSpec?.imageUrlList?.map((item: string, index: number) => <div key={index}>
+                                                        <Image preview={{ visible: false }} src={item} onClick={(e) => { e.stopPropagation(); setImgVisible(true) }} />
+                                                    </div>)}
+                                                </Carousel>
+                                                <div style={{ display: 'none' }}>
+                                                    <Image.PreviewGroup preview={{ visible: imgVisible, onVisibleChange: vis => setImgVisible(vis) }}>
+                                                        {topPageElements?.topSliderSpec?.imageUrlList?.map((item: string, index: number) => <Image src={item} key={index} />)}
+                                                    </Image.PreviewGroup>
+                                                </div>
+                                            </>
+                                            break
+                                        case 'TOP_VIDEO':
+                                            topName = "顶部视频"
+                                            El = <video src={topPageElements?.topVideoSpec?.videoUrl} style={{ width: 130, height: 100 }} controls />
+                                            break
+                                    }
+                                }
                                 return <Popconfirm
                                     title="确定要删除吗?"
                                     onConfirm={() => { dels(item.id) }}
@@ -287,7 +307,7 @@ function FlieBox(props: Props) {
                                 >
                                     <Spin tip='正在请求素材详情,请耐心等待...' spinning={get?.loading}>
                                         <div
-                                            className={`${style.image_box} ${selectFile?.some((id: number) => id === item.id) ? style.action : ''}`}
+                                            className={`${style.image_box} ${!isBack ? (selectFile?.some((id: number) => id === item.id) ? style.action : '') : (selectItem?.some((item1: { url: string }) => item1.url === item.url) ? style.action : '')}`}
                                             onContextMenu={(e) => { rightMenu(e, item) }}
                                             onClick={(e) => { changeClickFile(e, item, isAll) }}
                                             {...moveConfig(item)}
@@ -296,8 +316,8 @@ function FlieBox(props: Props) {
                                                 <span role="img" aria-label="check" style={{ color: '#fff' }}><svg viewBox="64 64 896 896" focusable="false" data-icon="check" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 00-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"></path></svg></span>
                                             </span>
                                             {El}
-                                            <span className={style.flex_box_name} onClick={(e) => { copyId(e, item?.url) }}>{item?.videoTitle || item?.title}</span>
-                                            <span onClick={(e) => { copyId(e, item?.number) }} >{item?.width}*{item.height}</span>
+                                            <span className={style.flex_box_name} onClick={(e) => { copyId(e, item?.videoTitle || item?.title) }}>{item?.videoTitle || item?.title}</span>
+                                            {mediaType === 'PAGE' ? <span>{topName}</span> : <span>{item?.width}*{item.height}</span>}
                                         </div>
                                     </Spin>
                                 </Popconfirm>

+ 23 - 62
src/models/useLaunchAdq/useBdMedia.ts

@@ -1,6 +1,6 @@
 import getMD5 from '@/components/MD5';
 import { useAjax } from '@/Hook/useAjax';
-import { bdSysMediaListImg, bdSysMediaListVideo, bdSysMediaAdd, delMedia, bdSysMediaEdit, getFileUrl, getMedia, configSortApi, getFolderTreeImg, getFolderTreeVideo, editMediaFolder } from '@/services/launchAdq/material';
+import { bdSysMediaList, bdSysMediaAdd, delMedia, bdSysMediaEdit, getFileUrl, getMedia, configSortApi, getFolderTree, editMediaFolder } from '@/services/launchAdq/material';
 import { blobToBase64, dataURLtoFile, videoMessage } from '@/utils/compress';
 import { getImgSize } from '@/utils/utils';
 import { message } from 'antd';
@@ -18,7 +18,7 @@ type State = {
     videoTitle?: string,//video文件名称
     videoDescription?: string,//视频描述
     belongUser?: any,//0公共本地|1个人本地
-    mediaType?: 'VIDEO' | 'IMG',//类型
+    mediaType?: 'VIDEO' | 'IMG' | 'PAGE',//类型
     parentId?: any,//上级目录ID,顶级使用null
     url?: string,//素材地址
     pathId?: string,//路径ID
@@ -44,7 +44,8 @@ export type Action = {
 }
 const typeEnum = {
     'IMG': '图片',
-    'VIDEO': '视频'
+    'VIDEO': '视频',
+    'PAGE': '落地页'
 }
 function reducer(state: State, action: Action) {
     let { type, params } = action
@@ -85,14 +86,12 @@ const initData: State = {
 function useBdMediaPup() {
     const [state, dispatch]: [State, Dispatch<Action>] = useReducer(reducer, initData)
     const { fileName, sort, belongUser, mediaType, parentId, selectFile, delPupId, rightClickPup, actionItem, path, publicPath, size, videoTitle, videoDescription } = state
-    const listImg = useAjax((params) => bdSysMediaListImg(params))
-    const listVideo = useAjax((params) => bdSysMediaListVideo(params))
+    const list = useAjax((params) => bdSysMediaList(params))
     const add = useAjax((params) => bdSysMediaAdd(params), { msgNmae: '新增' })
     const del = useAjax((params) => delMedia(params), { msgNmae: '删除' })
     const edit = useAjax((params) => bdSysMediaEdit(params), { msgNmae: '编辑' })
     const configSort = useAjax((params) => configSortApi(params), { msgNmae: '排序' })
-    const get_folder_tree_img = useAjax((params: any) => getFolderTreeImg(params))
-    const get_folder_tree_video = useAjax((params: any) => getFolderTreeVideo(params))
+    const get_folder_tree = useAjax((params: any) => getFolderTree(params))
     const edit_media_folder = useAjax((params: any) => editMediaFolder(params))
     const get = useAjax((params) => getMedia(params))//获取本地素材详情
     const [isOk, setIsOk] = useState<boolean>(true)
@@ -117,17 +116,8 @@ function useBdMediaPup() {
         if (fileName) {
             let obj = { title: fileName, mediaType, folder: true, parentId, belongUser: belongUser === '0' ? false : true, sort }
             add.run(obj).then((res) => {
-                if (mediaType === 'IMG') {
-                    get_folder_tree_img.refresh()
-                } else if (mediaType === 'VIDEO') {
-                    get_folder_tree_video.refresh()
-                }
-
-                if (mediaType === 'IMG') {
-                    listImg.refresh()
-                } else if (mediaType === 'VIDEO') {
-                    listVideo.refresh()
-                }
+                get_folder_tree.refresh()
+                list.refresh()
                 offEditFile()//关闭弹窗并清空相关数据
             })
         }
@@ -215,12 +205,7 @@ function useBdMediaPup() {
                                         obj['videoDescription'] = data?.videoDescription
                                     }
                                     add.run(obj).then((res) => {
-                                        console.log(res)
-                                        if (mediaType === 'IMG') {
-                                            listImg.refresh()
-                                        } else if (mediaType === 'VIDEO') {
-                                            listVideo.refresh()
-                                        }
+                                        list.refresh()
                                         offEditFile()//关闭弹窗并清空相关数据
                                         set({ upLoadLoading: false })
                                     }).catch(() => set({ upLoadLoading: false }))
@@ -243,11 +228,7 @@ function useBdMediaPup() {
                 obj['videoDescription'] = videoDescription
             }
             edit.run(obj).then((res) => {
-                if (mediaType === 'IMG') {
-                    listImg.refresh()
-                } else if (mediaType === 'VIDEO') {
-                    listVideo.refresh()
-                }
+                list.refresh()
                 offEditFile()//关闭弹窗并清空相关数据
             })
         }
@@ -261,30 +242,19 @@ function useBdMediaPup() {
                 del.run({ sysMediaId: id, mediaType }).then(() => {
                     set({ selectFile: selectFile?.filter(i => i !== id) })//清理已删除文件
                     if (index === len - 1) {
-                        if (mediaType === 'IMG') {
-                            listImg.refresh()
-                            get_folder_tree_img.refresh()
-                        } else if (mediaType === 'VIDEO') {
-                            listVideo.refresh()
-                            get_folder_tree_video.refresh()
-                        }
+                        list.refresh()
+                        get_folder_tree.refresh()
                     }
                 })
             })
         }
-    }, [listImg, listVideo, selectFile, mediaType])
+    }, [list, selectFile, mediaType])
     /**获取本地素材数据列表 */
     const getList = useCallback((props?: any) => {
-        let obj = { pageSize: 20, pageNum: 1, belongUser: belongUser === '0' ? false : true, parentId, ...props }
-        if (mediaType === 'IMG') {
-            listImg.run(obj).then((res) => {
-                setIsOk(true)
-            })
-        } else if (mediaType === 'VIDEO') {
-            listVideo.run(obj).then((res) => {
-                setIsOk(true)
-            })
-        }
+        let obj = { pageSize: 20, pageNum: 1, belongUser: belongUser === '0' ? false : true, mediaType, parentId, ...props }
+        list.run(obj).then((res) => {
+            setIsOk(true)
+        })
     }, [mediaType, belongUser, parentId])
     /**选中文件 single 开启可单选为了在右键删除选择时只选一个*/
     const onFile = useCallback((e: any, item: { id: any, folder?: boolean }, isAll?: boolean, single?: boolean) => {
@@ -413,18 +383,11 @@ function useBdMediaPup() {
     /**全选反选文件*/
     const allFile = useCallback(() => {
         let allArr: any[] = []
-
-        if (mediaType === 'IMG') {
-            listImg?.data?.records?.forEach((item: { id: any }) => {
-                allArr.push(item.id)
-            })
-        } else if (mediaType === "VIDEO") {
-            listVideo?.data?.records?.forEach((item: { id: any }) => {
-                allArr.push(item.id)
-            })
-        }
+        list?.data?.records?.forEach((item: { id: any }) => {
+            allArr.push(item.id)
+        })
         set({ selectFile: allArr.filter((i) => selectFile?.every(id => id !== i)) })
-    }, [selectFile, listImg, listVideo, mediaType])
+    }, [selectFile, list, mediaType])
     return {
         state,
         init,
@@ -446,14 +409,12 @@ function useBdMediaPup() {
         pathClick,
         configSort,
         fileUrl,
-        listImg,
-        listVideo,
+        list,
         add,
         get,
         edit,
         typeEnum,
-        get_folder_tree_img,
-        get_folder_tree_video,
+        get_folder_tree,
         edit_media_folder
     }
 }

+ 53 - 95
src/models/useLaunchAdq/useBdMediaPup.ts

@@ -1,6 +1,6 @@
 import getMD5 from '@/components/MD5';
 import { useAjax } from '@/Hook/useAjax';
-import { bdSysMediaListImg, bdSysMediaListVideo, bdSysMediaAdd, delMedia, bdSysMediaEdit, getFileUrl, getMedia, configSortApi, getFolderTreeImg, getFolderTreeVideo, editMediaFolder } from '@/services/launchAdq/material';
+import { bdSysMediaList, bdSysMediaAdd, delMedia, bdSysMediaEdit, getFileUrl, getMedia, configSortApi, getFolderTree, editMediaFolder } from '@/services/launchAdq/material';
 import { blobToBase64, dataURLtoFile, videoMessage } from '@/utils/compress';
 import { getImgSize } from '@/utils/utils';
 import { message } from 'antd';
@@ -18,7 +18,7 @@ type State = {
     videoTitle?: string,//video文件名称
     videoDescription?: string,//视频描述
     belongUser?: any,//0公共本地|1个人本地
-    mediaType?: 'IMG' | 'VIDEO',//类型
+    mediaType?: 'VIDEO' | 'IMG' | 'PAGE', // 类型
     parentId?: any,//上级目录ID,顶级使用null
     url?: string,//素材地址
     pathId?: string,//路径ID
@@ -30,12 +30,14 @@ type State = {
     path?: any[],//个人路径
     publicPath?: any[],//本地路径
     file?: File,//素材文件
-    selectItem?: any,//单选素材时存放选中的素材
+    selectItem?: any[],//单选素材时存放选中的素材
     knewsdefaultData?: any,//k图文编辑时的默认内容
-    sortVisible?:boolean,//排序弹窗
+    sortVisible?: boolean,//排序弹窗
 
+    num?: number, //选择数量
     size?: number,  // 需要上传素材的大小
-    upLoadLoading?: boolean
+    upLoadLoading?: boolean,
+    cloudSize?: any[]
 }
 export type Action = {
     type: 'set' | 'init',
@@ -77,22 +79,22 @@ const initData: State = {
     path: [{ title: '个人本地', number: '0' }],
     imgVisrible: false,
     newsVisrible: false,
-    sortVisible:false,
-    sort: 0
+    sortVisible: false,
+    sort: 0,
+
+    num: 1
 }
 /**本地素材管理器 */
 function useBdMediaPup() {
     const [state, dispatch]: [State, Dispatch<Action>] = useReducer(reducer, initData)
-    const { fileName, sort, belongUser, mediaType, parentId, selectFile, delPupId, rightClickPup, actionItem, path, publicPath, videoTitle, videoDescription, size } = state
-    const listImg = useAjax((params) => bdSysMediaListImg(params))
-    const listVideo = useAjax((params) => bdSysMediaListVideo(params))
+    const { fileName, sort, belongUser, mediaType, parentId, selectFile, selectItem, delPupId, cloudSize, num, rightClickPup, actionItem, path, publicPath, videoTitle, videoDescription, size } = state
+    const list = useAjax((params) => bdSysMediaList(params))
     const add = useAjax((params) => bdSysMediaAdd(params), { msgNmae: '新增' })
     const del = useAjax((params) => delMedia(params), { msgNmae: '删除' })
     const edit = useAjax((params) => bdSysMediaEdit(params), { msgNmae: '编辑' })
     const configSort = useAjax((params) => configSortApi(params), { msgNmae: '排序' })
     const get = useAjax((params) => getMedia(params))//获取图文详情
-    const get_folder_tree_img = useAjax((params: any) => getFolderTreeImg(params))
-    const get_folder_tree_video = useAjax((params: any) => getFolderTreeVideo(params))
+    const get_folder_tree = useAjax((params: any) => getFolderTree(params))
     const edit_media_folder = useAjax((params: any) => editMediaFolder(params))
     const [isOk, setIsOk] = useState<boolean>(true)
     //请求上传地址
@@ -114,17 +116,8 @@ function useBdMediaPup() {
         if (fileName) {
             let obj = { title: fileName, mediaType, folder: true, parentId, belongUser: belongUser === '0' ? false : true, sort }
             add.run(obj).then((res) => {
-                if (mediaType === 'IMG') {
-                    get_folder_tree_img.refresh()
-                } else if (mediaType === 'VIDEO') {
-                    get_folder_tree_video.refresh()
-                }
-
-                if (mediaType === 'IMG') {
-                    listImg.refresh()
-                } else if (mediaType === 'VIDEO') {
-                    listVideo.refresh()
-                }
+                get_folder_tree.refresh()
+                list.refresh()
                 offEditFile()//关闭弹窗并清空相关数据
             })
         }
@@ -212,12 +205,7 @@ function useBdMediaPup() {
                                         obj['videoDescription'] = data?.videoDescription
                                     }
                                     add.run(obj).then((res) => {
-                                        console.log(res)
-                                        if (mediaType === 'IMG') {
-                                            listImg.refresh()
-                                        } else if (mediaType === 'VIDEO') {
-                                            listVideo.refresh()
-                                        }
+                                        list.refresh()
                                         offEditFile()//关闭弹窗并清空相关数据
                                         set({ upLoadLoading: false })
                                     }).catch(() => set({ upLoadLoading: false }))
@@ -240,11 +228,7 @@ function useBdMediaPup() {
                 obj['videoDescription'] = videoDescription
             }
             edit.run(obj).then((res) => {
-                if (mediaType === 'IMG') {
-                    listImg.refresh()
-                } else if (mediaType === 'VIDEO') {
-                    listVideo.refresh()
-                }
+                list.refresh()
                 offEditFile()//关闭弹窗并清空相关数据
             })
         }
@@ -258,33 +242,20 @@ function useBdMediaPup() {
                 del.run({ sysMediaId: id, mediaType }).then(() => {
                     set({ selectFile: selectFile?.filter(i => i !== id) })//清理已删除文件
                     if (index === len - 1) {
-                        if (mediaType === 'IMG') {
-                            listImg.refresh()
-                            get_folder_tree_img.refresh()
-                        } else if (mediaType === 'VIDEO') {
-                            listVideo.refresh()
-                            get_folder_tree_video.refresh()
-                        }
+                        list.refresh()
+                        get_folder_tree.refresh()
                     }
                 })
             })
         }
-    }, [listImg, listVideo, selectFile, mediaType])
+    }, [list, selectFile, mediaType])
     /**获取本地素材数据列表 */
     const getList = useCallback((props?: any) => {
-        console.log('mediaType',mediaType)
-        let obj = { pageSize: 20, pageNum: 1, belongUser: belongUser === '0' ? false : true, parentId, ...props }
-        
-        if (mediaType === 'IMG') {
-            listImg.run(obj).then((res) => {
-                setIsOk(true)
-            })
-        } else if (mediaType === 'VIDEO') {
-            listVideo.run(obj).then((res) => {
-                setIsOk(true)
-            })
-        }
-    }, [listImg, listVideo, mediaType, belongUser, parentId])
+        let obj = { pageSize: 20, pageNum: 1, belongUser: belongUser === '0' ? false : true, mediaType, parentId, sizeQueries: cloudSize, ...props }
+        list.run(obj).then((res) => {
+            setIsOk(true)
+        })
+    }, [list, mediaType, belongUser, parentId, cloudSize])
     /**选中文件 single 开启可单选为了在右键删除选择时只选一个*/
     const onFile = useCallback((e: any, item: { id: any, folder?: boolean }, isAll?: boolean, single?: boolean) => {
         let { id } = item
@@ -296,7 +267,7 @@ function useBdMediaPup() {
                 message.error('不能选择文件夹')
                 return
             }
-            set({ selectFile: [item.id], selectItem: item })
+            set({ selectFile: [item.id], selectItem: [item] })
         }
     }, [selectFile])
     /**点击文件夹 */
@@ -312,15 +283,14 @@ function useBdMediaPup() {
             set({ parentId: item.id })
             getList({ parentId: item.id })//请求对应文件夹列表
         }
-
     }, [path, publicPath, mediaType, belongUser, isOk])
-     /**点击目录树*/
-     const treeClick=useCallback((item) => {
+    /**点击目录树*/
+    const treeClick = useCallback((item) => {
         if (isOk) {
             setIsOk(false)
             console.log(item, belongUser == '1')
             if (belongUser == '1' && path) {
-                    set({ path: [path[0], item] })
+                set({ path: [path[0], item] })
             }
             if (belongUser == '0' && publicPath) {
                 set({ publicPath: [publicPath[0], item] })
@@ -357,33 +327,29 @@ function useBdMediaPup() {
     }, [selectFile])
     /**判断是取消还是选中文件的操作在点击打钩时使用 */
     const changeClickFile = useCallback((e: any, item: { id: any, folder?: boolean }, isAll?: boolean, noFile?: boolean) => {
-        console.log('2222222---->', noFile);
         let { id } = item
         e?.stopPropagation()//阻止冒泡传递到文件夹被点击事件
         let state = selectFile?.some((i) => i === id)
-        if (isAll) {
-            if (state) {//存在就是删除
-                set({ selectFile: selectFile?.filter(i => i !== id) })
-            } else {//否则新增
-                set({ selectFile: [...selectFile as number[], id] })
-            }
-        } else {//单选情况存在于选择素材弹窗组件
-            if (!noFile) {
-                if (item?.folder) {//假如是文件不让选择
-                    message.error('不能选择文件夹')
+        if (state) {//存在就是删除
+            set({ selectFile: selectFile?.filter(i => i !== id), selectItem: selectItem?.filter((i: { id: number }) => i.id !== id) })
+        } else {//否则新增
+            if (num === 1) {
+                if (state) {//存在就是删除
+                    set({ selectFile: selectFile?.filter(i => i !== id), selectItem: [] })
+                } else {//否则新增
+                    set({ selectFile: [id], selectItem: [item] })
+                }
+            } else {
+                if (selectFile && num && (selectFile?.length >= num)) {
+                    message.error(`只能选择${num}张`)
                     return
                 }
-            }
-            
-            if (state) {//存在就是删除
-                set({ selectFile: selectFile?.filter(i => i !== id) })
-                set({ selectItem: null })
-            } else {//否则新增
-                set({ selectFile: [id] })
-                set({ selectItem: item })
+                let newSelectItem = selectItem || []
+                newSelectItem.push(item)
+                set({ selectItem: newSelectItem, selectFile: [...selectFile as number[], id] })
             }
         }
-    }, [selectFile, mediaType])
+    }, [selectFile, mediaType, num])
     /**开启删除弹窗 */
     const delPupOn = useCallback((delPupId) => {
         set({ delPupId })
@@ -408,22 +374,16 @@ function useBdMediaPup() {
     }, [rightClickPup])
     /**取消编辑后清空选中存放的数据并关闭弹窗*/
     const offEditFile = useCallback(() => {
-        set({ fileVisible: false, imgVisrible: false,sortVisible:false, actionItem: '', fileName: '', selectFile: selectFile?.filter(id => id !== actionItem?.id), sort: 0 })
+        set({ fileVisible: false, imgVisrible: false, sortVisible: false, actionItem: '', fileName: '', selectFile: selectFile?.filter(id => id !== actionItem?.id), sort: 0 })
     }, [selectFile, actionItem])
     /**全选反选文件*/
     const allFile = useCallback(() => {
         let allArr: any[] = []
-        if (mediaType === 'IMG') {
-            listImg?.data?.records?.forEach((item: { id: any }) => {
-                allArr.push(item.id)
-            })
-        } else if (mediaType === 'VIDEO') {
-            listVideo?.data?.records?.forEach((item: { id: any }) => {
-                allArr.push(item.id)
-            })
-        }
+        list?.data?.records?.forEach((item: { id: any }) => {
+            allArr.push(item.id)
+        })
         set({ selectFile: allArr.filter((i) => selectFile?.every(id => id !== i)) })
-    }, [selectFile, listImg, listVideo, mediaType])
+    }, [selectFile, list, mediaType])
     return {
         state,
         init,
@@ -444,14 +404,12 @@ function useBdMediaPup() {
         treeClick,
         pathClick,
         configSort,
-        listImg,
-        listVideo,
+        list,
         add,
         get,
         edit,
         typeEnum,
-        get_folder_tree_img,
-        get_folder_tree_video,
+        get_folder_tree,
         edit_media_folder
     }
 }

+ 970 - 0
src/pages/launchSystemNew/components/addLandingPage/content.ts

@@ -0,0 +1,970 @@
+import { Img, TopImg, TopSlider, TopVideo, Text, GhButton, WxAutoButton } from "../../req"
+
+// 头部内容
+export const topsliderContent: TopSlider = {
+    elementType: 'TOP_SLIDER',
+    imageUrlList: ["", "", ""],
+    width: 800,
+    height: 800,
+    activeIndex: 0
+}
+
+export const topimgContent: TopImg = {
+    elementType: 'TOP_IMAGE',
+    imageUrl: '',
+    width: 800,
+    height: 800,
+    adLocation: 'sns',
+    outerStyle: 0
+}
+
+export const topvideoNewContent: TopVideo = {
+    elementType: 'TOP_VIDEO',
+    videoUrl: '',
+    width: 640,
+    height: 360,
+    adLocation: 'sns',
+    outerStyle: 0
+}
+
+export const topvideoContent = {
+    id: 'widget_1625551322566_1',
+    widgetTypeV2: 'topvideo',
+    widgetType: 'top',
+    name: "顶部视频",
+    type: '61',
+    viewType: '0',
+    styleType: '0',
+    initWidth: 750,
+    initHeight: 750,
+    paddingTop: 0,
+    paddingBottom: 0,
+    paddingLeft: 0,
+    paddingRight: 0,
+    sightDisplayWidth: 0,
+    sightDisplayHeight: 0,
+    sightThumbUrl: '',
+    sightVideoUrl: '',
+    sightDisplayType: '0',
+    sightThumbMd5: '',
+    sightVideoMd5: '',
+    isFullScreen: '0',
+    videoDuration: '',
+    BitRate: 0,
+    video_name: 'topVideo',
+    video_type: 'mp4',
+    adLocation: 'sns',
+    fileSize: 0,
+    materialId: ''
+}
+
+// 内容
+export const imgContent: Img = {
+    elementType: 'IMAGE',
+    imageUrl: '',
+    width: 750,
+    height: 750,
+    paddingTop: 0,
+    paddingBottom: 0,
+}
+
+export const txtContent: Text = {
+    elementType: 'TEXT',
+    text: '',
+    fontStyle: 0,
+    textAlignment: 0,
+    fontSize: 20,
+    fontColor: '#595959',
+    paddingTop: 22,
+    paddingBottom: 22,
+}
+
+export const ghContent: GhButton = {
+    elementType: 'GH',
+    btnTitle: '关注公众号',
+    fontColor: '#FFFFFF',
+    btnBgColorTheme: '#07C160',
+    btnBorderColorTheme: '#FFFFFF',
+    btnFontType: 0,
+    useIcon: 0,
+    fastFollow: 1,
+    paddingTop: 28,
+    paddingBottom: 28,
+}
+
+export const linkContent = {
+    widgetTypeV2: "link",
+    widgetType: "button",
+    type: "21",
+    name: "跳转链接",
+    btnTitle: "了解详情",
+    subType: "0",
+    fontSize: "30",
+    fontColor: "#FFFFFF",
+    btnType: "0",
+    borderSize: "0",
+    btnHeight: "80",
+    btnBgColorTheme: "#07C160",
+    btnBorderColorTheme: "#FFFFFF",
+    btnFontType: "0",
+    btnStyle: "1",
+    paddingTop: 28,
+    paddingBottom: 28,
+    paddingRight: 185,
+    paddingLeft: 185,
+    cornerRadius: 4,
+    useIcon: "0",
+    id: "widget_1632641482474_3",
+    mpJumpType: "1",
+    origBtnJumpUrl: "",
+    btnJumpUrl: ""
+}
+
+// 添加商家微信
+export const wxAutoContent: WxAutoButton = {
+    elementType: 'ENTERPRISE_WX',
+    btnBgColorTheme: "#07C160",
+    btnBorderColorTheme: "#FFFFFF",
+    btnFontType: 0,
+    btnTitle: "联系商家",
+    useIcon: 0,
+    paddingTop: 28,
+    paddingBottom: 28,
+    fontColor: "#FFFFFF",
+}
+
+// 图文复合组件104
+export const shelfnewContent = {
+    widgetType: "shelfnew",
+    widgetTypeV2: "shelfnew",
+    name: "图文复合组件",
+    type: "104",
+    id: "widget_1638426338182_2",
+    layoutHeight: "228",
+    layoutWidth: "670",
+    paddingTop: 20,
+    paddingBottom: 20,
+    paddingLeft: 40,
+    paddingRight: 40,
+    borderColor: "#e5e5e5",
+    bgColor: "#ffffff",
+    borderSize: 1,
+    cornerRadius: 8,
+    isChooseJump: 1,
+    layoutItems: {
+        componentItem: [
+            {
+                layoutHeight: "228",
+                layoutWidth: "670",
+                type: "103",
+                subType: "1",
+                jumpMode: "btn_jump",
+                layoutItems: {
+                    componentItem: [
+                        {
+                            name: "图片",
+                            layoutHeight: "180",
+                            layoutWidth: "180",
+                            paddingTop: "24",
+                            paddingLeft: "24",
+                            pureImageUrl: "",  // 图片url
+                            type: "41",
+                            imageMd5: "",   // Md5
+                            cornerRadius: "4",
+                            materialId: '',
+                            wxad_IsSubNode: "1"
+                        },
+                        {
+                            layoutHeight: "228",
+                            layoutWidth: "470",
+                            paddingLeft: "24",
+                            paddingTop: "24",
+                            paddingRight: "40",
+                            subType: "0",
+                            type: "103",
+                            descType: "text",
+                            layoutItems: {
+                                componentItem: [
+                                    {
+                                        name: "标题",
+                                        content: "",  // 标题001
+                                        fontColor: "#353535",
+                                        fontSize: "32",
+                                        layoutHeight: "44",
+                                        layoutWidth: "410",
+                                        maxLines: "1",
+                                        showType: "1",
+                                        type: "1",
+                                        wxad_IsSubNode: "1"
+                                    },
+                                    {
+                                        name: "描述",
+                                        content: "",  // 文字描述001
+                                        fontColor: "#b2b2b2",
+                                        fontSize: "24",
+                                        layoutHeight: "40",
+                                        layoutWidth: "410",
+                                        paddingTop: "8",
+                                        type: "1",
+                                        wxad_IsSubNode: "1"
+                                    },
+                                    {
+                                        widgetTypeV2: "link",
+                                        widgetType: "button",
+                                        borderSize: "0",
+                                        btnBgColorTheme: "#07C160",
+                                        btnBorderColorTheme: "#FFFFFF",
+                                        btnFontType: "0",
+                                        btnHeight: "60",
+                                        btnJumpUrl: "",
+                                        btnStyle: "1",
+                                        btnTitle: "了解详情",
+                                        btnType: "0",
+                                        fontColor: "#FFFFFF",
+                                        fontSize: "26",
+                                        name: "跳转链接",
+                                        origBtnJumpUrl: "",
+                                        layoutWidth: "152",
+                                        layoutHeight: "60",
+                                        paddingBottom: "0",
+                                        paddingLeft: "0",
+                                        paddingRight: "0",
+                                        paddingTop: "28",
+                                        subType: "0",
+                                        mpJumpType: "1",
+                                        type: "21",
+                                        cornerRadius: "4"
+                                    }
+                                ]
+                            },
+                            wxad_IsSubNode: "1"
+                        }
+                    ]
+                }
+            }
+        ]
+    }
+}
+
+// 图文复合组件103
+export const shelfnewContent2 = {
+    widgetTypeV2: "shelfnew",
+    widgetType: "shelfnew",
+    name: "图文复合组件",
+    type: "103",
+    id: "widget_1638501500690_1",
+    layoutHeight: "542",
+    paddingLeft: "40",
+    paddingTop: "20",
+    paddingBottom: "20",
+    subType: "1",
+    isChooseJump: 1,
+    wxad_align: 0,
+    layoutItems: {
+        componentItem: [
+            {
+                layoutHeight: "542",
+                layoutWidth: "324",
+                type: "104",
+                borderColor: "#E5E5E5",
+                bgColor: "#FFFFFF",
+                borderSize: "1",
+                cornerRadius: "8",
+                layoutItems: {
+                    componentItem: [{
+                        jumpMode: "btn_jump",
+                        layoutHeight: "542",
+                        layoutWidth: "324",
+                        type: "103",
+                        subType: "0",
+                        descType: "text",
+                        layoutItems: {
+                            componentItem: [{
+                                name: "图片",
+                                layoutHeight: "300",
+                                pureImageUrl: "",
+                                type: "41",
+                                imageMd5: "",
+                                layoutWidth: "300",
+                                paddingLeft: "12",
+                                paddingTop: "12",
+                                cornerRadius: "4",
+                                materialId: '',
+                                wxad_IsSubNode: "1"
+                            }, {
+                                name: "标题",
+                                content: "",
+                                fontColor: "#353535",
+                                fontSize: "32",
+                                layoutHeight: "44",
+                                layoutWidth: "272",
+                                maxLines: "1",
+                                paddingLeft: "24",
+                                paddingTop: "16",
+                                showType: "1",
+                                type: "1",
+                                wxad_IsSubNode: "1"
+                            }, {
+                                name: "描述",
+                                content: "",
+                                fontColor: "#4D4D4D",
+                                fontSize: "24",
+                                layoutHeight: "40",
+                                layoutWidth: "272",
+                                paddingTop: "8",
+                                type: "1",
+                                wxad_IsSubNode: "1",
+                                paddingLeft: "24"
+                            }, {
+                                widgetTypeV2: "gh",
+                                widgetType: "button",
+                                type: "21",
+                                name: "商品关注公众号",
+                                btnTitle: "关注公众号",
+                                subType: "17",
+                                fontSize: "26",
+                                fontColor: "#FFFFFF",
+                                btnType: "0",
+                                borderSize: "0",
+                                btnHeight: "60",
+                                btnBgColorTheme: "#07C160",
+                                btnBorderColorTheme: "#FFFFFF",
+                                btnFontType: "0",
+                                btnStyle: "1",
+                                paddingTop: "28",
+                                paddingBottom: "0",
+                                paddingRight: "24",
+                                paddingLeft: "24",
+                                cornerRadius: 4,
+                                useIcon: "0",
+                                field21_1: {
+                                    origBtnJumpUrl: "",
+                                    wxad_guide_group_id: ""
+                                },
+                                id: "widget_1638502917231_4",
+                                layoutHeight: "60",
+                                layoutWidth: "152"
+                            }]
+                        }
+                    }]
+                },
+                wxad_IsSubNode: "1"
+            },
+            {
+                layoutHeight: "542",
+                layoutWidth: "324",
+                type: "104",
+                borderColor: "#E5E5E5",
+                bgColor: "#FFFFFF",
+                borderSize: "1",
+                cornerRadius: "8",
+                paddingLeft: '22',
+                layoutItems: {
+                    componentItem: [{
+                        jumpMode: "btn_jump",
+                        layoutHeight: "542",
+                        layoutWidth: "324",
+                        type: "103",
+                        subType: "0",
+                        descType: "text",
+                        layoutItems: {
+                            componentItem: [{
+                                name: "图片",
+                                layoutHeight: "300",
+                                pureImageUrl: "",
+                                type: "41",
+                                imageMd5: "",
+                                layoutWidth: "300",
+                                paddingLeft: "12",
+                                paddingRight: "12",
+                                paddingTop: "12",
+                                cornerRadius: "4",
+                                materialId: '',
+                                wxad_IsSubNode: "1"
+                            }, {
+                                name: "标题",
+                                content: "",
+                                fontColor: "#353535",
+                                fontSize: "32",
+                                layoutHeight: "44",
+                                layoutWidth: "272",
+                                maxLines: "1",
+                                paddingLeft: "24",
+                                paddingTop: "16",
+                                showType: "1",
+                                type: "1",
+                                wxad_IsSubNode: "1"
+                            }, {
+                                name: "描述",
+                                content: "",
+                                fontColor: "#4D4D4D",
+                                fontSize: "24",
+                                layoutHeight: "40",
+                                layoutWidth: "272",
+                                paddingLeft: "24",
+                                paddingTop: "8",
+                                type: "1",
+                                wxad_IsSubNode: "1"
+                            }, {
+                                widgetTypeV2: "gh",
+                                widgetType: "button",
+                                type: "21",
+                                name: "商品关注公众号",
+                                btnTitle: "关注公众号",
+                                subType: "17",
+                                fontSize: "26",
+                                fontColor: "#FFFFFF",
+                                btnType: "0",
+                                borderSize: "0",
+                                btnHeight: "60",
+                                btnBgColorTheme: "#07C160",
+                                btnBorderColorTheme: "#FFFFFF",
+                                btnFontType: "0",
+                                btnStyle: "1",
+                                paddingTop: "28",
+                                paddingBottom: "0",
+                                paddingRight: "24",
+                                paddingLeft: "24",
+                                cornerRadius: 4,
+                                useIcon: "0",
+                                field21_1: {
+                                    origBtnJumpUrl: "",
+                                    wxad_guide_group_id: ""
+                                },
+                                id: "widget_1638502917231_4",
+                                layoutHeight: "60",
+                                layoutWidth: "152"
+                            }]
+                        }
+                    }]
+                },
+                wxad_IsSubNode: "1"
+            }]
+    }
+}
+
+// 图文复合组件按钮类型参数
+// 单个
+// 跳转链接 104
+export const jumpLink104 = {
+    widgetTypeV2: "link",
+    widgetType: "button",
+    borderSize: "0",
+    btnBgColorTheme: "#07C160",
+    btnBorderColorTheme: "#FFFFFF",
+    btnFontType: "0",
+    btnHeight: "60",
+    btnJumpUrl: "",
+    btnStyle: "1",
+    btnTitle: "了解详情",
+    btnType: "0",
+    fontColor: "#FFFFFF",
+    fontSize: "26",
+    name: "跳转链接",
+    origBtnJumpUrl: "",
+    layoutWidth: "152",
+    layoutHeight: "60",
+    paddingBottom: "0",
+    paddingLeft: "0",
+    paddingRight: "0",
+    paddingTop: "28",
+    subType: "0",
+    mpJumpType: "1",
+    type: "21",
+    cornerRadius: "4"
+}
+// 跳转链接 103
+export const jumpLink103 = {
+    widgetTypeV2: "link",
+    widgetType: "button",
+    borderSize: "0",
+    btnBgColorTheme: "#07C160",
+    btnBorderColorTheme: "#FFFFFF",
+    btnFontType: "0",
+    btnHeight: "60",
+    btnJumpUrl: "",
+    btnStyle: "1",
+    btnTitle: "了解详情",
+    btnType: "0",
+    fontColor: "#FFFFFF",
+    fontSize: "26",
+    name: "跳转链接",
+    origBtnJumpUrl: "",
+    layoutWidth: "152",
+    layoutHeight: "60",
+    paddingBottom: "32",
+    paddingLeft: "24",
+    paddingRight: "24",
+    paddingTop: "28",
+    subType: "0",
+    mpJumpType: "1",
+    type: "21",
+    cornerRadius: "4"
+}
+
+// 关注公众号 104
+export const jumpGh104 = {
+    widgetTypeV2: "gh",
+    widgetType: "button",
+    type: "21",
+    name: "商品关注公众号",
+    btnTitle: "关注公众号",
+    subType: "17",
+    fontSize: "26",
+    fontColor: "#FFFFFF",
+    btnType: "0",
+    borderSize: "0",
+    btnHeight: "60",
+    btnBgColorTheme: "#07C160",
+    btnBorderColorTheme: "#FFFFFF",
+    btnFontType: "0",
+    btnStyle: "1",
+    paddingTop: "28",
+    paddingBottom: "0",
+    paddingRight: "0",
+    paddingLeft: "0",
+    cornerRadius: 4,
+    useIcon: "0",
+    field21_1: {
+        origBtnJumpUrl: "",
+        wxad_guide_group_id: ""
+    },
+    id: "widget_1634536343386_3",
+    layoutWidth: "152",
+    layoutHeight: "60"
+}
+// 关注公众号 103
+export const jumpGh103 = {
+    widgetTypeV2: "gh",
+    widgetType: "button",
+    type: "21",
+    name: "商品关注公众号",
+    btnTitle: "关注公众号",
+    subType: "17",
+    fontSize: "26",
+    fontColor: "#FFFFFF",
+    btnType: "0",
+    borderSize: "0",
+    btnHeight: "60",
+    btnBgColorTheme: "#07C160",
+    btnBorderColorTheme: "#FFFFFF",
+    btnFontType: "0",
+    btnStyle: "1",
+    paddingTop: "28",
+    paddingBottom: "0",
+    paddingRight: "24",
+    paddingLeft: "24",
+    cornerRadius: 4,
+    useIcon: "0",
+    field21_1: {
+        origBtnJumpUrl: "",
+        wxad_guide_group_id: ""
+    },
+    id: "widget_1638502917231_4",
+    layoutHeight: "60",
+    layoutWidth: "152"
+}
+
+// 商品添加商家微信 104
+export const jumpWxAuto104 = {
+    widgetTypeV2: "enterprise_wx_auto",
+    widgetType: "button",
+    type: "21",
+    name: "商品添加商家微信",
+    btnTitle: "联系商家",
+    subType: "15",
+    fontSize: "26",
+    fontColor: "#FFFFFF",
+    btnType: "0",
+    borderSize: "0",
+    btnHeight: "60",
+    btnBgColorTheme: "#07C160",
+    btnBorderColorTheme: "#FFFFFF",
+    btnFontType: "0",
+    btnStyle: "1",
+    paddingTop: "28",
+    paddingBottom: "0",
+    paddingRight: "0",
+    paddingLeft: "0",
+    cornerRadius: 4,
+    useIcon: "0",
+    mpJumpType: "1",
+    origBtnJumpUrl: "",//"https://wxa.wxs.qq.com/mptemplate/rtx/index.html?corpid=wp7eXwDAAA4NWRcT0uN05GKQesjmgB0g&groupid=215982&_=1646277580091",
+    btnJumpUrl: "",
+    corpid: "",//"wp7eXwDAAA4NWRcT0uN05GKQesjmgB0g",
+    groupid: null,//215982,
+    h5_config_id: "",//"6eeeabc55c3087ea251505ec8560371e",
+    wxapp_config_id: "",//"bee1788a8cbf0bbab69d3bfc5917231e",
+    qrUrl: "https://wework.qpic.cn/wwpic/87591_JQGte4L-Tc21VZW_1645432835/0",
+    needUpdateQrUrl: 0,
+    qrExtInfo: "",//"{\"qrType\":1,\"corpid\":\"wp7eXwDAAA4NWRcT0uN05GKQesjmgB0g\",\"groupid\":215982,\"useSet\":0}",
+    serviceType: "1",
+    chatGroupName: "", //"单人客服-木木",
+    id: "widget_1646277501989_6",
+    layoutWidth: "152",
+    layoutHeight: "60"
+}
+// 商品添加商家微信 103
+export const jumpWxAuto103 = {
+    widgetTypeV2: "enterprise_wx_auto",
+    widgetType: "button",
+    type: "21",
+    name: "商品添加商家微信",
+    btnTitle: "联系商家",
+    subType: "15",
+    fontSize: "26",
+    fontColor: "#FFFFFF",
+    btnType: "0",
+    borderSize: "0",
+    btnHeight: "60",
+    btnBgColorTheme: "#07C160",
+    btnBorderColorTheme: "#FFFFFF",
+    btnFontType: "0",
+    btnStyle: "1",
+    paddingTop: "28",
+    paddingBottom: "0",
+    paddingRight: "24",
+    paddingLeft: "24",
+    cornerRadius: 4,
+    useIcon: "0",
+    mpJumpType: "1",
+    origBtnJumpUrl: "",//"https://wxa.wxs.qq.com/mptemplate/rtx/index.html?corpid=wp7eXwDAAA4NWRcT0uN05GKQesjmgB0g&groupid=215982&_=1646279359946",
+    btnJumpUrl: "",
+    corpid: "",//"wp7eXwDAAA4NWRcT0uN05GKQesjmgB0g",
+    groupid: null, //215982,
+    h5_config_id: "", //"6eeeabc55c3087ea251505ec8560371e",
+    wxapp_config_id: "",//"bee1788a8cbf0bbab69d3bfc5917231e",
+    qrUrl: "https://wework.qpic.cn/wwpic/87591_JQGte4L-Tc21VZW_1645432835/0",
+    needUpdateQrUrl: 0,
+    qrExtInfo: "", //"{\"qrType\":1,\"corpid\":\"wp7eXwDAAA4NWRcT0uN05GKQesjmgB0g\",\"groupid\":215982,\"useSet\":0}",
+    serviceType: "1",
+    chatGroupName: "",//"单人客服-木木",
+    id: "widget_1646279296521_3",
+    layoutHeight: "60",
+    layoutWidth: "152"
+}
+
+// 全局跳转 跳转链接 104
+export const btModelJumpLink = {
+    widgetTypeV2: "link",
+    widgetType: "button",
+    borderSize: "0",
+    btnBgColorTheme: "clear",
+    btnBorderColorTheme: "clear",
+    btnFontType: "0",
+    btnHeight: "228",
+    btnJumpUrl: "",
+    btnStyle: "1",
+    btnTitle: "",
+    btnType: "0",
+    fontColor: "clear",
+    fontSize: "26",
+    name: "跳转链接",   // 
+    origBtnJumpUrl: "",
+    layoutWidth: "750",
+    layoutHeight: "228",
+    paddingBottom: "0",
+    paddingLeft: "0",
+    paddingRight: "0",
+    paddingTop: "0",
+    subType: "0",
+    fengyeId: "0",
+    fengyeUrl: "",
+    mpJumpType: "1",
+    type: "21",
+    cornerRadius: "4"
+}
+// 全局跳转 跳转链接 103
+export const btModelJumpLink103 = {
+    widgetTypeV2: "link",
+    widgetType: "button",
+    borderSize: "0",
+    btnBgColorTheme: "clear",
+    btnBorderColorTheme: "clear",
+    btnFontType: "0",
+    btnHeight: "542",
+    btnJumpUrl: "",
+    btnStyle: "1",
+    btnTitle: "",
+    btnType: "0",
+    fontColor: "clear",
+    fontSize: "26",
+    name: "跳转链接",
+    origBtnJumpUrl: "",
+    layoutWidth: "320",
+    layoutHeight: "542",
+    paddingBottom: "32",
+    paddingLeft: "30",
+    paddingRight: "24",
+    paddingTop: "0",
+    subType: "0",
+    fengyeId: "0",
+    fengyeUrl: "",
+    mpJumpType: "1",
+    type: "21",
+    cornerRadius: "4"
+}
+// 全局跳转 关注公众号 104
+export const btModelJumpGh = {
+    widgetTypeV2: "gh",
+    widgetType: "button",
+    type: "21",
+    name: "商品关注公众号",
+    btnTitle: "",
+    subType: "17",
+    wxad_guide_group_status: false,
+    fontSize: "26",
+    fontColor: "clear",
+    btnType: "0",
+    borderSize: "0",
+    btnHeight: "228",
+    btnBgColorTheme: "clear",
+    btnBorderColorTheme: "clear",
+    btnFontType: "0",
+    btnStyle: "1",
+    paddingTop: "0",
+    paddingBottom: "0",
+    paddingRight: "0",
+    paddingLeft: "0",
+    cornerRadius: 4,
+    useIcon: "0",
+    field21_1: {
+        origBtnJumpUrl: "",
+        wxad_guide_group_id: ""
+    },
+    id: "widget_1634288817126_3",
+    layoutWidth: "750",
+    layoutHeight: "228"
+}
+// 全局跳转 关注公众号 103
+export const btModelJumpGh103 = {
+    widgetTypeV2: "gh",
+    widgetType: "button",
+    type: "21",
+    name: "商品关注公众号",
+    btnTitle: "",
+    subType: "17",
+    wxad_guide_group_status: false,
+    fontSize: "26",
+    fontColor: "clear",
+    btnType: "0",
+    borderSize: "0",
+    btnHeight: "542",
+    btnBgColorTheme: "clear",
+    btnBorderColorTheme: "clear",
+    btnFontType: "0",
+    btnStyle: "1",
+    paddingTop: "0",
+    paddingBottom: "0",
+    paddingRight: "24",
+    paddingLeft: "24",
+    cornerRadius: 4,
+    useIcon: "0",
+    field21_1: {
+        origBtnJumpUrl: "",
+        wxad_guide_group_id: ""
+    },
+    id: "widget_1638759421426_2",
+    layoutHeight: "542",
+    layoutWidth: "320"
+}
+
+// 全局跳转 商品添加商家微信 104
+export const btModelJumpWxAuto = {
+    widgetTypeV2: "enterprise_wx_auto",
+    widgetType: "button",
+    type: "21",
+    name: "商品添加商家微信",
+    btnTitle: "",
+    subType: "15",
+    fontSize: "26",
+    fontColor: "clear",
+    btnType: "0",
+    borderSize: "0",
+    btnHeight: "228",
+    btnBgColorTheme: "clear",
+    btnBorderColorTheme: "clear",
+    btnFontType: "0",
+    btnStyle: "1",
+    paddingTop: "0",
+    paddingBottom: "0",
+    paddingRight: "0",
+    paddingLeft: "0",
+    cornerRadius: 4,
+    useIcon: "0",
+    mpJumpType: "1",
+    origBtnJumpUrl: "",//"https://wxa.wxs.qq.com/mptemplate/rtx/index.html?corpid=wp7eXwDAAA4NWRcT0uN05GKQesjmgB0g&groupid=215982&_=1646277580092",
+    btnJumpUrl: "",
+    corpid: "",//"wp7eXwDAAA4NWRcT0uN05GKQesjmgB0g",
+    groupid: null,//215982,
+    h5_config_id: "",//"6eeeabc55c3087ea251505ec8560371e",
+    wxapp_config_id: "", //"bee1788a8cbf0bbab69d3bfc5917231e",
+    qrUrl: "https://wework.qpic.cn/wwpic/87591_JQGte4L-Tc21VZW_1645432835/0",
+    needUpdateQrUrl: 0,
+    qrExtInfo: "", //"{\"qrType\":1,\"corpid\":\"wp7eXwDAAA4NWRcT0uN05GKQesjmgB0g\",\"groupid\":215982,\"useSet\":0}",
+    serviceType: "1",
+    chatGroupName: "", //"单人客服-木木",
+    id: "",  //"widget_1646277501989_6",
+    layoutWidth: "750",
+    layoutHeight: "228"
+}
+// 全局跳转 商品添加商家微信 103
+export const btModelJumpWxAuto103 = {
+    widgetTypeV2: "enterprise_wx_auto",
+    widgetType: "button",
+    type: "21",
+    name: "商品添加商家微信",
+    btnTitle: "",
+    subType: "15",
+    fontSize: "26",
+    fontColor: "clear",
+    btnType: "0",
+    borderSize: "0",
+    btnHeight: "542",
+    btnBgColorTheme: "clear",
+    btnBorderColorTheme: "clear",
+    btnFontType: "0",
+    btnStyle: "1",
+    paddingTop: "0",
+    paddingBottom: "0",
+    paddingRight: "24",
+    paddingLeft: "24",
+    cornerRadius: 4,
+    useIcon: "0",
+    mpJumpType: "1",
+    origBtnJumpUrl: "",//"https://wxa.wxs.qq.com/mptemplate/rtx/index.html?corpid=wp7eXwDAAA4NWRcT0uN05GKQesjmgB0g&groupid=215982&_=1646279359946",
+    btnJumpUrl: "",
+    corpid: "",//"wp7eXwDAAA4NWRcT0uN05GKQesjmgB0g",
+    groupid: null,//215982,
+    h5_config_id: "",//"6eeeabc55c3087ea251505ec8560371e",
+    wxapp_config_id: "", //"bee1788a8cbf0bbab69d3bfc5917231e",
+    qrUrl: "https://wework.qpic.cn/wwpic/87591_JQGte4L-Tc21VZW_1645432835/0",
+    needUpdateQrUrl: 0,
+    qrExtInfo: "", //"{\"qrType\":1,\"corpid\":\"wp7eXwDAAA4NWRcT0uN05GKQesjmgB0g\",\"groupid\":215982,\"useSet\":0}",
+    serviceType: "1",
+    chatGroupName: "", //"单人客服-木木",
+    id: "", //"widget_1646279296521_3",
+    layoutHeight: "542",
+    layoutWidth: "320"
+}
+
+// 悬浮组件 添加商家微信
+export const floatbuttonBtTypeWxAuto = {
+    widgetTypeV2: "enterprise_wx_auto",
+    widgetType: "button",
+    type: "21",
+    name: "添加商家微信",
+    btnTitle: "联系商家",
+    subType: "15",
+    fontSize: "28",
+    fontColor: "#FFFFFF",
+    btnType: "0",
+    btnBgColorTheme: "#07C160",
+    btnBorderColorTheme: "#FFFFFF",
+    btnFontType: "0",
+    btnStyle: "1",
+    cornerRadius: "4",
+    useIcon: "0",
+    mpJumpType: "1",
+    origBtnJumpUrl: "",
+    btnJumpUrl: "",
+    corpid: "",
+    groupid: null,
+    h5_config_id: "",
+    wxapp_config_id: "",
+    qrUrl: "https://wework.qpic.cn/wwpic/402461_L-7_oaL7St6-juF_1649837642/0",
+    needUpdateQrUrl: 0,
+    qrExtInfo: "",
+    serviceType: "1",
+    chatGroupName: "",
+    id: "",
+    layoutHeight: "70",
+    layoutWidth: "160",
+    borderSize: "0",
+    custorGroup: []
+}
+export const floatbuttonBtTypeGh = {
+    widgetTypeV2: "gh",
+    widgetType: "button",
+    type: "21",
+    name: "关注公众号",
+    btnTitle: "关注公众号",
+    subType: "17",
+    fontSize: "28",
+    fontColor: "#FFFFFF",
+    btnType: "0",
+    btnBgColorTheme: "#07C160",
+    btnBorderColorTheme: "#FFFFFF",
+    btnFontType: "0",
+    btnStyle: "1",
+    cornerRadius: "4",
+    useIcon: "0",
+    field21_1: {
+        origBtnJumpUrl: "",
+        wxad_guide_group_id: ""
+    },
+    id: "widget_1652153905113_9",
+    layoutHeight: "70",
+    layoutWidth: "160",
+    borderSize: "0",
+    custorGroup: []
+}
+
+// 悬浮组件
+export const floatbuttonContent = {
+    widgetTypeV2: "floatbutton",
+    widgetType: "float_button",
+    type: "134",
+    name: "悬浮组件",
+    wxad_styleType: "1",
+    onlyShowInTimelineAd: "0",
+    backgroundImg: "",
+    backgroundColor: "#F0F0F0",
+    backgroundColorAlpha: "0.96",
+    backgroundBlurEffect: "1",
+    backgroundBlurEffectColor: "#F0F0F0",
+    backgroundBlurEffectColorAlpha: "0.5",
+    titleColor: "#171717",
+    titleColorAlpha: "1",
+    descColor: "#4c4c4c",
+    descColorAlpha: "0.5",
+    iconUrl: "",
+    imageMd5: "",
+    materialId: "",
+    title: "",
+    desc: "",
+    isFullClickable: "1",
+    appearPaddingTop: "0",
+    appearPaddingBottom: "0",
+    paddingTop: 0,
+    paddingBottom: 0,
+    paddingRight: 0,
+    paddingLeft: 0,
+    id: "widget_1652076096083_2",
+    componentItem: {
+        widgetTypeV2: "gh",
+        widgetType: "button",
+        type: "21",
+        name: "关注公众号",
+        btnTitle: "关注公众号",
+        subType: "17",
+        fontSize: "28",
+        fontColor: "#FFFFFF",
+        btnType: "0",
+        btnBgColorTheme: "#07C160",
+        btnBorderColorTheme: "#FFFFFF",
+        btnFontType: "0",
+        btnStyle: "1",
+        cornerRadius: "4",
+        useIcon: "0",
+        field21_1: {
+            origBtnJumpUrl: "",
+            wxad_guide_group_id: ""
+        },
+        id: "widget_1652153905113_9",
+        layoutHeight: "70",
+        layoutWidth: "160",
+        borderSize: "0",
+        custorGroup: []
+    }
+}

+ 61 - 0
src/pages/launchSystemNew/components/addLandingPage/dropCon.tsx

@@ -0,0 +1,61 @@
+import { DropProps } from "ahooks/lib/useDrop"
+import React, { useEffect, useRef, useState } from "react"
+
+interface DropConProps {
+    onChange?: (value: any) => void,
+    isHoveringCon?: boolean,   // 是否拖进接收框
+    draggingCon?: string | null,     // 是否已经开始拖动元素
+    drop: DropProps,   // 设置元素为可接收拖动的元素区
+    conOffset: { top: number, left: number, width: number },
+    drawXY: { dragX: number, dragY: number },  // 鼠标位置
+    index: number
+}
+function DropCon(props: DropConProps) {
+    const { drop, conOffset, drawXY, onChange, isHoveringCon = false, draggingCon = null } = props
+
+    const [isPropOpen, setIsPropOpen] = useState<boolean>(false)
+    const ref: any = useRef(null)
+    console.log("---22222---", isHoveringCon, draggingCon);
+    useEffect(() => {
+        console.log("---11111---", isHoveringCon, draggingCon);
+
+        if (isHoveringCon) {
+            setIsPropOpen(() => true)
+        } else {
+            setIsPropOpen(() => false)
+            if (draggingCon) {
+                if (drawXY.dragX > conOffset.left && drawXY.dragX < conOffset.left + conOffset.width) { // 在本接收器左右两端
+                    if (drawXY.dragY < conOffset.top + ref.current?.offsetTop) { // 在本接收器上方小于50
+                        let nBad = (conOffset.top + ref.current?.offsetTop) - drawXY.dragY
+                        if (nBad < 60) {
+                            setIsPropOpen(() => true)
+                        } else {
+                            setIsPropOpen(() => false)
+                        }
+                    } else if (drawXY.dragY > (conOffset.top + ref.current?.offsetTop + ref.current?.offsetHeight)) {
+                        let nBad = drawXY.dragY - (conOffset.top + ref.current?.offsetTop + ref.current?.offsetHeight)
+                        if (nBad < 80) {
+                            setIsPropOpen(() => true)
+                        } else {
+                            setIsPropOpen(() => false)
+                        }
+                    }
+                }
+            } else {
+                setIsPropOpen(() => false)
+            }
+        }
+    }, [drawXY, conOffset, draggingCon, ref.current?.offsetTop, ref.current?.offsetHeight, isHoveringCon])
+    useEffect(() => {
+
+    }, [ref.current?.offsetTop])
+    return <div {...drop} className={`comptCon ${isHoveringCon && 'hovering'} ${isPropOpen && 'draggingCon1'}`} ref={ref}>
+        {(isHoveringCon || isPropOpen) && '请拖至此处'}
+    </div>
+}
+
+export default React.memo(DropCon, (prevProps, nextProps) => {
+    let _prev = JSON.stringify(prevProps)
+    let _next = JSON.stringify(nextProps)
+    return _prev === _next
+})

+ 159 - 0
src/pages/launchSystemNew/components/addLandingPage/index.less

@@ -0,0 +1,159 @@
+.drawer{
+    .boxCont{
+        width: 100%;
+        height: 100%;
+        background-color: #f3f3f3;
+        .row{
+            height: 100%;
+
+            >div{
+                overflow-y: scroll;
+
+                >.title{
+                    font-size: 13px;
+                    line-height: 20px;
+                    color: #1f1f1f;
+                    font-weight: 700;
+                    margin-bottom: 15px;
+                }
+                >.assembly{
+                    width: 100%;
+                    display: flex;
+                    flex-wrap: wrap;
+                    margin-bottom: 15px;
+                    >div{
+                        width: 33.33%;
+                        border: 1px solid #f3f3f3;
+                        cursor: pointer;
+                        font-size: 12px;
+                        color: #525252;
+                        min-width: 78px;
+                        display: flex;
+                        flex-direction: column;
+                        justify-content: center;
+                        align-items: center;
+                        padding: 20px 0;
+                        &:not(:nth-child(1)){
+                            border-left: none;
+                        }
+                    }
+                    >.disabled{
+                        cursor: default;
+                        >svg, span{
+                            opacity: .3;
+                        }
+                    }
+                }
+            }
+
+            .right{
+                background-color: #fff;
+                padding: 10px;
+                box-sizing: border-box;
+            }
+
+            .center{
+                max-height: 100%;
+                padding: 15px 0 200px;
+                box-sizing: border-box;
+                >.page{
+                    margin: 0 auto;
+                    width: 375px;
+                    min-height: 667px;
+                    background-color: rgb(255, 255, 255);
+                    position: relative;
+                    padding-top: 1px;
+                    box-shadow: 0 3px 5px 0 rgba(0, 0, 0, 3%), 0 6px 15px 0 rgba(0, 0, 0, 4%), 0 0 0 1px hsl(0deg, 0%, 87%, 60%);
+                }
+                    
+            }
+
+            .left {
+                max-height: 100%;
+                width: 100%;
+                overflow: hidden;
+            }
+        }
+    }
+    .ant-drawer-body{
+        padding: 0 !important;
+    }
+
+    .drawerTitle{
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        button.next {
+            margin-right: 50px;
+        }
+    }
+}
+
+
+.sidebar{
+    position: absolute;
+    top: 0;
+    left: -64px;
+    width: 32px;
+    z-index: 4;
+    transform-origin: top right;
+    text-align: center;
+    font-size: 12px;
+    color: #6b6b6b;
+    z-index: 1008;
+}
+.selectChannels{
+    border: 1px solid #e6e6e6;
+    width: 100%;
+    max-height: 200px;
+    border-radius: 4px;
+    padding: 2px 10px;
+    overflow-y: scroll;
+    h3{
+        font-size: 14px;
+        margin: 5px 0;
+    }
+    p {
+        margin-bottom: 0;
+        margin-left: 10px;
+        font-size: 12px;
+    }
+}
+
+
+.inputAcc {
+    box-shadow: 0 0 0 1px rgba(0,0,0,.08) inset;
+    width: 220px;
+    height: 30px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    border-radius: 4px;
+
+    .acc {
+        flex: 1;
+        height: 100%;
+        cursor: pointer;
+        display: flex;
+        align-items: center;
+        color: #24262e;
+        font-size: 13px;
+        padding-left: 10px;
+        box-sizing: border-box;
+
+        &>span {
+            margin-left: 10px;
+            color: #1890EF;
+        } 
+    }
+
+    
+    .edit {
+        color: #6b6b6b;
+    }
+}
+.cc {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+}

+ 2269 - 0
src/pages/launchSystemNew/components/addLandingPage/index.tsx

@@ -0,0 +1,2269 @@
+import { Button, Checkbox, Col, Drawer, Form, Input, InputNumber, message, Modal, Radio, RadioChangeEvent, Row, Select, Slider, Space, Spin, Switch, Tooltip } from 'antd'
+import React, { useCallback, useEffect, useMemo, useState } from "react"
+import modal from "antd/lib/modal"
+import style from './index.less'
+import './index1.less'
+import { ReactComponent as Topimg } from '@/assets/topimg.svg'
+import { ReactComponent as Topslider } from '@/assets/topslider.svg'
+import { ReactComponent as Topvideo } from '@/assets/topvideo.svg'
+import { ReactComponent as Img } from '@/assets/img.svg'
+import { ReactComponent as MyText } from '@/assets/text.svg'
+import { ReactComponent as TopNullBack } from '@/assets/topNullBack.svg'
+import { ReactComponent as FollowAcc } from '@/assets/followAcc.svg'
+import { ReactComponent as EditSvg } from '@/assets/edit.svg'
+import { ReactComponent as SliderImgSvg } from '@/assets/sliderImgSvg.svg'
+import { ReactComponent as JumpLink } from '@/assets/jumpLink.svg'
+import { ReactComponent as ImgText } from '@/assets/imgText.svg'
+import { ReactComponent as FloatbuttonSvg } from '@/assets/floatbuttonSvg.svg'
+import { ReactComponent as WxAutoSvg } from '@/assets/wxAutoSvg.svg'
+
+import { AlignCenterOutlined, AlignLeftOutlined, AlignRightOutlined, DeleteOutlined, PlusOutlined, QuestionCircleOutlined, RetweetOutlined, SwapLeftOutlined, SwapRightOutlined } from '@ant-design/icons'
+import { useDrop, useDrag } from 'ahooks'
+import arrayMove from "array-move";
+import ColorPicker from '@/components/ColorPicker1'
+import TextArea from 'antd/lib/input/TextArea'
+import moment from 'moment'
+import { TopImg, TopVideo, Text, Img as ImgProps, GhButton, TopSlider, LinkButton, Shelfnew, WxAutoButton, Floatbutton } from '../../req'
+import { imgContent, txtContent, ghContent, topvideoNewContent, linkContent, topsliderContent, topimgContent, topvideoContent, shelfnewContent, shelfnewContent2, btModelJumpGh, btModelJumpLink, btModelJumpLink103, btModelJumpGh103, jumpLink104, jumpGh104, jumpLink103, jumpGh103, wxAutoContent, jumpWxAuto104, jumpWxAuto103, btModelJumpWxAuto, btModelJumpWxAuto103, floatbuttonContent, floatbuttonBtTypeWxAuto, floatbuttonBtTypeGh } from './content'
+import { landingPageReducer, Content, Props } from './landingPageReducer'
+
+import {
+    SortableList,
+    SortableItemText,
+    SortableItemImg,
+    SortableItemFollowAcc,
+    SortableItemImgText,
+    SortableUlList,
+    SortableItemNoLi,
+    SortableItemLi,
+    SortableItemJumpLink,
+    SortableItemWxAuto,
+    SortableItemFloatbutton
+} from './sortable'
+import { useModel } from 'umi'
+import SelectCloud from '../selectCloud'
+import { getTypeKey, txtLength } from '@/utils/utils'
+
+const { Option } = Select;
+
+function AddLandingPage(props: Props) {
+    let { visible, hideModal, ajax, id } = props
+
+    const { state, stateGlobal, dispatch, dispatchGlobal } = landingPageReducer()
+    const { content, pageBackColor } = state
+    const { componentItem } = stateGlobal
+    const { currentUser }: any = useModel('@@initialState', model => ({ currentUser: model.initialState?.currentUser }))
+    const { init, add, get, state: { parentId, belongUser } } = useModel('useLaunchAdq.useBdMediaPup')
+    /** 变量开始 */
+    const [selectImgVisible, setSelectImgVisible] = useState<boolean>(false)   // 选择图片弹窗
+    const [lastVisible, setLastVisible] = useState<boolean>(false)   // 最后保存设置
+    const [shareTittle, setShareTittle] = useState<string>('')  // 分享标题
+    const [shareDesc, setShareDesc] = useState<string>('')  // 分享描述
+    const [pageName, setPageName] = useState<string>('')  // 落地页名称
+    const [sort, setSort] = useState<number>(0) // 排序
+    const [imgSize, setImgSize] = useState<{
+        aspectRatio?: string, //宽高比
+        minWidth?: string,   //最小宽度
+        maxWidth?: string,   //最大宽度
+        minHeight?: string,  //最小高度
+        maxHeight?: string,  //最大高度
+        minMediaSize?: number, // 最小媒体大小
+        maxMediaSize?: number, // 最大媒体大小
+        minVideoLength?: number, // 最小视频时长
+        maxVideoLength?: number, // 最大视频时长
+        minVideoBitRate?: number, // 最小视频比特率
+        maxVideoBitRate?: number, // 最大视频比特率
+        mediaType?: 'IMG' | 'VIDEO' | 'PAGE',  // 内容类型 1 图片 2 视频
+    }>({})   // 要选择的素材信息
+    const [isFootlock, setIsFootlock] = useState<boolean>(false)  // 是否横板视频
+    const [scType, setCcType] = useState<1 | 2 | 3 | 4 | 5>(1) // 视频 单图片 多图片处理
+    const [sliderImgContent, setSliderImgContent] = useState<{ url: string, width?: number, height?: number }[]>([])  // 保存回填数据
+    const [imgTextButtonShow, setImgTextButtonShow] = useState<boolean>(false)
+    const [goodsCount, setGoodsCount] = useState<number>(0)
+    /** 变量结束 */
+
+    // console.log('content---->', content)
+
+    // 回填
+    useEffect(() => {
+        if (id) {
+            get.run({ sysMediaId: id, mediaType: 'PAGE' }).then(res => {
+                if (res) {
+                    const { pageSpecsList, shareContentSpec } = res
+                    dispatch({ type: 'setPageBackColor', params: { pageBackColor: pageSpecsList[0]?.bgColor } })
+                    let pageElementsSpecList = pageSpecsList[0]?.pageElementsSpecList
+                    dispatch({
+                        type: 'setCon', params: {
+                            content: pageElementsSpecList?.map((item: any) => {
+                                let typeKey = getTypeKey(item?.elementType)
+                                if (typeKey) {
+                                    let data = item[typeKey] || {}
+                                    return {
+                                        elementType: item?.elementType,
+                                        ...data
+                                    }
+                                }
+                                return item
+                            })
+                        }
+                    })
+                    setShareDesc(() => shareContentSpec?.shareTitle || '')
+                    setShareTittle(() => shareContentSpec?.shareDescription || '')
+                    // global = global?.map((item: any) => ({ ...item, comptActive: false }))
+                    // dispatchGlobal({ type: "setConItem", params: { componentItem: global || [] } })
+                }
+            })
+        } else {
+            // dispatch({ type: 'init', params: { elementType: 'empty' } })
+        }
+    }, [id])
+
+    const config = {
+        title: '警告!',
+        cancelText: '取消',
+        okText: '确定',
+        onOk: () => { hideModal() },
+        content: (
+            <>
+                <div>不会保存您所做的更改,确定关闭?</div>
+            </>
+        ),
+    };
+
+    /** 获取选中内容对应图片视频尺寸大小 */
+    useEffect(() => {
+        let selectData = content?.find((item: Content) => item.comptActive)
+        if (!selectData) {
+            selectData = componentItem?.find((item: { comptActive: boolean }) => item.comptActive)
+        }
+        if (selectData?.elementType === "TOP_IMAGE") {
+            setIsFootlock(() => false)
+            if (selectData?.adLocation === 'sns') { // 朋友圈信息流
+                if (selectData?.outerStyle === 0) { // 常规广告
+                    init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: 800, height: 800 }]] })
+                } else { // 卡片广告
+                    init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: 800, height: 450 }]] })
+                }
+            } else { // 公众号及其他
+                init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: 800, height: 800 }], [{ relation: '=', width: 800, height: 450 }], [{ relation: '=', width: 800, height: 640 }], [{ relation: '=', width: 640, height: 800 }]] })
+            }
+        } else if (selectData?.elementType === 'IMAGE') { // 内容图片
+            setIsFootlock(() => false)
+            init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: 750, height: null }, { relation: '<=', width: null, height: 1536 }]] })
+        } else if (selectData?.elementType === 'TOP_SLIDER') { // 轮播图
+            init({ mediaType: 'IMG', num: selectData?.imageUrlList?.length || 3, cloudSize: [[{ relation: '=', width: 800, height: 800 }]] })
+            setIsFootlock(() => false)
+        } else if (selectData?.elementType === 'TOP_VIDEO') { // 视频
+            if (selectData?.adLocation === 'sns') { // 朋友圈信息流
+                if (selectData?.outerStyle === 0) { // 常规广告
+                    init({ mediaType: 'VIDEO', cloudSize: [[{ relation: '=', width: 640, height: 480 }], [{ relation: '=', width: 640, height: 360 }], [{ relation: '=', width: 750, height: 1334 }], [{ relation: '=', width: 720, height: 1280 }] ] })
+                } else { // 卡片广告
+                    init({ mediaType: 'VIDEO', cloudSize: [[{ relation: '=', width: 750, height: null }, { relation: '<=', width: null, height: 1536 }]] })
+                }
+            } else { // 公众号及其它
+                init({ mediaType: 'VIDEO', cloudSize: [[{ relation: '=', width: 750, height: null }, { relation: '<=', width: null, height: 1536 }]] })
+                setIsFootlock(() => false)
+            }
+        } else if (selectData?.elementType === 'shelfnew') {  // 图文复合组件 1个
+            if (selectData?.type === '104') {
+                setIsFootlock(() => false)
+                setImgSize(() => ({ minWidth: '360', maxWidth: '360', minHeight: '360', maxHeight: '360', minMediaSize: 0, maxMediaSize: 307200, mediaType: 'IMG' }))
+            } else if (selectData?.type === '103') {
+                setIsFootlock(() => false)
+                setImgSize(() => ({ minWidth: '480', maxWidth: '480', minHeight: '480', maxHeight: '480', minMediaSize: 0, maxMediaSize: 307200, mediaType: 'IMG' }))
+            }
+        } else if (selectData?.elementType === 'FLOAT_BUTTON') {
+            setIsFootlock(() => false)
+            init({ mediaType: 'VIDEO', cloudSize: { minWidth: '96', maxWidth: '96', minHeight: '96', maxHeight: '96', minMediaSize: 0, maxMediaSize: 307200 } })
+            setImgSize(() => ({ minWidth: '96', maxWidth: '96', minHeight: '96', maxHeight: '96', minMediaSize: 0, maxMediaSize: 307200, mediaType: 'IMG' }))
+        }
+    }, [content])
+
+    const [dragging, setDragging] = useState<string | null>(null);
+    const getDragProps = useDrag({
+        onDragStart: (data) => {
+            setDragging(data);
+        },
+        onDragEnd: () => {
+            setDragging(null);
+        },
+    });
+    // 头部内容拖到接收区
+    const [dropProps] = useDrop({
+        onDom: (con: string, e) => { // 头部
+            let newCon = content?.map((item: TopImg | TopVideo | TopSlider) => {
+                return { ...item, comptActive: false }
+            })
+            let topCon = newCon[0]
+            topCon.type = con
+            topCon.comptActive = true
+            let conContent: TopImg | TopVideo | TopSlider
+            if (con === 'TOP_SLIDER') {
+                conContent = { ...topsliderContent, comptActive: true } as TopSlider
+            } else if (con === 'TOP_IMAGE') {
+                conContent = { ...topimgContent, comptActive: true } as TopImg
+            } else if (con === 'TOP_VIDEO') {
+                conContent = { ...topvideoNewContent, comptActive: true } as TopVideo
+            } else {
+                return
+            }
+            topCon = { ...conContent }
+            newCon[0] = topCon
+            dispatch({ type: 'setCon', params: { content: newCon } })
+        },
+    });
+    const [draggingCon, setDraggingCon] = useState<string | null>(null);
+    const getDragPropsCon = useDrag({
+        onDragStart: (data) => {
+            setDraggingCon(data);
+        },
+        onDragEnd: () => {
+            setDraggingCon(null);
+        },
+    });
+    // 内容拖到接收区
+    const [dropConProps, { isHovering: isHoveringCon }] = useDrop({
+        onDom: (con: string, e) => { // 内容
+            let newCon: Content[] = content?.map((item: Text | ImgProps | GhButton) => { // | LinkButton | WxAutoButton
+                return { ...item, comptActive: false }
+            })
+            let newConItem = componentItem?.map((item: Floatbutton) => {
+                return { ...item, comptActive: false }
+            })
+            if (con === 'TEXT') {
+                newCon.push({ ...txtContent, comptActive: true } as any)
+            } else if (con === 'IMAGE') {
+                newCon.push({ ...imgContent, comptActive: true } as any)
+            } else if (con === 'GH') {
+                newCon.push({ ...ghContent, comptActive: true } as any)
+                // } else if (con === 'JumpLink') {
+                //     newCon.push({ ...linkContent, comptActive: true } as any)
+                // } else if (con === 'shelfnew') {
+                //     newCon.push({ ...shelfnewContent, comptActive: true } as any)
+            } else if (con === 'ENTERPRISE_WX') {
+                newCon.push({ ...wxAutoContent, comptActive: true } as any)
+            } else if (con === 'FLOAT_BUTTON') {
+                newConItem.push({ ...floatbuttonContent, comptActive: true })
+                setDraggingCon(null);
+            } else {
+                return
+            }
+
+            dispatch({ type: 'setCon', params: { content: newCon } })
+            dispatchGlobal({ type: "setConItem", params: { componentItem: newConItem } })
+        },
+    });
+
+    /** 选中设置 */
+    const installActive = useCallback((e, index: number) => {
+        e.stopPropagation(); e.preventDefault();
+        let newCon = content?.map((item: any) => {
+            return { ...item, comptActive: false }
+        })
+        let newConItem = componentItem?.map((item: { widgetTypeV2: string }) => {
+            return { ...item, comptActive: false }
+        })
+        setImgTextButtonShow(false)
+        if (index === 99999) {
+            newConItem = newConItem?.map((item: { widgetTypeV2: string }) => {
+                if (item.widgetTypeV2 === 'floatbutton') {
+                    return { ...item, comptActive: true }
+                }
+                return item
+            })
+        } else {
+            newCon[index].comptActive = true
+        }
+        dispatch({ type: 'setCon', params: { content: newCon } })
+        dispatchGlobal({ type: "setConItem", params: { componentItem: newConItem } })
+    }, [content, imgTextButtonShow, componentItem])
+    /** 清除选中 */
+    const installActiveNull = useCallback((e) => {
+        e.stopPropagation(); e.preventDefault();
+        let newCon = content?.map((item: any) => {
+            return { ...item, comptActive: false }
+        })
+        setImgTextButtonShow(false)
+        dispatch({ type: 'setCon', params: { content: newCon } })
+        let newConItem = componentItem?.map((item: { widgetTypeV2: string }) => {
+            return { ...item, comptActive: false }
+        })
+        dispatchGlobal({ type: "setConItem", params: { componentItem: newConItem } })
+    }, [content])
+    /** 内容功能按钮区 */
+    const handleBtn = useCallback((type: string, index: number) => {
+        let newContent = JSON.parse(JSON.stringify(content))
+        switch (type) {
+            case 'lower': // 下移动
+                dispatch({ type: 'setCon', params: { content: arrayMove(content, index, index + 1) } })
+                break;
+            case 'upper': // 上移动
+                dispatch({ type: 'setCon', params: { content: arrayMove(content, index, index - 1) } })
+                break;
+            case 'IMAGE':   // 图片
+                newContent.splice(index, 0, { ...imgContent });
+                dispatch({ type: 'setCon', params: { content: newContent } })
+                break;
+            case 'TEXT': // 文本
+                newContent.splice(index, 0, { ...txtContent });
+                dispatch({ type: 'setCon', params: { content: newContent } })
+                break;
+            case 'GH': // 关注公众号按钮
+                newContent.splice(index, 0, { ...ghContent });
+                dispatch({ type: 'setCon', params: { content: newContent } })
+                break;
+            // case 'link': // 跳转链接按钮
+            //     newContent.splice(index, 0, { ...linkContent });
+            //     dispatch({ type: 'setCon', params: { content: newContent } })
+            //     break;
+            // case 'shelfnew': // 图文复合组件
+            //     newContent.splice(index, 0, { ...shelfnewContent });
+            //     dispatch({ type: 'setCon', params: { content: newContent } })
+            //     break;
+            case 'ENTERPRISE_WX': // 图文复合组件
+                newContent.splice(index, 0, { ...wxAutoContent });
+                dispatch({ type: 'setCon', params: { content: newContent } })
+                break;
+        }
+    }, [content])
+    /** 头部删除 */
+    const topDelType = useCallback(() => {
+        setDragging(null);
+        content[0] = {
+            elementType: 'empty',
+            comptActive: false
+        }
+        dispatch({ type: 'setCon', params: { content } })
+    }, [content, dragging])
+    /** 内容删除 */
+    const delType = useCallback((e, index: number) => {
+        e.stopPropagation(); e.preventDefault();
+        setImgTextButtonShow(false)
+        if (index === 99999) { // 删除悬浮组件
+            let newContentItem = JSON.parse(JSON.stringify(componentItem))
+            newContentItem = newContentItem?.filter((item: { widgetTypeV2: string }) => item.widgetTypeV2 !== 'floatbutton')
+            dispatchGlobal({ type: 'setConItem', params: { componentItem: JSON.parse(JSON.stringify(newContentItem)) } })
+        } else {
+            let newContent = JSON.parse(JSON.stringify(content))
+            newContent?.splice(index, 1)
+            dispatch({ type: 'setCon', params: { content: JSON.parse(JSON.stringify(newContent)) } })
+        }
+    }, [content, componentItem, imgTextButtonShow])
+
+    const onSortEnd = ({ oldIndex, newIndex }: { oldIndex: number, newIndex: number }) => {
+        dispatch({ type: 'setCon', params: { content: arrayMove(content, oldIndex, newIndex) } })
+    }
+    // 基础内容
+    const comptCon = () => {
+        if (content?.length === 0) {
+            return null
+        } else {
+            return <SortableList axis='y' onSortEnd={onSortEnd} useDragHandle isFloatButton={componentItem?.some((item: { elementType: string }) => item.elementType === 'FLOAT_BUTTON')}>
+                {content.map((value: Text | ImgProps | GhButton, index: number) => {
+                    if (value?.elementType === 'IMAGE') {
+                        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} />
+                    } else if (value?.elementType === 'TEXT') {
+                        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} />
+                    } else if (value?.elementType === 'GH') {
+                        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} />
+                    } else if (value?.elementType === 'ENTERPRISE_WX') {
+                        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} />
+                        // } else if (value?.elementType === 'link') {
+                        //     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} />
+                        // } else if (value?.elementType === 'shelfnew') {
+                        //     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} />
+                    } else {
+                        return null
+                    }
+                })}
+                <div className={`comptCon ${isHoveringCon && 'hovering'} ${draggingCon && 'draggingCon'}`} {...dropConProps}>{(isHoveringCon || draggingCon) && '请拖至此处'}</div>
+                {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) }} />}
+            </SortableList>
+        }
+    }
+    /** 设置start */
+    const setCon = useCallback((key: string, value: any, isTopImg?: boolean) => {
+        let newContent = JSON.parse(JSON.stringify(content))
+        let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
+        let oldContent = newContent
+        if (selectIndex !== -1) {
+            let selectCon = oldContent[selectIndex]
+            selectCon[key] = value
+            if (selectCon?.elementType === 'TOP_VIDEO' && (key === 'adLocation' || key === 'outerStyle')) {
+                selectCon['videoUrl'] = ''
+            }
+            let newSelectCon = { ...selectCon }
+            oldContent[selectIndex] = newSelectCon
+            dispatch({ type: 'setCon', params: { content: [...oldContent] } })
+        }
+    }, [content, goodsCount])
+    /** 设置end */
+    // 顶部组件
+    const topCon = useMemo(() => {
+        let { imageUrl, elementType, activeIndex, imageUrlList, videoUrl } = content[0]
+        return <>
+            {
+                elementType === 'TOP_IMAGE' ? <>
+                    <div className={`compt componentType41 ${content[0]?.comptActive && 'comptActive'}`} onClick={(e) => { installActive(e, 0) }}>
+                        <div className={'componentWrap'}>
+                            <div className={'componentContent'}>
+                                {
+                                    imageUrl ? <img src={imageUrl} style={{ display: 'block', width: '100%', margin: 0 }} /> : <div className={'default'} style={{ width: 375, height: 300, margin: 0 }}>
+                                        <div className={'defaultIcon'} style={{ marginTop: 80 }}>
+                                            <Topimg />
+                                        </div>
+                                    </div>
+                                }
+                            </div>
+                        </div>
+
+                        {
+                            !imageUrl && <div className={'comptUpload'} style={{ margin: 0 }}>
+                                <button style={{ marginTop: 150 }} className={'comptEditButton'} onClick={() => { clickUpdateImg(1) }}>上传图片</button>
+                            </div>
+                        }
+                        <section className={'comptEditBtns'}>
+                            <div className={'comptEditBtnsInner'}>
+                                {imageUrl && <button onClick={() => { editSelectImg(imageUrl) }}><EditSvg /></button>}
+                                <button onClick={topDelType}><DeleteOutlined /></button>
+                            </div>
+                        </section>
+                    </div>
+                </> :
+                    elementType === 'TOP_SLIDER' ? <>
+                        <div className={`compt componentType101 ${content[0]?.comptActive && 'comptActive'}`} onClick={(e) => { installActive(e, 0) }}>
+                            <div className={'componentWrap'}>
+                                <div className={'componentContent'}>
+                                    {imageUrlList?.length > 0 ? <div style={{ position: 'relative', width: 375, height: 375 }}>
+                                        {imageUrlList?.map((imgUrl: any, index: number) => {
+                                            if (imgUrl) {
+                                                return <img src={imgUrl} key={index} style={{ maxWidth: '100%', position: 'absolute', display: 'block', zIndex: activeIndex === index ? 1 : 0 }} />
+                                            } else {
+                                                return <div className="default" key={index} style={{ width: 375, zIndex: activeIndex === index ? 1 : 0, height: '100%', position: 'absolute', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
+                                                    <div className="defaultIcon">
+                                                        <SliderImgSvg />
+                                                    </div>
+                                                </div>
+                                            }
+                                        })}
+                                        <div className={'sliderD'}>
+                                            {imageUrlList.map((item: any, index: number) => <i key={index} style={activeIndex === index ? { backgroundColor: 'rgba(0, 0, 0, .4)' } : {}}></i>)}
+                                        </div>
+                                    </div> : <div style={{ position: 'relative', height: 222, width: 375 }} className={'sliderCon'}>
+                                        <div className={'default'}>
+                                            <div className={'defaultIcon'}>
+                                                <Topslider />
+                                            </div>
+                                        </div>
+                                        <div className={'sliderD'}>
+                                            {imageUrlList.fill('').map((item: any, index: number) => <i key={index} style={activeIndex === index ? { backgroundColor: 'rgba(0, 0, 0, .4)' } : {}}></i>)}
+                                        </div>
+                                    </div>}
+                                </div>
+                            </div>
+                            <section className={'comptEditBtns'}>
+                                <div className={'comptEditBtnsInner'}>
+                                    <button onClick={topDelType}><DeleteOutlined /></button>
+                                </div>
+                            </section>
+                        </div>
+                    </> :
+                        elementType === 'TOP_VIDEO' ? <>
+                            <div className={`compt componentType61 ${content[0]?.comptActive && 'comptActive'}`} onClick={(e) => { installActive(e, 0) }}>
+                                <div className={'componentWrap'}>
+                                    <div className={'componentContent'}>
+                                        {
+                                            videoUrl ? <div className="videoPlay">
+                                                <video src={videoUrl} style={{ display: 'block', width: '100%', margin: 0 }} />
+                                                <span></span>
+                                            </div>
+                                                : <div className={'default'} style={{ width: 375, height: 300, margin: 0 }}>
+                                                    <div className={'defaultIcon'} style={{ marginTop: 80 }}>
+                                                        <Topvideo />
+                                                    </div>
+                                                </div>
+                                        }
+
+                                    </div>
+                                </div>
+                                {!videoUrl && <div className={'comptUpload'} style={{ margin: 0 }}>
+                                    <button style={{ marginTop: 150 }} className={'comptEditButton'} onClick={() => { clickUpdateVideo() }}>上传视频</button>
+                                </div>}
+
+                                <section className={'comptEditBtns'}>
+                                    <div className={'comptEditBtnsInner'}>
+                                        {videoUrl && <button onClick={() => { clickUpdateVideo() }}><EditSvg /></button>}
+                                        <button onClick={topDelType}><DeleteOutlined /></button>
+                                    </div>
+                                </section>
+                            </div>
+                        </> :
+                            <div className={`compt topComptArea ${dragging ? 'dragging' : ''}`} {...dropProps}>
+                                <TopNullBack />
+
+                                {dragging ? <div className="topAreaTitle" style={{ marginTop: 30 }}>
+                                    拖至此处
+                                </div> : <>
+                                    <p className={'topAreaTitle'}>顶部组件区</p>
+                                    <div className={'desc'}>在左上方,选择顶部组件添加到此处</div>
+                                </>}
+                            </div>
+            }
+        </>
+    }, [content, state, dragging])
+
+    /** 图标开启与关闭 */
+    const iconHandle = (e: boolean) => {
+        let newContent = JSON.parse(JSON.stringify(content))
+        let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
+        let oldContent = newContent
+        if (selectIndex !== -1) {
+            let selectCon = oldContent[selectIndex]
+            selectCon['useIcon'] = e ? 1 : 0
+            oldContent[selectIndex] = selectCon
+            dispatch({ type: 'setCon', params: { content: [...oldContent] } })
+        }
+    }
+    /** 视频切换广告位 */
+    const changeAdLocation = (value: 'sns' | 'gh') => {
+        let newContent = JSON.parse(JSON.stringify(content))
+        let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
+        let oldContent = newContent
+        if (selectIndex !== -1) {
+            let selectCon = oldContent[selectIndex]
+            selectCon['adLocation'] = value
+            if (value === 'gh') {
+                selectCon['styleType'] = 1
+            } else {
+                selectCon['styleType'] = 0
+            }
+            selectCon['videoUrl'] = ''
+            oldContent[selectIndex] = selectCon
+            dispatch({ type: 'setCon', params: { content: [...oldContent] } })
+        }
+    }
+
+    /** 视频切换外层样式 */
+    const changeOuterLayout = (value: string) => {
+        let newContent = JSON.parse(JSON.stringify(content))
+        let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
+        let oldContent = newContent
+        if (selectIndex !== -1) {
+            let selectCon = oldContent[selectIndex]
+            selectCon['viewType'] = value
+            let newSelectCon: any
+            if (value === '0') {
+                let { outerUseTopMaterial, streamDisplayWidth, streamDisplayHeight, streamVideoThumb, streamVideoUrl, streamDisplayType, streamThumbMd5, streamVideoMd5, displayType, ...clearSelectCon } = selectCon
+                newSelectCon = {
+                    ...clearSelectCon
+                }
+            } else {
+                newSelectCon = {
+                    ...selectCon,
+                    outerUseTopMaterial: '0',
+                    styleType: '0'
+                }
+            }
+            oldContent[selectIndex] = newSelectCon
+            dispatch({ type: 'setCon', params: { content: [...oldContent] } })
+        }
+    }
+
+    /** 轮播图选择点击切换图片 多张图片 */
+    const sliderSelect = (index: number, count: number) => {
+        let newContent = JSON.parse(JSON.stringify(content))
+        let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
+        if (selectIndex !== -1) {
+            let selectCon = newContent[selectIndex]
+            let imgList: any[] = []
+            selectCon?.imageUrlList?.forEach((item: string) => {
+                if (item) {
+                    imgList.push({ url: item })
+                }
+            })
+            setSliderImgContent(imgList)
+        }
+        setCon('activeIndex', index)
+        setCcType(3)
+        setSelectImgVisible(true)
+    }
+    /** 轮播图图片数量设置 */
+    const sliderImgNum = useCallback((num: string) => {
+        let newContent = JSON.parse(JSON.stringify(content))
+        let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
+        let oldContent = newContent
+        if (selectIndex !== -1) {
+            let selectCon = oldContent[selectIndex]
+            let { imageUrlList } = selectCon
+            if (imageUrlList?.length > Number(num)) { // 减少
+                selectCon['imageUrlList'] = [...imageUrlList?.splice(0, Number(num))]
+            } else {  // 增加
+                let newGroup = Array(Number(num) - imageUrlList?.length).fill('').map(() => "")
+                imageUrlList = [...imageUrlList, ...newGroup]
+                selectCon['imageUrlList'] = imageUrlList
+            }
+            oldContent[selectIndex] = { ...selectCon }
+            dispatch({ type: 'setCon', params: { content: [...oldContent] } })
+        }
+    }, [content])
+
+    /** 轮播图位置拖动切换顺序 */
+    const onSortEndSlider = useCallback(({ oldIndex, newIndex }: { oldIndex: number, newIndex: number }) => {
+        let newContent = JSON.parse(JSON.stringify(content))
+        let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
+        let oldContent = newContent
+        if (selectIndex !== -1) {
+            let selectCon = oldContent[selectIndex]
+            let { imageUrlList } = selectCon
+            imageUrlList = arrayMove(imageUrlList, oldIndex, newIndex)
+            selectCon['imageUrlList'] = imageUrlList
+            oldContent[selectIndex] = { ...selectCon }
+            dispatch({ type: 'setCon', params: { content: [...oldContent] } })
+        }
+    }, [content])
+
+    /** 图文单个设置 */
+    const onShelfnewTxtCon = useCallback((value: string, count: number, parameter: string) => {
+        let newContent = JSON.parse(JSON.stringify(content))
+        let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
+        let oldContent = newContent
+        if (selectIndex !== -1) {
+            let selectCon = oldContent[selectIndex]
+            if (selectCon?.type === '103') {
+                let componentItem = selectCon?.layoutItems.componentItem[goodsCount]
+                let shelfnewItem = componentItem?.layoutItems?.componentItem[0]?.layoutItems?.componentItem
+                shelfnewItem[count + 1][parameter] = value
+                if (count + 1 === 2 && parameter === 'content') {
+                    if (shelfnewItem[count + 1]?.name === '价格') {
+                        shelfnewItem[count + 1][parameter] = '¥' + value
+                    } else {
+                        shelfnewItem[count + 1][parameter] = value
+                    }
+                }
+            } else if (selectCon?.type === '104') {
+                let componentItem = selectCon?.layoutItems?.componentItem[0]?.layoutItems?.componentItem
+                let textData = componentItem[1]?.layoutItems?.componentItem
+                textData[count][parameter] = value
+                if (count === 1 && parameter === 'content') {
+                    let name = textData[count]?.name
+                    if (name === '价格') {
+                        textData[count][parameter] = '¥' + value
+                    } else {
+                        textData[count][parameter] = value
+                    }
+                }
+            }
+            oldContent[selectIndex] = { ...selectCon }
+            dispatch({ type: 'setCon', params: { content: [...oldContent] } })
+        }
+    }, [content, goodsCount])
+    /** 设置描述类型 */
+    const onSetShelfnewDescType = useCallback((e: RadioChangeEvent) => {
+        let newContent = JSON.parse(JSON.stringify(content))
+        let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
+        let oldContent = newContent
+        if (selectIndex !== -1) {
+            let selectCon = oldContent[selectIndex]
+            if (selectCon?.type === '103') {
+                let componentItem = selectCon?.layoutItems.componentItem[goodsCount]
+                let shelfnewItem = componentItem?.layoutItems?.componentItem[0]
+                shelfnewItem.descType = e.target.value
+                let textData = shelfnewItem?.layoutItems?.componentItem
+                if (e.target.value === 'price') {
+                    textData[2] = {
+                        ...textData[2],
+                        name: '价格',
+                        content: '',
+                        fontColor: '#353535',
+                        fontSize: '32',
+                        showType: "1",
+                    }
+                } else {
+                    let { showType, ...desc } = textData[2]
+                    textData[2] = {
+                        ...desc,
+                        name: '描述',
+                        content: '',
+                        fontColor: '#4D4D4D',
+                        fontSize: '24'
+                    }
+                }
+            } else if (selectCon?.type === '104') {
+                let componentItem = selectCon?.layoutItems?.componentItem[0]?.layoutItems?.componentItem
+                componentItem[1].descType = e.target.value
+                let textData = componentItem[1]?.layoutItems?.componentItem
+                if (e.target.value === 'price') {
+                    textData[1] = {
+                        ...textData[1],
+                        name: '价格',
+                        content: '',
+                        fontColor: '#353535',
+                        fontSize: '32',
+                        showType: "1",
+                    }
+                } else {
+                    let { showType, ...desc } = textData[1]
+                    textData[1] = {
+                        ...desc,
+                        name: '描述',
+                        content: '',
+                        fontColor: '#4D4D4D',
+                        fontSize: '24'
+                    }
+                }
+            }
+            oldContent[selectIndex] = { ...selectCon }
+            dispatch({ type: 'setCon', params: { content: [...oldContent] } })
+        }
+    }, [content, goodsCount])
+
+    /** 设置跳转方式 */
+    const onSetShelfnewJumpMode = useCallback((e: RadioChangeEvent) => {
+        let newContent = JSON.parse(JSON.stringify(content))
+        let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
+        let oldContent = newContent
+        if (selectIndex !== -1) {
+            let selectCon = oldContent[selectIndex]
+            if (selectCon?.type === '103') {
+                selectCon.layoutItems.componentItem[goodsCount].layoutItems.componentItem[0].jumpMode = e.target.value
+                let jumpData = selectCon.layoutItems.componentItem[goodsCount].layoutItems.componentItem[0].layoutItems.componentItem[3]
+                let widgetTypeV2 = jumpData?.widgetTypeV2
+                let componentItem = selectCon?.layoutItems?.componentItem[goodsCount]?.layoutItems?.componentItem
+                if (e.target.value === 'btn_jump') {
+                    selectCon.layoutItems.componentItem[goodsCount].layoutItems.componentItem = [{ ...componentItem[0] }]
+                } else {
+                    switch (widgetTypeV2) {
+                        case 'link':
+                            componentItem[1] = { ...btModelJumpLink103, origBtnJumpUrl: jumpData?.origBtnJumpUrl }
+                            break
+                        case 'gh':
+                            componentItem[1] = { ...btModelJumpGh103, subType: jumpData?.subType, btnFontType: jumpData?.btnFontType }
+                            break
+                        case 'enterprise_wx_auto':
+                            componentItem[1] = { ...btModelJumpWxAuto103, subType: jumpData?.subType, btnFontType: jumpData?.btnFontType }
+                            break
+                    }
+                }
+            } else if (selectCon?.type === '104') {
+                selectCon.layoutItems.componentItem[0].jumpMode = e.target.value
+                let textData = selectCon?.layoutItems?.componentItem[0]?.layoutItems?.componentItem[1]?.layoutItems?.componentItem
+                let widgetTypeV2 = textData[2].widgetTypeV2
+                let componentItem = selectCon?.layoutItems?.componentItem
+                if (e.target.value === 'btn_jump') {
+                    selectCon.layoutItems.componentItem = [{ ...componentItem[0] }]
+                } else {
+                    switch (widgetTypeV2) {
+                        case 'link':
+                            componentItem[1] = { ...btModelJumpLink, origBtnJumpUrl: textData[2]?.origBtnJumpUrl }
+                            break
+                        case 'gh':
+                            componentItem[1] = { ...btModelJumpGh, subType: textData[2]?.subType, btnFontType: textData[2]?.btnFontType }
+                            break
+                        case 'enterprise_wx_auto':
+                            componentItem[1] = { ...btModelJumpWxAuto, subType: textData[2]?.subType, btnFontType: textData[2]?.btnFontType }
+                            break
+                    }
+                }
+            }
+            oldContent[selectIndex] = { ...selectCon }
+            dispatch({ type: 'setCon', params: { content: [...oldContent] } })
+        }
+    }, [content, goodsCount])
+    // 设置图文跳转链接按钮
+    const onSetShelfnewButton = useCallback((value: string) => {
+        let newContent = JSON.parse(JSON.stringify(content))
+        let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
+        let oldContent = newContent
+        if (selectIndex !== -1) {
+            let selectCon = oldContent[selectIndex]
+            // "btn_jump" "total_jump"
+            if (selectCon?.type === '103') {
+                let jumpMode = selectCon.layoutItems.componentItem[goodsCount].layoutItems.componentItem[0].jumpMode
+                let btnContent = selectCon.layoutItems.componentItem[goodsCount].layoutItems.componentItem[0].layoutItems.componentItem[3]
+                if (value === 'link') {
+                    if (jumpMode === "total_jump") {
+                        selectCon.layoutItems.componentItem[goodsCount].layoutItems.componentItem[1] = { ...btModelJumpLink103 }
+                    }
+                    if (btnContent?.horizontalAlignment) {
+                        selectCon.layoutItems.componentItem[goodsCount].layoutItems.componentItem[0].layoutItems.componentItem[3] = { ...jumpLink103, horizontalAlignment: '1' }
+                    } else {
+                        selectCon.layoutItems.componentItem[goodsCount].layoutItems.componentItem[0].layoutItems.componentItem[3] = { ...jumpLink103 }
+                    }
+                } else if (value === 'gh') {
+                    if (jumpMode === "total_jump") {
+                        selectCon.layoutItems.componentItem[goodsCount].layoutItems.componentItem[1] = { ...btModelJumpGh103 }
+                    }
+                    if (btnContent?.horizontalAlignment) {
+                        selectCon.layoutItems.componentItem[goodsCount].layoutItems.componentItem[0].layoutItems.componentItem[3] = { ...jumpGh103, horizontalAlignment: '1' }
+                    } else {
+                        selectCon.layoutItems.componentItem[goodsCount].layoutItems.componentItem[0].layoutItems.componentItem[3] = { ...jumpGh103 }
+                    }
+                } else if (value === 'enterprise_wx_auto') {
+                    if (jumpMode === "total_jump") {
+                        selectCon.layoutItems.componentItem[goodsCount].layoutItems.componentItem[1] = { ...btModelJumpWxAuto103 }
+                    }
+                    if (btnContent?.horizontalAlignment) {
+                        selectCon.layoutItems.componentItem[goodsCount].layoutItems.componentItem[0].layoutItems.componentItem[3] = { ...jumpWxAuto103, horizontalAlignment: '1' }
+                    } else {
+                        selectCon.layoutItems.componentItem[goodsCount].layoutItems.componentItem[0].layoutItems.componentItem[3] = { ...jumpWxAuto103 }
+                    }
+                }
+            } else if (selectCon?.type === '104') {
+                let jumpMode = selectCon.layoutItems.componentItem[0].jumpMode
+                if (value === 'link') {
+                    if (jumpMode === "total_jump") {
+                        selectCon.layoutItems.componentItem[1] = { ...btModelJumpLink }
+                    }
+                    selectCon.layoutItems.componentItem[0].layoutItems.componentItem[1].layoutItems.componentItem[2] = { ...jumpLink104 }
+                } else if (value === 'gh') {
+                    if (jumpMode === "total_jump") {
+                        selectCon.layoutItems.componentItem[1] = { ...btModelJumpGh }
+                    }
+                    selectCon.layoutItems.componentItem[0].layoutItems.componentItem[1].layoutItems.componentItem[2] = { ...jumpGh104 }
+                } else if (value === 'enterprise_wx_auto') {
+                    if (jumpMode === "total_jump") {
+                        selectCon.layoutItems.componentItem[1] = { ...btModelJumpWxAuto }
+                    }
+                    selectCon.layoutItems.componentItem[0].layoutItems.componentItem[1].layoutItems.componentItem[2] = { ...jumpWxAuto104 }
+                }
+            }
+            oldContent[selectIndex] = { ...selectCon }
+            dispatch({ type: 'setCon', params: { content: [...oldContent] } })
+        }
+    }, [content, goodsCount])
+    // 配置图文跳转链接按钮字段
+    const onSetShelfnewButtonField = useCallback((field: string, value: string) => {
+        let newContent = JSON.parse(JSON.stringify(content))
+        let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
+        let oldContent = newContent
+        if (selectIndex !== -1) {
+            let selectCon = oldContent[selectIndex]
+            if (selectCon?.type === '103') {
+                let jumpMode = selectCon.layoutItems.componentItem[goodsCount].layoutItems.componentItem[0].jumpMode
+                selectCon.layoutItems.componentItem[goodsCount].layoutItems.componentItem[0].layoutItems.componentItem[3][field] = value
+                if (field === 'btnBorderColorTheme') {
+                    selectCon.layoutItems.componentItem[goodsCount].layoutItems.componentItem[0].layoutItems.componentItem[3]['borderSize'] = "2"
+                }
+                if (jumpMode === 'total_jump' && (field === 'subType' || field === 'btnFontType')) {
+                    selectCon.layoutItems.componentItem[goodsCount].layoutItems.componentItem[1][field] = value
+                }
+            } else if (selectCon?.type === '104') {
+                let jumpMode = selectCon.layoutItems.componentItem[0].jumpMode
+                selectCon.layoutItems.componentItem[0].layoutItems.componentItem[1].layoutItems.componentItem[2][field] = value
+                if (field === 'btnBorderColorTheme') {
+                    selectCon.layoutItems.componentItem[0].layoutItems.componentItem[1].layoutItems.componentItem[2]['borderSize'] = "2"
+                }
+                if (jumpMode === 'total_jump' && (field === 'subType' || field === 'btnFontType')) {
+                    selectCon.layoutItems.componentItem[1][field] = value
+                }
+            }
+            oldContent[selectIndex] = { ...selectCon }
+            dispatch({ type: 'setCon', params: { content: [...oldContent] } })
+        }
+    }, [content, goodsCount])
+
+    // 设置图文复合组件类型
+    const setShelfnewType = useCallback((value: string) => {
+        let newContent = JSON.parse(JSON.stringify(content))
+        let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
+        if (selectIndex !== -1) {
+            if (value === '103') {
+                newContent[selectIndex] = { ...shelfnewContent2, comptActive: true }
+            } else if (value === '104') {
+                newContent[selectIndex] = { ...shelfnewContent, comptActive: true }
+            }
+            dispatch({ type: 'setCon', params: { content: [...newContent] } })
+        }
+    }, [content])
+
+    // 设置对齐方式
+    const setAilgin103 = useCallback((align: number) => {
+        let newContent = JSON.parse(JSON.stringify(content))
+        let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
+        if (selectIndex !== -1) {
+            let selectCon = newContent[selectIndex]
+            selectCon.wxad_align = align
+            let componentItem = selectCon?.layoutItems?.componentItem?.map((item: any) => {
+                let shelfnewItem = item?.layoutItems?.componentItem[0]?.layoutItems?.componentItem
+                if (align === 0) {
+                    shelfnewItem = shelfnewItem?.map((oldItem: any, index: number) => {
+                        if (index === 0) {
+                            return oldItem
+                        } else if (index === 1 || index === 2) {
+                            let { textAlignment, ...newItem } = oldItem
+                            return newItem
+                        } else {
+                            let { horizontalAlignment, ...newItem } = oldItem
+                            return newItem
+                        }
+                    })
+                } else {
+                    shelfnewItem = shelfnewItem?.map((newItem: any, index: number) => {
+                        if (index === 0) {
+                            return newItem
+                        } else if (index === 1 || index === 2) {
+                            return { ...newItem, textAlignment: "1" }
+                        } else {
+                            return { ...newItem, horizontalAlignment: "1" }
+                        }
+
+                    })
+                }
+                item.layoutItems.componentItem[0].layoutItems.componentItem = shelfnewItem
+                return item
+            })
+            selectCon.layoutItems.componentItem = componentItem
+            newContent[selectIndex] = { ...selectCon }
+            dispatch({ type: 'setCon', params: { content: [...newContent] } })
+        }
+    }, [content])
+
+    // 设置悬浮弹窗
+    const setGlobalComponentItem = (key: string, value: any, isDel = false) => {
+        let newConItem = JSON.parse(JSON.stringify(componentItem))
+        let selectIndex = newConItem?.findIndex((item: Content) => item.comptActive)
+        let oldContent = newConItem
+        if (selectIndex !== -1) {
+            let selectCon = oldContent[selectIndex]
+            // 设置悬浮窗 转化按钮
+            if (key === 'componentItem' && (value === 'gh' || value === 'enterprise_wx_auto')) {
+                if (value === 'gh') {
+                    selectCon[key] = floatbuttonBtTypeGh
+                } else if (value === 'enterprise_wx_auto') {
+                    selectCon[key] = floatbuttonBtTypeWxAuto
+                }
+            } else {
+                if (!isDel) {
+                    selectCon[key] = value
+                    if (key === 'wxad_styleType') {
+                        if (value === '1') {
+                            selectCon['iconUrl'] = ""
+                            selectCon['desc'] = selectCon?.desc || ""
+                        } else if (value === '2') {
+                            delete selectCon['iconUrl']
+                            selectCon['desc'] = selectCon?.desc || ""
+                        } else if (value === '3') {
+                            delete selectCon['iconUrl']
+                            delete selectCon['desc']
+                        }
+                        selectCon[key] = value
+                    }
+                } else {
+                    delete selectCon[key]
+                }
+            }
+
+            dispatchGlobal({ type: 'setConItem', params: { componentItem: JSON.parse(JSON.stringify(oldContent)) } })
+        }
+    }
+
+    // 选中设置
+    const rightCon = () => {
+        let selectCon = content?.find((item: Content) => item.comptActive)
+        if (!selectCon) {
+            selectCon = componentItem?.find((item: { comptActive: boolean }) => item.comptActive)
+        }
+        if (selectCon) {
+            let { elementType, adLocation, outerStyle, imageUrlList, pureImageUrl, imageUrl, text, fontColor, fontSize, fontStyle, textAlignment,
+                paddingTop, paddingBottom, fastFollow, btnTitle, btnFontType, btnBorderColorTheme, btnBgColorTheme, useIcon,
+                styleType, type, initHeight, outerUseTopMaterial, componentCount, activeIndex, componentGroupList, origBtnJumpUrl,
+                layoutItems, borderColor, bgColor, wxad_align, wxad_styleType, iconUrl, title, desc, titleColor, descColor, componentItem, appearPaddingTop, appearPaddingBottom } = selectCon
+            let descType = "text"
+            let jumpMode = "btn_jump"
+            let shelfnewImgData: { pureImageUrl: string } = { pureImageUrl: '' }  // 图片信息
+            let shelfnewTitleData: { content: string, fontColor: string } = { content: "", fontColor: "#353535" } // 标题信息
+            let shelfnewDescData: { name: string, content: string, fontColor: string } = { name: '', content: "", fontColor: "#B2B2B2" } // 描述信息
+            let shelfnewBtData: {
+                widgetTypeV2: string, btnTitle: string, btnBgColorTheme: string,
+                subType: '17' | '1', btnFontType: '0' | '1',
+                btnBorderColorTheme: string, fontColor: string, origBtnJumpUrl: string,
+                custorData: any[]
+            } = { widgetTypeV2: '', btnFontType: '0', subType: '17', btnTitle: "", btnBgColorTheme: "#07C160", btnBorderColorTheme: "#FFFFFF", fontColor: "#FFFFFF", origBtnJumpUrl: "", custorData: [] }
+
+            let custorGroup = []
+            if (elementType === 'enterprise_wx_auto') {
+                custorGroup = selectCon?.custorData || []
+            } else if (elementType === 'shelfnew') {
+                if (type === '104') {  // 图文复合组件 单个
+                    let shelfnewData = layoutItems?.componentItem[0]?.layoutItems?.componentItem
+                    jumpMode = layoutItems?.componentItem[0]?.jumpMode
+                    descType = shelfnewData[1]?.descType
+                    shelfnewImgData = shelfnewData[0]
+                    shelfnewTitleData = shelfnewData[1]?.layoutItems?.componentItem[0]
+                    shelfnewDescData = shelfnewData[1]?.layoutItems?.componentItem[1]
+                    shelfnewBtData = shelfnewData[1]?.layoutItems?.componentItem[2]
+                } else if (type === '103') {
+                    // goodsCount
+                    let componentItems = layoutItems.componentItem
+                    let componentItem = componentItems[goodsCount]
+                    borderColor = componentItem?.borderColor || "#e5e5e5"
+                    bgColor = componentItem?.bgColor || "#ffffff"
+                    jumpMode = componentItem?.layoutItems?.componentItem[0]?.jumpMode
+                    let shelfnewItem = componentItem?.layoutItems?.componentItem[0]?.layoutItems?.componentItem
+                    descType = componentItem?.layoutItems?.componentItem[0]?.descType
+                    shelfnewImgData = shelfnewItem[0]
+                    shelfnewTitleData = shelfnewItem[1]
+                    shelfnewDescData = shelfnewItem[2]
+                    shelfnewBtData = shelfnewItem[3]
+                }
+            }
+
+            return <>
+                {
+                    elementType === 'TOP_IMAGE' ? <div className="widget">
+                        <div className="caption section">
+                            <div className="caption-title">顶部组件:图片</div>
+                        </div>
+                        <div className="form section">
+                            <div className="form-caption">广告位与样式</div>
+                            <div className="adui-form-item">
+                                <div className="adui-form-label">广告位</div>
+                                <div className="adui-form-control">
+                                    <Radio.Group onChange={(e) => { setCon('adLocation', e.target.value, true) }} value={adLocation} size='small'>
+                                        <Radio value='sns'>朋友圈信息流</Radio>
+                                        <Radio value='gh'>公众号及其他</Radio>
+                                    </Radio.Group>
+                                </div>
+                            </div>
+                            {adLocation === 'sns' && <div className="adui-form-item">
+                                <div className="adui-form-label">外层样式</div>
+                                <div className="adui-form-control">
+                                    <Select value={outerStyle} style={{ width: 100 }} onChange={(e) => { setCon('outerStyle', e, true) }}>
+                                        <Option value={0}>常规广告</Option>
+                                        <Option value={1}>卡片广告</Option>
+                                    </Select>
+                                </div>
+                            </div>}
+                        </div>
+                        <div className="form section">
+                            <div className="form-caption">素材设置</div>
+                            <div className="adui-form-item" style={{ alignItems: 'flex-start' }}>
+                                <div className="adui-form-label">图片素材</div>
+                                <div className="adui-form-control">
+                                    <div className={`upload-img-item ${imageUrl ? 'upload-img-item_uploaded' : ''}`}>
+                                        {imageUrl ? <div className="upload-img-item-inner" style={{ backgroundImage: `url(${imageUrl})` }}>
+                                            <div className='upload-img-item-action' onClick={() => { clickUpdateImg(1) }}>
+                                                <RetweetOutlined />
+                                            </div>
+                                        </div> : <div className="upload-img-item-inner" onClick={() => { clickUpdateImg(1) }}>
+                                            <PlusOutlined />
+                                        </div>}
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                        :
+                        elementType === 'TOP_SLIDER' ? <div className="widget">
+                            <div className="caption section">
+                                <div className="caption-title">顶部组件:轮播图</div>
+                            </div>
+                            <div className="form section">
+                                <div className="form-caption">素材设置</div>
+                                <div className="adui-form-item">
+                                    <div className="adui-form-label">图片数量</div>
+                                    <div className="adui-form-control">
+                                        <Radio.Group onChange={(e) => { sliderImgNum(e.target.value) }} value={imageUrlList?.length || 3} size='small'>
+                                            <Radio.Button value={3}>3张</Radio.Button>
+                                            <Radio.Button value={4}>4张</Radio.Button>
+                                            <Radio.Button value={6}>6张</Radio.Button>
+                                        </Radio.Group>
+                                    </div>
+                                </div>
+                            </div>
+
+                            <div className="form section">
+                                <div className="form-caption">上传素材</div>
+                                <SortableUlList axis='xy' onSortEnd={onSortEndSlider} useDragHandle>
+                                    {imageUrlList?.map((item: any, index: number) => {
+                                        if (item) {
+                                            return <SortableItemLi key={`slider-${index}`} index={index} isActive={activeIndex === index} pureImageUrl={item} click={() => { sliderSelect(index, imageUrlList.length) }} setActiveIndex={() => { setCon('activeIndex', index) }}></SortableItemLi>
+                                        } else {
+                                            return <SortableItemNoLi key={`slider-${index}`} index={index} isActive={activeIndex === index} click={() => { sliderSelect(index, imageUrlList.length) }}></SortableItemNoLi>
+                                        }
+                                    })}
+                                </SortableUlList>
+                            </div>
+                        </div>
+                            :
+                            elementType === 'TOP_VIDEO' ? <div className="widget">
+                                <div className="caption section">
+                                    <div className="caption-title">顶部组件:视频</div>
+                                </div>
+                                <div className="form section">
+                                    <div className="form-caption">广告位与样式</div>
+                                    <div className="adui-form-item">
+                                        <div className="adui-form-label">广告位</div>
+                                        <div className="adui-form-control">
+                                            <Radio.Group onChange={(e) => { changeAdLocation(e.target.value) }} value={adLocation} size='small'>
+                                                <Radio value='sns'>朋友圈信息流</Radio>
+                                                <Radio value='gh'>公众号及其他</Radio>
+                                            </Radio.Group>
+                                        </div>
+                                    </div>
+                                    {adLocation === 'sns' && <div className="adui-form-item">
+                                        <div className="adui-form-label">外层样式</div>
+                                        <div className="adui-form-control">
+                                            <Select size="small" value={outerStyle} style={{ width: 100 }} onChange={(e) => { changeOuterLayout(e) }}>
+                                                <Option value={0}>常规广告</Option>
+                                                <Option value={1} disabled>卡片广告</Option>
+                                            </Select>
+                                        </div>
+                                    </div>}
+                                    {/* {
+                                        adLocation === 'sns' && outerStyle === 0 && <div className="adui-form-item">
+                                            <div className="adui-form-label">视频类型</div>
+                                            <div className="adui-form-control">
+                                                <Radio.Group onChange={(e) => { viewTypeChange(e.target.value, 1) }} value={styleType} size='small'>
+                                                    <Radio value='0'>横板视频</Radio>
+                                                    <Radio value='1'>竖版视频</Radio>
+                                                </Radio.Group>
+                                            </div>
+                                        </div>
+                                    }
+                                    {
+                                        adLocation === 'sns' && outerStyle === 1 && <div className="adui-form-item">
+                                            <div className="adui-form-label">外层素材</div>
+                                            <div className="adui-form-control">
+                                                <Checkbox onChange={(e: CheckboxChangeEvent) => { outerUseTopMaterialHandle(e.target.checked) }} checked={outerUseTopMaterial === '0' ? false : true}>顶部素材不用于广告外层</Checkbox>
+                                            </div>
+                                        </div>
+                                    } */}
+                                    {/* {
+                                        outerUseTopMaterial === '1' && <div className="adui-form-item">
+                                            <div className="adui-form-label">视频类型</div>
+                                            <div className="adui-form-control">
+                                                <Radio.Group onChange={(e) => { viewTypeChange(e.target.value, 2) }} value={type} size='small'>
+                                                    <Radio value='61'>短视频</Radio>
+                                                    <Radio value='62'>长视频</Radio>
+                                                </Radio.Group>
+                                            </div>
+                                        </div>
+                                    } */}
+                                    {/* {
+                                        adLocation === 'gh' && <div className="adui-form-item">
+                                            <div className="adui-form-label">视频类型</div>
+                                            <div className="adui-form-control">
+                                                <Radio.Group onChange={(e) => { viewTypeChange(e.target.value, 2) }} value={type} size='small'>
+                                                    <Radio value='61'>短视频</Radio>
+                                                    <Radio value='62'>长视频</Radio>
+                                                </Radio.Group>
+                                            </div>
+                                        </div>
+                                    } */}
+                                    {adLocation === 'sns' && styleType === '1' && <div className="adui-form-item" style={{ alignItems: 'flex-start' }}>
+                                        <div className="adui-form-label" style={{ marginTop: 2 }}>视频尺寸</div>
+                                        <div className="adui-form-control">
+                                            <Radio.Group value={initHeight} onChange={(e) => { setCon('initHeight', e.target.value) }}>
+                                                <Radio value={1536}>750像素 x 1536像素</Radio>
+                                                <Radio value={1334}>750像素 x 1334像素</Radio>
+                                                <Radio value={1280}>720像素 x 1280像素</Radio>
+                                            </Radio.Group>
+                                        </div>
+                                    </div>}
+                                </div>
+                            </div>
+                                :
+                                elementType === 'TEXT' ? <div className="widget">
+                                    <div className="caption section">
+                                        <div className="caption-title">文本</div>
+                                    </div>
+                                    <div className="form section">
+                                        <div className="form-caption">推广文案</div>
+                                        <TextArea
+                                            placeholder="请输入"
+                                            autoSize={{ minRows: 4, maxRows: 6 }}
+                                            value={text}
+                                            onChange={(e) => { setCon('text', e.target.value) }}
+                                        />
+                                    </div>
+                                    <div className="form section">
+                                        <div className="form-caption">字符与段落</div>
+                                        <div className="adui-form-item">
+                                            <div className="adui-form-label">字符样式</div>
+                                            <div className="adui-form-control">
+                                                <div className="fl-sb">
+                                                    <div style={{ display: 'flex', justifyContent: 'flex-start', alignItems: 'center' }}>
+                                                        <Space>
+                                                            <Select value={fontSize} style={{ width: 60 }} onChange={(e) => { setCon('fontSize', e) }}>
+                                                                {[14, 15, 16, 18, 20, 24, 36].map((item: number) => (<Option value={item} key={item}>{item}</Option>))}
+                                                            </Select>
+                                                            <ColorPicker onColor={(color: string) => { setCon('fontColor', color) }} color={fontColor}></ColorPicker>
+                                                        </Space>
+                                                    </div>
+                                                    <Radio.Group onChange={(e) => { setCon('fontStyle', e.target.value) }} value={fontStyle}>
+                                                        <Radio.Button value={0}>常规</Radio.Button>
+                                                        <Radio.Button value={1}>加粗</Radio.Button>
+                                                    </Radio.Group>
+                                                </div>
+                                            </div>
+                                        </div>
+                                        <div className="adui-form-item">
+                                            <div className="adui-form-label">对齐方式</div>
+                                            <div className="adui-form-control">
+                                                <Radio.Group onChange={(e) => { setCon('textAlignment', e.target.value) }} value={textAlignment}>
+                                                    <Radio.Button value={0}><AlignLeftOutlined /></Radio.Button>
+                                                    <Radio.Button value={1}><AlignCenterOutlined /></Radio.Button>
+                                                    <Radio.Button value={2}><AlignRightOutlined /></Radio.Button>
+                                                </Radio.Group>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div className="form section">
+                                        <div className="form-caption">边距</div>
+                                        <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                            <div className="adui-form-label">上边距</div>
+                                            <div className="adui-form-control">
+                                                <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+                                                    <div style={{ flexGrow: 1 }}><Slider value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} /></div>
+                                                    <InputNumber min={0} max={100} step={1} value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} style={{ width: 80, marginLeft: 20 }} />
+                                                </div>
+                                            </div>
+                                        </div>
+                                        <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                            <div className="adui-form-label">下边距</div>
+                                            <div className="adui-form-control">
+                                                <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+                                                    <div style={{ flexGrow: 1 }}><Slider value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} /></div>
+                                                    <InputNumber min={0} max={100} step={1} value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} style={{ width: 80, marginLeft: 20 }} />
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                                    :
+                                    elementType === 'IMAGE' ? <div className="widget">
+                                        <div className="caption section">
+                                            <div className="caption-title">图片</div>
+                                        </div>
+                                        <div className="form section">
+                                            <div className="form-caption">素材设置</div>
+                                            <div className="adui-form-item" style={{ alignItems: 'flex-start' }}>
+                                                <div className="adui-form-label">图片素材</div>
+                                                <div className="adui-form-control">
+                                                    <div className={`upload-img-item ${imageUrl ? 'upload-img-item_uploaded' : ''}`}>
+                                                        {
+                                                            imageUrl ? <div className="upload-img-item-inner" style={{ backgroundImage: `url(${imageUrl ? imageUrl : ""})` }}>
+                                                                <div className='upload-img-item-action' onClick={() => { clickUpdateImg(1) }}>
+                                                                    <RetweetOutlined />
+                                                                </div>
+                                                            </div>
+                                                                :
+                                                                <div className="upload-img-item-inner" onClick={() => { clickUpdateImg(1) }}>
+                                                                    <PlusOutlined />
+                                                                </div>
+                                                        }
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </div>
+                                        <div className="form section">
+                                            <div className="form-caption">边距</div>
+                                            <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                <div className="adui-form-label">上边距</div>
+                                                <div className="adui-form-control">
+                                                    <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+                                                        <div style={{ flexGrow: 1 }}><Slider value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} /></div>
+                                                        <InputNumber min={0} max={100} step={1} value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} style={{ width: 80, marginLeft: 20 }} />
+                                                    </div>
+                                                </div>
+                                            </div>
+                                            <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                <div className="adui-form-label">下边距</div>
+                                                <div className="adui-form-control">
+                                                    <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+                                                        <div style={{ flexGrow: 1 }}><Slider value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} /></div>
+                                                        <InputNumber min={0} max={100} step={1} value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} style={{ width: 80, marginLeft: 20 }} />
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                        :
+                                        elementType === 'GH' ? <div className="widget">
+                                            <div className="caption section">
+                                                <div className="caption-title">关注公众号</div>
+                                            </div>
+                                            <div className="form section">
+                                                <Space>
+                                                    <Switch size="small" checked={fastFollow === 1 ? true : false} onChange={(e) => { setCon('fastFollow', e ? 1 : 0) }} />
+                                                    一键关注
+                                                    <Tooltip placement="top" title={'唤起公众号简介的半屏面板,点击其中按钮直接关注公众号'}>
+                                                        <QuestionCircleOutlined />
+                                                    </Tooltip>
+                                                </Space>
+                                            </div>
+                                            <div className="form section">
+                                                <div className="form-caption">按钮外观</div>
+                                                <div className="adui-form-item">
+                                                    <div className="adui-form-label">按钮文案</div>
+                                                    <div className="adui-form-control">
+                                                        <div className="fl-sb">
+                                                            <div style={{ display: 'flex', justifyContent: 'flex-start', alignItems: 'center' }}>
+                                                                <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', border: '1px solid #d9d9d9', borderRadius: 2, paddingRight: 8 }}>
+                                                                    <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>
+                                                                </div>
+                                                            </div>
+                                                            <Radio.Group onChange={(e) => { setCon('btnFontType', e.target.value) }} value={btnFontType}>
+                                                                <Radio.Button value={0}>常规</Radio.Button>
+                                                                <Radio.Button value={1}>加粗</Radio.Button>
+                                                            </Radio.Group>
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                                <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                    <div className="adui-form-label">字体色</div>
+                                                    <div className="adui-form-control">
+                                                        <Space><ColorPicker onColor={(color: string) => { setCon('fontColor', color) }} color={fontColor}></ColorPicker><div className="colorShow">{fontColor}</div></Space>
+                                                    </div>
+                                                </div>
+                                                <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                    <div className="adui-form-label">图标</div>
+                                                    <div className="adui-form-control">
+                                                        <Switch size="small" checked={useIcon === 1 ? true : false} onChange={(e) => { iconHandle(e) }} /> <span>{useIcon === 1 ? '已启用' : '未启用'}</span>
+                                                    </div>
+                                                </div>
+                                                <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                    <div className="adui-form-label">边框色</div>
+                                                    <div className="adui-form-control">
+                                                        <Space><ColorPicker onColor={(color: string) => { setCon('btnBorderColorTheme', color) }} color={btnBorderColorTheme}></ColorPicker><div className="colorShow">{btnBorderColorTheme}</div></Space>
+                                                    </div>
+                                                </div>
+                                                <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                    <div className="adui-form-label">背景色</div>
+                                                    <div className="adui-form-control">
+                                                        <Space><ColorPicker onColor={(color: string) => { setCon('btnBgColorTheme', color) }} color={btnBgColorTheme}></ColorPicker><div className="colorShow">{btnBgColorTheme}</div></Space>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                            <div className="form section">
+                                                <div className="form-caption">边距</div>
+                                                <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                    <div className="adui-form-label">上边距</div>
+                                                    <div className="adui-form-control">
+                                                        <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+                                                            <div style={{ flexGrow: 1 }}><Slider value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} /></div>
+                                                            <InputNumber min={0} max={100} step={1} value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} style={{ width: 80, marginLeft: 20 }} />
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                                <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                    <div className="adui-form-label">下边距</div>
+                                                    <div className="adui-form-control">
+                                                        <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+                                                            <div style={{ flexGrow: 1 }}><Slider value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} /></div>
+                                                            <InputNumber min={0} max={100} step={1} value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} style={{ width: 80, marginLeft: 20 }} />
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </div> :
+                                            elementType === 'ENTERPRISE_WX' ? <div className="widget">
+                                                <div className="caption section">
+                                                    <div className="caption-title">添加商家微信</div>
+                                                </div>
+                                                <div className="form section">
+                                                    <div className="form-caption">按钮外观</div>
+                                                    <div className="adui-form-item">
+                                                        <div className="adui-form-label">按钮文案</div>
+                                                        <div className="adui-form-control">
+                                                            <div className="fl-sb">
+                                                                <div style={{ display: 'flex', justifyContent: 'flex-start', alignItems: 'center' }}>
+                                                                    <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', border: '1px solid #d9d9d9', borderRadius: 2, paddingRight: 8 }}>
+                                                                        <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>
+                                                                    </div>
+                                                                </div>
+                                                                <Radio.Group onChange={(e) => { setCon('btnFontType', e.target.value) }} value={btnFontType}>
+                                                                    <Radio.Button value={0}>常规</Radio.Button>
+                                                                    <Radio.Button value={1}>加粗</Radio.Button>
+                                                                </Radio.Group>
+                                                            </div>
+                                                        </div>
+                                                    </div>
+                                                    <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                        <div className="adui-form-label">字体色</div>
+                                                        <div className="adui-form-control">
+                                                            <Space><ColorPicker onColor={(color: string) => { setCon('fontColor', color) }} color={fontColor}></ColorPicker> <div className="colorShow">{fontColor}</div></Space>
+                                                        </div>
+                                                    </div>
+                                                    <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                        <div className="adui-form-label">图标</div>
+                                                        <div className="adui-form-control">
+                                                            <Switch size="small" checked={useIcon === 1 ? true : false} onChange={(e) => { iconHandle(e) }} /> <span>{useIcon === 1 ? '已启用' : '未启用'}</span>
+                                                        </div>
+                                                    </div>
+                                                    <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                        <div className="adui-form-label">边框色</div>
+                                                        <div className="adui-form-control">
+                                                            <Space><ColorPicker onColor={(color: string) => { setCon('btnBorderColorTheme', color) }} color={btnBorderColorTheme}></ColorPicker> <div className="colorShow">{btnBorderColorTheme}</div></Space>
+                                                        </div>
+                                                    </div>
+                                                    <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                        <div className="adui-form-label">背景色</div>
+                                                        <div className="adui-form-control">
+                                                            <Space><ColorPicker onColor={(color: string) => { setCon('btnBgColorTheme', color) }} color={btnBgColorTheme}></ColorPicker> <div className="colorShow">{btnBgColorTheme}</div></Space>
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                                <div className="form section">
+                                                    <div className="form-caption">边距</div>
+                                                    <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                        <div className="adui-form-label">上边距</div>
+                                                        <div className="adui-form-control">
+                                                            <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+                                                                <div style={{ flexGrow: 1 }}><Slider value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} /></div>
+                                                                <InputNumber min={0} max={100} step={1} value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} style={{ width: 80, marginLeft: 20 }} />
+                                                            </div>
+                                                        </div>
+                                                    </div>
+                                                    <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                        <div className="adui-form-label">下边距</div>
+                                                        <div className="adui-form-control">
+                                                            <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+                                                                <div style={{ flexGrow: 1 }}><Slider value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} /></div>
+                                                                <InputNumber min={0} max={100} step={1} value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} style={{ width: 80, marginLeft: 20 }} />
+                                                            </div>
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                                :
+                                                elementType === 'link' ? <div className="widget">
+                                                    {/* <div className="caption section">
+                                                        <div className="caption-title">跳转链接</div>
+                                                    </div>
+                                                    <div className="form section">
+                                                        <div className="form-caption">链接设置</div>
+                                                        <Input placeholder="以 http:// 或 https:// 开头" value={origBtnJumpUrl} onChange={(e) => { setCon('origBtnJumpUrl', e.target.value) }} />
+                                                    </div>
+                                                    <div className="form section">
+                                                        <div className="form-caption">按钮外观</div>
+                                                        <div className="adui-form-item">
+                                                            <div className="adui-form-label">按钮文案</div>
+                                                            <div className="adui-form-control">
+                                                                <div className="fl-sb">
+                                                                    <div style={{ display: 'flex', justifyContent: 'flex-start', alignItems: 'center' }}>
+                                                                        <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', border: '1px solid #d9d9d9', borderRadius: 2, paddingRight: 8 }}>
+                                                                            <Input maxLength={10} style={{ width: 90 }} bordered={false} value={btnTitle} onChange={(e) => { setCon('btnTitle', e.target.value) }} /> <span>{btnTitle?.length}/10</span>
+                                                                        </div>
+                                                                    </div>
+                                                                    <Radio.Group onChange={(e) => { setCon('btnFontType', e.target.value) }} value={btnFontType}>
+                                                                        <Radio.Button value="0">常规</Radio.Button>
+                                                                        <Radio.Button value="1">加粗</Radio.Button>
+                                                                    </Radio.Group>
+                                                                </div>
+                                                            </div>
+                                                        </div>
+                                                        <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                            <div className="adui-form-label">字体色</div>
+                                                            <div className="adui-form-control">
+                                                                <Space><ColorPicker onColor={(color: string) => { setCon('fontColor', color) }} color={fontColor}></ColorPicker><div className="colorShow">{fontColor}</div></Space>
+                                                            </div>
+                                                        </div>
+                                                        <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                            <div className="adui-form-label">图标</div>
+                                                            <div className="adui-form-control">
+                                                                <Switch size="small" checked={useIcon === '1' ? true : false} onChange={(e) => { iconHandle(e) }} /> <span>{useIcon === '1' ? '已启用' : '未启用'}</span>
+                                                            </div>
+                                                        </div>
+                                                        <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                            <div className="adui-form-label">边框色</div>
+                                                            <div className="adui-form-control">
+                                                                <Space><ColorPicker onColor={(color: string) => { setCon('btnBorderColorTheme', color) }} color={btnBorderColorTheme}></ColorPicker><div className="colorShow">{btnBorderColorTheme}</div></Space>
+                                                            </div>
+                                                        </div>
+                                                        <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                            <div className="adui-form-label">背景色</div>
+                                                            <div className="adui-form-control">
+                                                                <Space><ColorPicker onColor={(color: string) => { setCon('btnBgColorTheme', color) }} color={btnBgColorTheme}></ColorPicker><div className="colorShow">{btnBgColorTheme}</div></Space>
+                                                            </div>
+                                                        </div>
+                                                    </div>
+                                                    <div className="form section">
+                                                        <div className="form-caption">边距</div>
+                                                        <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                            <div className="adui-form-label">上边距</div>
+                                                            <div className="adui-form-control">
+                                                                <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+                                                                    <div style={{ flexGrow: 1 }}><Slider value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} /></div>
+                                                                    <InputNumber min={0} max={100} step={1} value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} style={{ width: 80, marginLeft: 20 }} />
+                                                                </div>
+                                                            </div>
+                                                        </div>
+                                                        <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                            <div className="adui-form-label">下边距</div>
+                                                            <div className="adui-form-control">
+                                                                <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+                                                                    <div style={{ flexGrow: 1 }}><Slider value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} /></div>
+                                                                    <InputNumber min={0} max={100} step={1} value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} style={{ width: 80, marginLeft: 20 }} />
+                                                                </div>
+                                                            </div>
+                                                        </div>
+                                                    </div> */}
+                                                </div>
+                                                    :
+                                                    elementType === 'shelfnew' ? <div className="widget">
+                                                        {/* {imgTextButtonShow ? <>
+                                                            <div className="caption section goBack" onClick={() => { setImgTextButtonShow(false) }}>
+                                                                <SwapLeftOutlined />
+                                                                <span>返回</span>
+                                                            </div>
+                                                            {shelfnewBtData.widgetTypeV2 === 'gh' || shelfnewBtData.widgetTypeV2 === 'link' || shelfnewBtData.widgetTypeV2 === 'enterprise_wx_auto' ? <>
+
+                                                                {shelfnewBtData.widgetTypeV2 === 'link' ? <div className="form section">
+                                                                    <div className="form-caption">链接设置</div>
+                                                                    <Input placeholder="以 http:// 或 https:// 开头" value={shelfnewBtData?.origBtnJumpUrl} onChange={(e) => { onSetShelfnewButtonField('origBtnJumpUrl', e.target.value) }} />
+                                                                </div> : shelfnewBtData.widgetTypeV2 === 'gh' ? <div className="form section">
+                                                                    <Space>
+                                                                        <Switch size="small" checked={shelfnewBtData?.subType === '17' ? true : false} onChange={(e) => { onSetShelfnewButtonField('subType', e ? '17' : '1') }} />
+                                                                        一键关注
+                                                                        <Tooltip placement="top" title={'唤起公众号简介的半屏面板,点击其中按钮直接关注公众号'}>
+                                                                            <QuestionCircleOutlined />
+                                                                        </Tooltip>
+                                                                    </Space>
+                                                                </div> : <div className="form section">
+                                                                    <div className="form-caption">客服设置</div>
+                                                                    <div className="adui-form-item">
+                                                                        <div className="adui-form-label">客服分配</div>
+                                                                        <div className="adui-form-control">
+                                                                            <div className="fl-sb">
+                                                                                <Button size="small" onClick={clickCustorGroup}>{shelfnewBtData?.custorData?.length > 0 ? '修改客服组' : '选择客服组'}</Button>
+                                                                            </div>
+                                                                        </div>
+                                                                    </div>
+                                                                    {shelfnewBtData?.custorData?.length > 0 && <div className='adui-form-item custorGroup'>
+                                                                        {shelfnewBtData?.custorData?.map((item: { label: string, custorData: any[], id: number }) => <div key={item.id}>
+                                                                            <strong>{item.label}:</strong><span>{item.custorData[0]?.name}</span>
+                                                                        </div>)}
+                                                                    </div>}
+                                                                </div>}
+
+                                                                <div className="form section">
+                                                                    <div className="form-caption">按钮外观</div>
+                                                                    <div className="adui-form-item">
+                                                                        <div className="adui-form-label">按钮文案</div>
+                                                                        <div className="adui-form-control">
+                                                                            <div className="fl-sb">
+                                                                                <div style={{ display: 'flex', justifyContent: 'flex-start', alignItems: 'center' }}>
+                                                                                    <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', border: '1px solid #d9d9d9', borderRadius: 2, paddingRight: 8 }}>
+                                                                                        <Input maxLength={5} style={{ width: 90 }} bordered={false} value={shelfnewBtData?.btnTitle} onChange={(e) => { onSetShelfnewButtonField('btnTitle', e.target.value) }} /> <span>{shelfnewBtData?.btnTitle?.length}/5</span>
+                                                                                    </div>
+                                                                                </div>
+                                                                                <Radio.Group onChange={(e) => { onSetShelfnewButtonField('btnFontType', e.target.value) }} value={shelfnewBtData?.btnFontType}>
+                                                                                    <Radio.Button value="0">常规</Radio.Button>
+                                                                                    <Radio.Button value="1">加粗</Radio.Button>
+                                                                                </Radio.Group>
+                                                                            </div>
+                                                                        </div>
+                                                                    </div>
+                                                                    <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                                        <div className="adui-form-label">字体色</div>
+                                                                        <div className="adui-form-control">
+                                                                            <Space><ColorPicker onColor={(color: string) => { onSetShelfnewButtonField('fontColor', color) }} color={shelfnewBtData?.fontColor}></ColorPicker><div className="colorShow">{shelfnewBtData?.fontColor}</div></Space>
+                                                                        </div>
+                                                                    </div>
+                                                                    <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                                        <div className="adui-form-label">边框色</div>
+                                                                        <div className="adui-form-control">
+                                                                            <Space><ColorPicker onColor={(color: string) => { onSetShelfnewButtonField('btnBorderColorTheme', color) }} color={shelfnewBtData?.btnBorderColorTheme}></ColorPicker><div className="colorShow">{shelfnewBtData?.btnBorderColorTheme}</div></Space>
+                                                                        </div>
+                                                                    </div>
+                                                                    <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                                        <div className="adui-form-label">背景色</div>
+                                                                        <div className="adui-form-control">
+                                                                            <Space><ColorPicker onColor={(color: string) => { onSetShelfnewButtonField('btnBgColorTheme', color) }} color={shelfnewBtData?.btnBgColorTheme}></ColorPicker><div className="colorShow">{shelfnewBtData?.btnBgColorTheme}</div></Space>
+                                                                        </div>
+                                                                    </div>
+                                                                </div>
+                                                            </> : <></>}
+                                                        </> : <>
+                                                            <div className="caption section">
+                                                                <div className="caption-title">图文复合组件</div>
+                                                            </div>
+                                                            <div className="form section">
+                                                                <div className="adui-form-item">
+                                                                    <div className="adui-form-label">类型</div>
+                                                                    <div className="adui-form-control">
+                                                                        <>
+                                                                            <Radio.Group onChange={(e) => { setShelfnewType(e.target.value) }} value={type}>
+                                                                                <Radio value='104'>一行1个</Radio>
+                                                                                <Radio value='103'>一行2个</Radio>
+                                                                            </Radio.Group>
+                                                                            {type === '103' && <Radio.Group onChange={(e) => { setGoodsCount(e.target.value) }} value={goodsCount} size='small' style={{ marginTop: 10 }}>
+                                                                                <Radio.Button value={0}>商品1</Radio.Button>
+                                                                                <Radio.Button value={1}>商品2</Radio.Button>
+                                                                            </Radio.Group>}
+                                                                        </>
+                                                                    </div>
+                                                                </div>
+                                                            </div>
+
+                                                            <div className="form section">
+                                                                <div className="adui-form-item">
+                                                                    <div className="adui-form-label">配图</div>
+                                                                    <div className="adui-form-control">
+                                                                        <div>
+                                                                            <Button onClick={() => { setCcType(4); setSelectImgVisible(true); setNum(1) }}>上传图片</Button>
+                                                                            <div style={{ marginTop: 4, fontSize: 12, color: '#636363' }}>{type === '103' ? '尺寸:480像素*480像素' : '尺寸:360像素*360像素'}</div>
+                                                                            <div style={{ marginTop: 4, fontSize: 12, color: '#636363' }}>格式:不超过300k</div>
+                                                                        </div>
+                                                                    </div>
+                                                                </div>
+                                                                <div className="adui-form-item" style={{ alignItems: 'flex-start' }}>
+                                                                    <div className="adui-form-label" style={{ marginTop: 6 }}>标题</div>
+                                                                    <div className="adui-form-control">
+                                                                        <Input.TextArea placeholder="请输入标题" onChange={(e) => { onShelfnewTxtCon(e.target.value?.replace(/\r|\n/ig, ""), 0, 'content') }} value={shelfnewTitleData?.content} showCount maxLength={type === '104' ? 12 : 8} autoSize />
+                                                                    </div>
+                                                                </div>
+
+                                                                <div className="adui-form-item">
+                                                                    <div className="adui-form-label">描述</div>
+                                                                    <div className="adui-form-control">
+                                                                        <Space direction="vertical" style={{ width: '100%' }}>
+                                                                            <Radio.Group onChange={onSetShelfnewDescType} value={descType}>
+                                                                                <Radio value="text">文字</Radio>
+                                                                                <Radio value="price">价格</Radio>
+                                                                            </Radio.Group>
+                                                                            {descType === 'text' ?
+                                                                                <Input.TextArea placeholder="请输入描述" onChange={(e) => { onShelfnewTxtCon(e.target.value?.replace(/\r|\n/ig, ""), 1, 'content') }} value={shelfnewDescData?.content} showCount maxLength={type === '104' ? 15 : 10} autoSize={{ minRows: 2, maxRows: 2 }} /> :
+                                                                                <div>
+                                                                                    <Input placeholder="请输入" suffix="元" onChange={(e) => { onShelfnewTxtCon(e.target.value, 1, 'content') }} style={{ width: 150 }} value={shelfnewDescData?.content?.split('¥')[1]} />
+                                                                                    <div style={{ fontSize: 12, color: '#636363', marginTop: 5 }}>价格范围0.01~999,999,999.99元</div>
+                                                                                </div>
+                                                                            }
+                                                                        </Space>
+                                                                    </div>
+                                                                </div>
+
+                                                                {type === '103' && <div className="adui-form-item">
+                                                                    <div className="adui-form-label">对齐</div>
+                                                                    <div className="adui-form-control">
+                                                                        <Radio.Group onChange={(e) => { setAilgin103(e.target.value) }} value={wxad_align}>
+                                                                            <Radio value={0}>左对齐</Radio>
+                                                                            <Radio value={1}>居中对齐</Radio>
+                                                                        </Radio.Group>
+                                                                    </div>
+                                                                </div>}
+
+                                                                <div className="adui-form-item" style={{ alignItems: 'flex-start' }}>
+                                                                    <div className="adui-form-label" style={{ marginTop: 14 }}>字体颜色</div>
+                                                                    <div className="adui-form-control">
+                                                                        <Space style={{ width: '100%' }} size="large" className="shelfnewColor">
+                                                                            <div>
+                                                                                <Space><ColorPicker onColor={(color: string) => { onShelfnewTxtCon(color, 0, 'fontColor') }} color={shelfnewTitleData?.fontColor}></ColorPicker> <div className="colorShow">{shelfnewTitleData?.fontColor}</div></Space>
+                                                                                <div className="colorName">标题</div>
+                                                                            </div>
+                                                                            <div>
+                                                                                <Space><ColorPicker onColor={(color: string) => { onShelfnewTxtCon(color, 1, 'fontColor') }} color={shelfnewDescData?.fontColor}></ColorPicker> <div className="colorShow">{shelfnewDescData?.fontColor}</div></Space>
+                                                                                <div className="colorName">{shelfnewDescData?.name}</div>
+                                                                            </div>
+                                                                        </Space>
+                                                                    </div>
+                                                                </div>
+                                                                <div className="adui-form-item" style={{ alignItems: 'flex-start' }}>
+                                                                    <div className="adui-form-label" style={{ marginTop: 14 }}>其它颜色</div>
+                                                                    <div className="adui-form-control">
+                                                                        <Space style={{ width: '100%' }} size="large" className="shelfnewColor">
+                                                                            <div>
+                                                                                <Space><ColorPicker onColor={(color: string) => { setCon('borderColor', color) }} color={borderColor}></ColorPicker> <div className="colorShow">{borderColor}</div></Space>
+                                                                                <div className="colorName">边框</div>
+                                                                            </div>
+                                                                            <div>
+                                                                                <Space><ColorPicker onColor={(color: string) => { setCon('bgColor', color) }} color={bgColor}></ColorPicker> <div className="colorShow">{bgColor}</div></Space>
+                                                                                <div className="colorName">背景</div>
+                                                                            </div>
+                                                                        </Space>
+                                                                    </div>
+                                                                </div>
+                                                            </div>
+
+                                                            <div className="form section">
+                                                                <div className="form-caption">边距</div>
+                                                                <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                                    <div className="adui-form-label">上边距</div>
+                                                                    <div className="adui-form-control">
+                                                                        <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+                                                                            <div style={{ flexGrow: 1 }}><Slider value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} /></div>
+                                                                            <InputNumber min={0} max={100} step={1} value={paddingTop} onChange={(value: number) => { setCon('paddingTop', value) }} style={{ width: 80, marginLeft: 20 }} />
+                                                                        </div>
+                                                                    </div>
+                                                                </div>
+                                                                <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                                    <div className="adui-form-label">下边距</div>
+                                                                    <div className="adui-form-control">
+                                                                        <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+                                                                            <div style={{ flexGrow: 1 }}><Slider value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} /></div>
+                                                                            <InputNumber min={0} max={100} step={1} value={paddingBottom} onChange={(value: number) => { setCon('paddingBottom', value) }} style={{ width: 80, marginLeft: 20 }} />
+                                                                        </div>
+                                                                    </div>
+                                                                </div>
+                                                            </div>
+
+                                                            <div className="form section">
+                                                                <div className="form-caption">按钮</div>
+                                                                <div className="adui-form-item">
+                                                                    <div className="adui-form-label">跳转方式</div>
+                                                                    <div className="adui-form-control">
+                                                                        <Radio.Group onChange={onSetShelfnewJumpMode} value={jumpMode}>
+                                                                            <Radio value="btn_jump">按钮跳转</Radio>
+                                                                            <Radio value="total_jump">全局跳转</Radio>
+                                                                        </Radio.Group>
+                                                                    </div>
+                                                                </div>
+                                                                <div className="adui-form-item">
+                                                                    <div className="adui-form-label">按钮类型</div>
+                                                                    <div className="adui-form-control">
+                                                                        <Space>
+                                                                            <Select style={{ width: 120 }} value={shelfnewBtData.widgetTypeV2} onChange={(e) => { onSetShelfnewButton(e) }}>
+                                                                                <Option value="link">跳转链接</Option>
+                                                                                <Option value="gh">关注公众号</Option>
+                                                                                <Option value="enterprise_wx_auto">添加商家微信</Option>
+                                                                            </Select>
+                                                                            <Button type="link" size="small" onClick={() => { setImgTextButtonShow(true) }}>配置</Button>
+                                                                        </Space>
+                                                                    </div>
+                                                                </div>
+                                                            </div>
+                                                        </>} */}
+                                                    </div>
+                                                        :
+                                                        elementType === 'floatbutton' ? <div style={{ height: '100%' }}>
+                                                            <div className={`widget ${imgTextButtonShow ? 'widget_back' : ''}`}>
+                                                                <div className="caption section">
+                                                                    <div className="caption-title">悬浮按钮</div>
+                                                                </div>
+                                                                <div className="form section">
+                                                                    <div className="form-caption">卡片设置</div>
+                                                                    <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                                        <div className="adui-form-label">转化按钮</div>
+                                                                        <div className="adui-form-control">
+                                                                            <div className='form-result-text'><span>{componentItem?.name}</span> <Button type="link" size='small' style={{ color: '#6b6b6b', fontSize: 12 }} onClick={() => { setImgTextButtonShow(true) }}>设置</Button> </div>
+                                                                        </div>
+                                                                    </div>
+                                                                    <div className="adui-form-item">
+                                                                        <div className="adui-form-label">卡片样式</div>
+                                                                        <div className="adui-form-control">
+                                                                            <Radio.Group onChange={(e) => { setGlobalComponentItem('wxad_styleType', e.target.value) }} className="floatType" value={wxad_styleType}>
+                                                                                <Radio.Button value='1'>
+                                                                                    <div className='floatTypeInner'>
+                                                                                        <i className="floatTypeAvatar"></i>
+                                                                                        <i className="floatTypeText_two"></i>
+                                                                                        <i className="floatTypeButton"></i>
+                                                                                    </div>
+                                                                                </Radio.Button>
+                                                                                <Radio.Button value='2'>
+                                                                                    <div className='floatTypeInner'>
+                                                                                        <i style={{ display: 'inline-block', height: 12 }}></i>
+                                                                                        <i className="floatTypeText_two"></i>
+                                                                                        <i className="floatTypeButton"></i>
+                                                                                    </div>
+                                                                                </Radio.Button>
+                                                                                <Radio.Button value='3'>
+                                                                                    <div className='floatTypeInner'>
+                                                                                        <i style={{ display: 'inline-block', height: 12 }}></i>
+                                                                                        <i className="floatTypeText_one"></i>
+                                                                                        <i className="floatTypeButton"></i>
+                                                                                    </div>
+                                                                                </Radio.Button>
+                                                                            </Radio.Group>
+                                                                        </div>
+                                                                    </div>
+                                                                </div>
+                                                                <div className="form section">
+                                                                    <div className="form-caption">内容设置</div>
+                                                                    {wxad_styleType === '1' && <div className="adui-form-item" style={{ alignItems: 'flex-start' }}>
+                                                                        <div className="adui-form-label">图片</div>
+                                                                        <div className="adui-form-control">
+                                                                            <div className={`upload-img-item ${iconUrl ? 'upload-img-item_uploaded' : ''}`}>
+                                                                                {
+                                                                                    iconUrl ? <div className="upload-img-item-inner" style={{ backgroundImage: `url(${iconUrl ? iconUrl : ""})` }}>
+                                                                                        <div className='upload-img-item-action' onClick={() => { setCcType(5); setSelectImgVisible(true); }}>
+                                                                                            <RetweetOutlined />
+                                                                                        </div>
+                                                                                    </div>
+                                                                                        :
+                                                                                        <div className="upload-img-item-inner" onClick={() => { setCcType(5); setSelectImgVisible(true); }}>
+                                                                                            <PlusOutlined />
+                                                                                        </div>
+                                                                                }
+                                                                            </div>
+                                                                            <div style={{ marginTop: 4, fontSize: 12, color: '#A3A3A3' }}>尺寸:96像素*96像素</div>
+                                                                            <div style={{ marginTop: 4, fontSize: 12, color: '#A3A3A3' }}>格式:不超过300k</div>
+                                                                        </div>
+                                                                    </div>}
+
+                                                                    <div className="adui-form-item" style={{ alignItems: 'flex-start', marginBottom: 10 }}>
+                                                                        <div className="adui-form-label" style={{ marginTop: 6 }}>标题</div>
+                                                                        <div className="adui-form-control">
+                                                                            <Input.TextArea placeholder="请输入标题" onChange={(e) => { setGlobalComponentItem('title', e.target.value?.replace(/\r|\n/ig, "")) }} value={title} showCount maxLength={10} autoSize />
+                                                                        </div>
+                                                                    </div>
+
+                                                                    {(wxad_styleType === '1' || wxad_styleType === '2') && <div className="adui-form-item" style={{ alignItems: 'flex-start', marginBottom: 10 }}>
+                                                                        <div className="adui-form-label" style={{ marginTop: 6 }}>描述</div>
+                                                                        <div className="adui-form-control">
+                                                                            <Input.TextArea placeholder="请输入描述" onChange={(e) => { setGlobalComponentItem('desc', e.target.value?.replace(/\r|\n/ig, "")) }} value={desc} showCount maxLength={14} autoSize />
+                                                                        </div>
+                                                                    </div>}
+                                                                    <div className="adui-form-item" style={{ alignItems: 'center', marginBottom: 10 }}>
+                                                                        <div className="adui-form-label" style={{ marginTop: 6 }}>标题字色</div>
+                                                                        <div className="adui-form-control">
+                                                                            <Space><ColorPicker onColor={(color: string) => { setGlobalComponentItem('titleColor', color) }} color={titleColor}></ColorPicker> <div className="colorShow">{titleColor}</div></Space>
+                                                                        </div>
+                                                                    </div>
+                                                                    {(wxad_styleType === '1' || wxad_styleType === '2') && <div className="adui-form-item" style={{ alignItems: 'center', marginBottom: 10 }}>
+                                                                        <div className="adui-form-label" style={{ marginTop: 6 }}>描述字色</div>
+                                                                        <div className="adui-form-control">
+                                                                            <Space><ColorPicker onColor={(color: string) => { setGlobalComponentItem('descColor', color) }} color={descColor}></ColorPicker> <div className="colorShow">{descColor}</div></Space>
+                                                                        </div>
+                                                                    </div>}
+                                                                </div>
+                                                                <div className="form section">
+                                                                    <div className="form-caption">更多设置</div>
+                                                                    <div className='adui-form-tip adui-form-tip_normal'>如果落地页只有一页,悬浮组件的更多设置必须为“进入页面时出现”、且“不消失”</div>
+                                                                    <div className="adui-form-item">
+                                                                        <div className="adui-form-label">出现方式</div>
+                                                                        <div className="adui-form-control">
+                                                                            <Radio.Group onChange={(e) => { setGlobalComponentItem('appearPaddingTop', e.target.value) }} value={Number(appearPaddingTop)}>
+                                                                                <Radio value={0}>进入页面时出现</Radio>
+                                                                                <Radio value={100}>滑动页面时出现</Radio>
+                                                                            </Radio.Group>
+                                                                        </div>
+                                                                    </div>
+                                                                    <div className="adui-form-item">
+                                                                        <div className="adui-form-label">消失方式</div>
+                                                                        <div className="adui-form-control">
+                                                                            <Radio.Group onChange={(e) => { setGlobalComponentItem('appearPaddingBottom', e.target.value) }} value={Number(appearPaddingBottom)}>
+                                                                                <Radio value={0}>不消失</Radio>
+                                                                                <Radio value={80}>滑至页面底部时消失</Radio>
+                                                                            </Radio.Group>
+                                                                        </div>
+                                                                    </div>
+                                                                </div>
+                                                            </div>
+                                                            {/* 设置转化按钮 imgTextButtonShow */}
+                                                            <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' }}>
+                                                                <div className='aside-nav'>
+                                                                    <Button type='link' icon={<SwapLeftOutlined />} onClick={() => { setImgTextButtonShow(false) }}>返回</Button>
+                                                                </div>
+                                                                <div className="form section">
+                                                                    <div className="form-caption">按钮类型</div>
+                                                                    <div className="adui-form-item" style={{ alignItems: 'center' }}>
+                                                                        <Select style={{ width: 180 }} className="aside-select" dropdownClassName="aside-select" onChange={(e) => { setGlobalComponentItem('componentItem', e) }} value={componentItem?.widgetTypeV2} size="small">
+                                                                            <Option value="gh"><FollowAcc />关注公众号</Option>
+                                                                            <Option value="enterprise_wx_auto"><WxAutoSvg />添加商家微信</Option>
+                                                                        </Select>
+                                                                    </div>
+                                                                </div>
+                                                                {componentItem?.widgetTypeV2 === 'gh' ? <>
+                                                                    <div className="form section">
+                                                                        <Space>
+                                                                            <Switch size="small" checked={componentItem?.subType === '17' ? true : false} onChange={(e) => { setGlobalComponentItem('componentItem', { ...componentItem, subType: e ? '17' : '1' }) }} />
+                                                                            一键关注
+                                                                            <Tooltip placement="top" title={'唤起公众号简介的半屏面板,点击其中按钮直接关注公众号'}>
+                                                                                <QuestionCircleOutlined />
+                                                                            </Tooltip>
+                                                                        </Space>
+                                                                    </div>
+                                                                </> : null}
+                                                                <div className="form section">
+                                                                    <div className="form-caption">按钮外观</div>
+                                                                    <div className="adui-form-item" style={{ marginBottom: 10 }}>
+                                                                        <div className="adui-form-label">按钮文案</div>
+                                                                        <div className="adui-form-control">
+                                                                            <div className="fl-sb">
+                                                                                <div style={{ display: 'flex', justifyContent: 'flex-start', alignItems: 'center' }}>
+                                                                                    <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', border: '1px solid #d9d9d9', borderRadius: 2, paddingRight: 8 }}>
+                                                                                        <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>
+                                                                                    </div>
+                                                                                </div>
+                                                                                <Radio.Group onChange={(e) => { setGlobalComponentItem('componentItem', { ...componentItem, btnFontType: e.target.value }) }} value={componentItem?.btnFontType}>
+                                                                                    <Radio.Button value={0}>常规</Radio.Button>
+                                                                                    <Radio.Button value={1}>加粗</Radio.Button>
+                                                                                </Radio.Group>
+                                                                            </div>
+                                                                        </div>
+                                                                    </div>
+                                                                    <div className="adui-form-item" style={{ alignItems: 'center', marginBottom: 10 }}>
+                                                                        <div className="adui-form-label">字体色</div>
+                                                                        <div className="adui-form-control">
+                                                                            <Space><ColorPicker onColor={(color: string) => { setGlobalComponentItem('componentItem', { ...componentItem, fontColor: color }) }} color={componentItem?.fontColor}></ColorPicker><div className="colorShow">{componentItem?.fontColor}</div></Space>
+                                                                        </div>
+                                                                    </div>
+                                                                    <div className="adui-form-item" style={{ alignItems: 'center', marginBottom: 10 }}>
+                                                                        <div className="adui-form-label">填充色</div>
+                                                                        <div className="adui-form-control">
+                                                                            <Space><ColorPicker onColor={(color: string) => { setGlobalComponentItem('componentItem', { ...componentItem, btnBgColorTheme: color }) }} color={componentItem?.btnBgColorTheme}></ColorPicker><div className="colorShow">{componentItem?.btnBgColorTheme}</div></Space>
+                                                                        </div>
+                                                                    </div>
+                                                                </div>
+                                                            </div>
+                                                        </div> :
+                                                            null
+                }
+
+            </>
+        } else {
+            return null
+        }
+    }
+    /** 选择单张图片 */
+    const clickUpdateImg = useCallback((num: number) => {
+        setSliderImgContent([])
+        setCcType(1)
+        setSelectImgVisible(true)
+    }, [imgSize, scType, content, sliderImgContent])
+
+    /** 选择视频 */
+    const clickUpdateVideo = useCallback(() => {
+        setCcType(2)
+        setSelectImgVisible(true)
+    }, [imgSize, scType])
+
+    /** 弹窗返回设置图片 */
+    const setImg = useCallback((value: any[]) => {
+        setSelectImgVisible(false)
+        let newContent = JSON.parse(JSON.stringify(content))
+        let selectIndex = newContent?.findIndex((item: Content) => item.comptActive)
+        let oldContent = newContent
+        if (scType === 1) {  // 图片
+            if (selectIndex !== -1) {
+                let selectCon = oldContent[selectIndex]
+                selectCon['imageUrl'] = value[0]?.url
+                selectCon['width'] = Number(value[0]?.width)
+                selectCon['height'] = Number(value[0]?.height)
+                oldContent[selectIndex] = { ...selectCon }
+                dispatch({ type: 'setCon', params: { content: [...oldContent] } })
+            }
+        } else if (scType === 2) { // 视频要判断是否是长视频 还是短视频
+            if (selectIndex !== -1) {
+                let selectCon = oldContent[selectIndex]
+                selectCon['width'] = value[0]?.width
+                selectCon['height'] = value[0]?.height
+                selectCon['videoUrl'] = value[0]?.url
+                oldContent[selectIndex] = { ...selectCon }
+                dispatch({ type: 'setCon', params: { content: [...oldContent] } })
+            }
+        } else if (scType === 3) { // 轮播
+            if (selectIndex !== -1) {
+                let urlList = value?.map(item => item?.url)
+                let selectCon = oldContent[selectIndex]
+                selectCon.imageUrlList = selectCon.imageUrlList?.reduce((prev: any[], cur: any, index: number) => {
+                    prev.push(urlList[index] || "")
+                    return prev
+                }, [])
+                oldContent[selectIndex] = { ...selectCon }
+                dispatch({ type: 'setCon', params: { content: [...oldContent] } })
+            }
+        } else if (scType === 4) { // 设置图文复合组件图片
+            if (selectIndex !== -1) {
+                let selectCon = oldContent[selectIndex]
+                if (selectCon?.type === '104') {
+                    let componentItem = selectCon?.layoutItems?.componentItem[0]?.layoutItems?.componentItem
+                    componentItem[0] = {
+                        ...componentItem[0],
+                        pureImageUrl: value[0]?.content
+                    }
+                    oldContent[selectIndex] = { ...selectCon }
+                    dispatch({ type: 'setCon', params: { content: [...oldContent] } })
+                } else if (selectCon?.type === '103') {
+                    let componentItem = selectCon?.layoutItems.componentItem[goodsCount]
+                    let shelfnewItem = componentItem?.layoutItems?.componentItem[0]?.layoutItems?.componentItem
+                    shelfnewItem[0].pureImageUrl = value[0]?.content
+                    oldContent[selectIndex] = { ...selectCon }
+                    dispatch({ type: 'setCon', params: { content: [...oldContent] } })
+                }
+            }
+        } else if (scType === 5) { // 设置悬浮组件 
+            if (selectIndex !== -1) {
+                let selectCon = oldContent[selectIndex]
+                selectCon['iconUrl'] = value[0]?.content
+                dispatchGlobal({ type: 'setConItem', params: { componentItem: JSON.parse(JSON.stringify(oldContent)) } })
+            }
+        }
+    }, [content, scType, goodsCount])
+    /** 回填数据 */
+    const editSelectImg = useCallback((url: any[]) => {
+        setSelectImgVisible(true)
+    }, [selectImgVisible])
+
+    /** 下一步 */
+    const nextHandle = useCallback(() => {
+        if (content.length === 1) {
+            message.error('请完善内容')
+            return
+        }
+        if (((content[0]?.elementType === "TOP_IMAGE") && !content[0].imageUrl) || content[0]?.elementType === "empty") {
+            message.error('请完善顶部组件内容')
+            return
+        }
+        if (content[0]?.elementType === "TOP_SLIDER" && !content[0].imageUrlList?.every((item: string) => item)) {
+            message.error('请完善轮播图组件内容~~')
+            return
+        }
+        if (content[0]?.elementType === "TOP_VIDEO" && !content[0].videoUrl) {
+            message.error('请完善顶部视频组件内容~~')
+            return
+        }
+        let reg = /(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?/;
+        let a = content?.every((item: any) => {
+            if (item?.elementType === "GH") {
+                if (item?.btnTitle) {
+                    return true
+                } else {
+                    message.error('关注公众号按钮按钮文案未填写')
+                    return false
+                }
+            } else if (item?.elementType === "link") {
+                if (!reg.test(item?.origBtnJumpUrl)) {
+                    message.error('跳转链接按钮请输入正确链接')
+                    return false
+                }
+                if (item?.btnTitle) {
+                    return true
+                } else {
+                    message.error('关注公众号按钮按钮文案未填写')
+                    return false
+                }
+            } else if (item?.elementType === "IMAGE") {
+                if (item.imageUrl) {
+                    return true
+                } else {
+                    message.error('请选择图片')
+                    return false
+                }
+            } else if (item?.elementType === "TEXT") {
+                if (item.text) {
+                    return true
+                } else {
+                    message.error('请完善文本内容')
+                    return false
+                }
+            } else if (item?.elementType === "ENTERPRISE_WX") {
+                if (item?.btnTitle) {
+                    return true
+                } else {
+                    message.error('客服组按钮按钮文案未填写')
+                    return false
+                }
+            } else if (item?.elementType === "shelfnew") {
+                return true
+            } else {
+                return true
+            }
+        })
+        if (componentItem?.length > 0) {
+            let b = componentItem?.every((item: any) => {
+                if (item?.elementType === "floatbutton") {
+                    if (item?.wxad_styleType === '1') {
+                        if (!item?.iconUrl) {
+                            message.error('悬浮组件请上传图片')
+                            return false
+                        }
+                        if (!item?.title) {
+                            message.error('悬浮组件请输入标题')
+                            return false
+                        }
+                        if (!item?.desc) {
+                            message.error('悬浮组件请输入描述')
+                            return false
+                        }
+                    } else if (item?.wxad_styleType === '2') {
+                        if (!item?.title) {
+                            message.error('悬浮组件请输入标题')
+                            return false
+                        }
+                        if (!item?.desc) {
+                            message.error('悬浮组件请输入描述')
+                            return false
+                        }
+                    } else if (item?.wxad_styleType === '3') {
+                        if (!item?.title) {
+                            message.error('悬浮组件请输入标题')
+                            return false
+                        }
+                    }
+                    if (item?.componentItem?.widgetTypeV2 === 'enterprise_wx_auto') {
+                        if (item?.componentItem?.custorData && item?.componentItem?.custorData?.length > 0) {
+                            return true
+                        } else {
+                            message.error('请完善悬浮组件客服组内容')
+                            return false
+                        }
+                    }
+                }
+                return true
+            })
+            if (b) {
+
+            } else {
+                return
+            }
+        }
+        if (a) {
+            setLastVisible(true)
+        }
+
+    }, [lastVisible, state, componentItem])
+
+    /** 保存 */
+    const saveHandle = useCallback(() => {
+        let { content, pageBackColor } = state
+        if (!shareTittle || !shareDesc) {
+            message.error('请填写分享标题或者描述')
+            return
+        }
+        let layoutName: string = pageName
+        if (!pageName) {
+            layoutName = `${id ? '复制' : ''}原生推广页` + moment().format("YYYYMMDDHHmmss") + '_' + currentUser?.userId
+        }
+
+        let pageContextList: Array<ImgProps | TopImg | TopVideo | TopSlider | Text | GhButton> = content?.map((item: { elementType: string, comptActive: boolean, activeIndex: boolean }) => {
+            let { elementType, comptActive, activeIndex, ...data } = item
+            let typeKey = getTypeKey(elementType)
+            let newItem = { elementType }
+            newItem[typeKey] = data
+            return newItem
+        })
+
+        let pageSpecs = {
+            bgColor: pageBackColor,
+            pageElementsSpecList: pageContextList
+        }
+
+        let params = {
+            mediaType: 'PAGE',
+            folder: false,
+            parentId, title:
+                layoutName,
+            pageName: layoutName,
+            belongUser: belongUser === '0' ? false : true,
+            sort,
+            pageSpecsList: [pageSpecs],
+            globalSpec: null,
+            shareContentSpec: {
+                shareTitle: shareTittle,
+                shareDescription: shareDesc
+            }
+        }
+        add.run(params).then(res => {
+            if (res) {
+                ajax.refresh()
+                hideModal && hideModal()
+            }
+        })
+    }, [state, shareTittle, pageName, parentId, sort, shareDesc, belongUser, currentUser, ajax, id, componentItem])
+
+    return <Drawer
+        title={
+            <div className={style.drawerTitle}>
+                <div>
+                    <Space>
+                        <span>{id ? '复制' : '创建'}推广页</span>
+                    </Space>
+                </div>
+                <Button type='primary' size='small' className={style.next} onClick={() => { nextHandle() }}>下一步 <SwapRightOutlined /></Button>
+            </div>
+        }
+        placement="right"
+        onClose={() => {
+            modal.confirm(config);
+        }}
+        visible={visible}
+        width='90%'
+        className={`addDraw ${style.drawer}`}
+    >
+        {/* 选择素材 */}
+        {selectImgVisible && <SelectCloud visible={selectImgVisible} sliderImgContent={sliderImgContent} onClose={() => setSelectImgVisible(false)} onChange={setImg} />}
+
+        <Modal
+            title={<>
+                <div style={{ marginBottom: 2, color: '#1f1f1f' }}>分享设置</div>
+                <div style={{ color: '#a3a3a3', fontSize: 12 }}>设置推广页的分享样式</div>
+            </>}
+            visible={lastVisible}
+            confirmLoading={add.loading}
+            onOk={saveHandle}
+            onCancel={() => { setLastVisible(false) }}
+        >
+            <Form labelCol={{ span: 4 }}>
+                <Form.Item label="落地页名称">
+                    <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>
+                </Form.Item>
+                <Form.Item label="分享标题">
+                    <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>
+                </Form.Item>
+                <Form.Item label="分享描述">
+                    <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>
+                </Form.Item>
+                <Form.Item label="排序" tooltip="值越大越靠前">
+                    <InputNumber placeholder='输入排序' min={0} value={sort} onChange={(e) => { setSort(e) }} />
+                </Form.Item>
+            </Form>
+        </Modal>
+
+        <Spin spinning={get.loading}>
+            <div className={style.boxCont}>
+                <Row className={style.row}>
+                    <Col flex="320px" className={style.right}>
+                        <div className={style.title}>顶部组件</div>
+                        <div className={style.assembly}>
+                            {
+                                content[0].elementType === 'empty' ? <>
+                                    <div {...getDragProps(`TOP_IMAGE`)}> <Topimg /> <span>图片</span> </div>
+                                    <div {...getDragProps(`TOP_SLIDER`)}> <Topslider /> <span>轮播图</span> </div>
+                                    <div {...getDragProps(`TOP_VIDEO`)}> <Topvideo /> <span>视频</span></div>
+                                </>
+                                    :
+                                    <>
+                                        <div className={style.disabled}> <Topimg /> <span>图片</span> </div>
+                                        <div className={style.disabled}> <Topslider /> <span>轮播图</span> </div>
+                                        <div className={style.disabled}> <Topvideo /> <span>视频</span></div>
+                                    </>
+                            }
+                        </div>
+
+                        <div className={style.title}>基础组件</div>
+                        <div className={style.assembly}>
+                            <div {...getDragPropsCon(`IMAGE`)}> <Img /> <span className="my">图片</span> </div>
+                            <div {...getDragPropsCon(`TEXT`)}> <MyText /> <span>文字</span> </div>
+                        </div>
+                        <div className={style.title}>转化按钮</div>
+                        <div className={style.assembly}>
+                            <div {...getDragPropsCon(`GH`)}> <FollowAcc /> <span className="my">关注公众号</span> </div>
+                            {/* <div {...getDragPropsCon(`JumpLink`)}> <JumpLink /> <span className="my">跳转链接</span> </div> */}
+                            <div {...getDragPropsCon(`ENTERPRISE_WX`)}> <WxAutoSvg /> <span className="my">添加商家微信</span> </div>
+                        </div>
+                        {/* <div className={style.title}>营销组件</div>
+                    <div className={style.assembly}>
+                        <div {...getDragPropsCon(`shelfnew`)}> <ImgText /> <span className="my">图文复合组件</span> </div>
+                        {componentItem?.some((item: { elementType: string }) => item.elementType === 'FLOAT_BUTTON') ?
+                            <div className={style.disabled}> <FloatbuttonSvg /> <span>悬浮组件</span></div> : <div {...getDragPropsCon(`FLOAT_BUTTON`)}> <FloatbuttonSvg /> <span className="my">悬浮组件</span> </div>
+                        }
+                    </div> */}
+                    </Col>
+                    <Col flex="auto" className={style.center} onClick={installActiveNull}>
+
+                        <div className={style.page} style={{ backgroundColor: pageBackColor || '#FFFFFF' }}>
+                            {/* 头部 */}
+                            <div>{topCon}</div>
+                            {/* 内容*/}
+                            <div className={`comptPlaceholder lastChild`} id="comptCon">
+                                {comptCon()}
+                            </div>
+                            <div className={style.sidebar}>
+                                <div>
+                                    <ColorPicker onColor={(color: string) => { dispatch({ type: 'setPageBackColor', params: { pageBackColor: color } }) }} color={pageBackColor}></ColorPicker>
+                                    <div style={{ marginTop: 4 }}>背景</div>
+                                </div>
+                            </div>
+                        </div>
+
+                    </Col>
+                    <Col flex="380px" className={style.left}>{rightCon()}</Col>
+                </Row>
+            </div>
+        </Spin>
+    </Drawer>
+}
+export default React.memo(AddLandingPage)

+ 988 - 0
src/pages/launchSystemNew/components/addLandingPage/index1.less

@@ -0,0 +1,988 @@
+.widget {
+  box-sizing: border-box;
+  // padding: 15px 0;
+  background-color: #fff;
+  box-shadow: -1px 0 0 0 hsl(0deg 0% 87% / 65%);
+  width: 100%;
+  height: 100%;
+  position: relative;
+  overflow-y: scroll;
+  font-size: 12px;
+  transition: all .35s ease;
+}
+
+.aside .form,
+.widget .form {
+  padding-top: 28px;
+  padding-bottom: 28px;
+  height: auto;
+
+  .adui-form-item {
+    margin-bottom: 20px;
+  }
+
+  .adui-form-label,
+  .adui-form-tip {
+    font-size: 12px;
+    line-height: 18px;
+  }
+
+  .adui-form-label {
+    width: 4em;
+  }
+
+  .adui-form-control {
+    flex: 1;
+  }
+
+  .ant-radio-wrapper {
+    font-size: 12px;
+  }
+}
+
+.widget_back {
+  opacity: 0;
+  transform: translateX(-30px);
+}
+
+.caption {
+  padding-top: 20px;
+  padding-bottom: 20px;
+}
+
+.section {
+  padding-left: 28px;
+  padding-right: 28px;
+  box-shadow: 0 1px 0 0 hsl(0deg, 0%, 87%, 65%);
+}
+
+.goBack {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  color: #000;
+  cursor: pointer;
+}
+
+.caption-title {
+  font-size: 14px;
+  line-height: 24px;
+  color: #1f1f1f;
+  font-weight: 600;
+}
+
+.adui-form-label,
+.adui-form-tip {
+  font-size: var(--font-size-small);
+}
+
+.adui-form-tip_normal {
+  color: #a3a3a3;
+}
+
+.adui-form-tip:not(:empty) {
+  margin-top: 8px;
+}
+
+.adui-form-label {
+  flex: none;
+  display: inline-flex;
+  align-items: center;
+  margin-right: 10px;
+  color: var(--gray-900);
+  white-space: nowrap;
+}
+
+.adui-form-item {
+  display: flex;
+  margin-bottom: 24px;
+  align-items: baseline;
+}
+
+.form-caption {
+  margin-bottom: 16px;
+  font-size: 13px;
+  line-height: 20px;
+  color: #1f1f1f;
+  font-weight: 600;
+}
+
+.upload-img-item {
+  width: 70px;
+  height: 70px;
+  cursor: pointer;
+
+}
+
+.upload-img-item-inner {
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: #fdfdfd;
+  border: 1px dashed #e3e3e3;
+  color: #919191;
+  border-radius: 4px;
+  background-size: cover;
+  background-position: 50% 50%;
+
+  &::before {
+    content: "";
+    padding-top: 100%;
+  }
+
+  &::after {
+    content: "";
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background-color: rgba(0, 0, 0, .1);
+    opacity: 0;
+    border-radius: 4px;
+    visibility: hidden;
+    transition: all .1s ease;
+  }
+
+  &:hover {
+    background-color: #fafafa;
+    color: #363636;
+    border: 1px dashed #d6d6d6;
+
+    & .upload-img-item-action {
+      opacity: 1;
+      visibility: visible;
+    }
+
+    & .sliderhandle {
+      opacity: 1;
+      visibility: visible;
+    }
+  }
+}
+
+.upload-img-item_uploaded {
+  .upload-img-item-inner {
+    background-clip: content-box;
+    border: 2px solid transparent;
+    border-radius: 6px;
+  }
+}
+
+.sliderhandle {
+  position: absolute;
+  z-index: 1;
+  top: 4px;
+  left: 4px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 16px;
+  height: 16px;
+  font-size: 12px;
+  line-height: 12px;
+  color: #d6d6d6;
+  font-weight: 600;
+  background-color: rgba(0, 0, 0, .6);
+  border-radius: 100%;
+  // opacity: 0;
+  // visibility: hidden;
+  cursor: pointer;
+  transform: scale(1.2);
+  transform-origin: 80% 0;
+}
+
+.upload-img-item-action {
+  position: absolute;
+  z-index: 1;
+  top: 4px;
+  right: 4px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 16px;
+  height: 16px;
+  font-size: 12px;
+  line-height: 12px;
+  color: #d6d6d6;
+  font-weight: 600;
+  background-color: rgba(0, 0, 0, .6);
+  border-radius: 100%;
+  opacity: 0;
+  visibility: hidden;
+  cursor: pointer;
+  transform: scale(1.2);
+  transform-origin: 80% 0;
+}
+
+.imageUpload {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  padding: 4px;
+  border-radius: 6px;
+  box-shadow: 0 0 0 1px hsl(0deg, 0%, 87%, 45%);
+  list-style-type: none;
+  margin: 0;
+}
+
+.imageUploadItem {
+  display: inline-block;
+  vertical-align: top;
+  padding: 4px;
+  background-color: transparent;
+  border-radius: 4px;
+
+  &.active .upload-img-item-inner {
+    border: 1px dashed #1890ff;
+  }
+}
+
+.imageUploadItemInnerDone {
+  &.active .upload-img-item-inner {
+    border: 1px solid #1890ff;
+  }
+}
+
+.addDraw {
+  .ant-drawer-body {
+    padding-right: 0;
+
+    >.ant-spin-nested-loading {
+      width: 100%;
+      height: 100%;
+
+      >.ant-spin-container {
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
+
+  .fl-sb {
+    justify-content: space-between;
+  }
+
+  .fl-center,
+  .fl-sb {
+    display: flex;
+    align-items: center;
+  }
+
+  .fl-sb>div .swatch {
+    margin-top: 4px !important;
+  }
+}
+
+
+
+.compt {
+  position: relative;
+  z-index: 1000;
+
+  &:hover {
+    z-index: 1002;
+  }
+
+  &.comptActive {
+    position: relative;
+    z-index: 1002;
+
+    .componentWrap {
+      z-index: 2;
+    }
+  }
+
+  &.comptFixedBottom {
+    position: absolute;
+    z-index: 2;
+    bottom: 0;
+    width: 375px;
+    text-align: left;
+  }
+
+  &:hover .comptEditBtnsInner {
+    opacity: 1;
+    visibility: visible;
+  }
+
+  &:hover .componentWrap {
+    z-index: 2;
+
+    &::after {
+      box-shadow: 0 0 0 1px #1890ff;
+      display: block;
+    }
+  }
+
+  &:hover .comptEditTrBtns {
+    display: block;
+  }
+
+  &.comptActive .componentWrap {
+    z-index: 2;
+    box-shadow: 0 0 0 2px #1890ff;
+    display: block;
+  }
+}
+
+
+.topComptArea {
+  align-items: center;
+  justify-content: center;
+  margin: 8px;
+  height: 240px;
+  z-index: 2;
+  font-size: 16px;
+  color: rgba(0, 0, 0, .58);
+  letter-spacing: 0;
+  line-height: 22px;
+  text-align: center;
+  background: #fafafa;
+  border: 1.5px dashed rgba(0, 0, 0, .06);
+
+  svg {
+    margin-top: 35px;
+  }
+
+  .desc {
+    font-size: 12px;
+    line-height: 20px;
+    color: rgba(0, 0, 0, .36);
+  }
+
+  .topAreaTitle {
+    margin: 0;
+    margin-top: 4px;
+    font-size: 14px;
+    font-weight: 600;
+    line-height: 24px;
+    text-align: center;
+    color: rgba(0, 0, 0, .58);
+  }
+
+  &.dragging {
+    padding: 0;
+    background-color: #e4f2ff;
+    border: 1px dashed #1890ff;
+  }
+}
+
+.uiFlexCenter {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+}
+
+.uiFlexAlignCenter {
+  -ms-flex-pack: center;
+  justify-content: center;
+}
+
+.comptPlaceholder {
+  position: relative;
+  height: 0;
+  list-style-type: none;
+  padding: 0;
+  border-radius: 0;
+  margin: 0;
+  font-weight: 700;
+  font-size: 14px;
+  // color: #1890ff;
+  letter-spacing: 0;
+  text-align: center;
+  display: flex;
+  flex-direction: column;
+
+  &>div.page-0 {
+    min-height: 420px;
+    position: relative;
+    display: flex;
+    flex-direction: column;
+  }
+
+  .comptCon {
+    // min-height: 40px;
+    display: flex;
+    align-items: center;
+    font-size: 14px;
+    justify-content: center;
+    font-weight: 600;
+
+    &.draggingCon {
+      min-height: 100px;
+      border: 1px dashed rgba(0, 0, 0, 0.2);
+      color: rgba(0, 0, 0, 0.2);
+      flex: 1;
+    }
+
+    &.draggingCon1 {
+      min-height: 60px;
+      border: 1px dashed rgba(0, 0, 0, 0.2);
+      color: rgba(0, 0, 0, 0.2);
+    }
+
+    &.hovering {
+      border: 1px dashed #1890ff;
+      color: #1890ff;
+    }
+  }
+}
+
+.comptPlaceholder.lastChild,
+.pageEmpty .comptPlaceholder {
+  flex: 1;
+  height: auto;
+}
+
+.default {
+  width: 100%;
+  height: 100%;
+  background: #fafafa;
+  overflow: hidden;
+}
+
+.defaultIcon {
+  width: 56px;
+  height: 56px;
+  display: block;
+  margin: 0 auto;
+  color: #b0b0b0;
+
+  >svg {
+    width: 100%;
+    height: 100%;
+    fill: currentColor;
+  }
+}
+
+.form-result-text {
+  color: #1f1f1f;
+  line-height: 26px;
+  font-size: 12px;
+
+  &>span {
+    margin-right: 10px;
+    position: relative;
+
+    &::before {
+      content: "";
+      position: absolute;
+      top: 50%;
+      right: -10px;
+      margin-top: -6px;
+      width: 1px;
+      height: 12px;
+      background-color: #ebebeb;
+    }
+  }
+}
+
+.componentWrap {
+  position: relative;
+  z-index: 1;
+
+  &::after {
+    content: "";
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    z-index: 1;
+    box-shadow: 0 0 0 2px #1890ff;
+    display: none;
+  }
+
+  .textAreaDiv {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: #fff;
+  }
+
+  textarea {
+    outline: 0;
+    padding: 0;
+    color: #666;
+    font-family: inherit;
+    font-size: 100%;
+    margin: 0;
+    overflow: auto;
+    vertical-align: top;
+    resize: none;
+  }
+
+  .textarea {
+    position: absolute;
+    z-index: 2;
+    left: 0;
+    border: none;
+    width: 100%;
+    height: 100%;
+    background: transparent;
+    overflow: hidden;
+  }
+}
+
+.componentContent {
+  overflow: hidden;
+
+  .sliderCon {
+    position: relative;
+    width: 375px;
+    height: 222px;
+
+    >.default {
+      width: 375px;
+      height: 100%;
+      position: absolute;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+  }
+
+  .sliderD {
+    position: absolute;
+    bottom: 10px;
+    width: 100%;
+    display: flex;
+    justify-content: center;
+    z-index: 2;
+
+    >i {
+      display: inline-block;
+      vertical-align: middle;
+      width: 6px;
+      height: 6px;
+      border-radius: 3px;
+      background-color: rgba(0, 0, 0, 0.15);
+      margin-left: 0px;
+
+      &:not(:nth-child(1)) {
+        margin-left: 10px;
+      }
+    }
+  }
+}
+
+.comptUpload {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  z-index: 2;
+}
+
+.comptEditButton {
+  padding: 12px 34px;
+  font-size: 13px;
+  color: #6b6b6b;
+  letter-spacing: 0;
+  line-height: 22px;
+  border: 1px dashed #d6d6d6;
+  border-radius: 3px;
+  display: block;
+  margin-left: auto;
+  margin-right: auto;
+  cursor: pointer;
+
+  &:hover {
+    color: #292929;
+    background: rgba(0, 0, 0, .03);
+    border: 1px dashed #a3a3a3;
+    border-radius: 3px;
+  }
+}
+
+.shelfItemContent {
+  .title {
+    font-weight: 700;
+    font-size: 14px;
+    color: #353535;
+    letter-spacing: 0;
+  }
+
+  .desc {
+    font-size: 13px;
+    color: #4d4d4d;
+    letter-spacing: 0;
+    font-weight: normal;
+  }
+
+  .btn {
+    display: inline-block;
+    background: #07c160;
+    width: 77px;
+    height: 30px;
+    line-height: 30px;
+    font-size: 13px;
+    color: #fff;
+    text-align: center;
+    box-sizing: content-box;
+  }
+}
+
+.shelf.listType {
+  flex-direction: column;
+
+  p {
+    margin: 0;
+    text-align: left;
+  }
+
+  .shelfItem {
+    height: 114px;
+    border-radius: 4px;
+  }
+
+  .shelfItemImg {
+    width: 90px;
+    height: 90px;
+  }
+
+  .shelfItemContent {
+    // flex: 1;
+    width: calc(100% - 90px);
+    height: 100%;
+    min-width: 0;
+    text-align: left;
+
+    .title {
+      white-space: nowrap;
+      overflow: hidden;
+    }
+
+    .desc {
+      height: 35px;
+      overflow: hidden;
+    }
+  }
+}
+
+.shelfItem-3q {
+  display: flex;
+}
+
+.shelf.gridType {
+  display: flex;
+  flex-wrap: wrap;
+
+  .shelfItem-3q {
+    flex-direction: column;
+    width: 160px;
+    border-radius: 4px;
+    box-sizing: content-box;
+  }
+
+  .shelfItemImg {
+    width: 150px;
+    height: 150px;
+    margin-bottom: 8px;
+  }
+}
+
+.shelfItem {
+  display: flex;
+}
+
+.comptEditBtns {
+  position: absolute;
+  top: 0;
+  right: -35px;
+  width: 34px;
+  z-index: 1;
+  box-sizing: inherit;
+}
+
+.comptEditBtnsInner {
+  opacity: 0;
+  visibility: hidden;
+  background-color: #fff;
+  transition: all .1s ease;
+  border-radius: 0 6px 6px 0;
+  box-shadow: 0 0 0 1px hsl(0deg, 0%, 87%, 20%), 0 3px 6px 0 rgb(0, 0, 0, 4%);
+  overflow: hidden;
+
+  button {
+    position: relative;
+    display: inline-block;
+    font-weight: 500;
+    color: var(--gray-800);
+    background-color: transparent;
+    padding: 0 4px;
+    margin: 4px;
+    border-radius: 4px;
+    text-decoration: none;
+    outline: none;
+    appearance: none;
+    cursor: pointer;
+    user-select: none;
+    border: none;
+    transition: color var(--motion-duration-fast) var(--ease-in-out), background-color var(--motion-duration-fast) var(--ease-in-out), background-image var(--motion-duration-fast) var(--ease-in-out), box-shadow var(--motion-duration-fast) var(--ease-in-out);
+  }
+}
+
+.text {
+  word-wrap: break-word;
+}
+
+.shelfnewColor {
+  &>div {
+    .colorName {
+      text-align: center;
+      color: rgb(99, 99, 99);
+      font-size: 12px;
+    }
+  }
+}
+
+.colorShow {
+  box-shadow: 0 0 0 1px #e0e0e0;
+  padding: 4px 8px;
+  font-size: 12px;
+  border-radius: 4px;
+  width: 70px;
+}
+
+.comptEditTrBtns {
+  position: absolute;
+  top: 0;
+  right: 0;
+  z-index: 2;
+  display: none;
+  background-color: rgba(0, 0, 0, 0.3);
+
+  .comptEditTrBtnsInner {
+    display: flex;
+    justify-content: flex-end;
+
+    &>a {
+      font-size: 12px;
+      padding: 0 5px;
+
+      &:hover {
+        box-shadow: 0 0 0 1px #1890ff;
+      }
+    }
+
+    .ant-popover-inner-content {
+      padding: 3px !important;
+    }
+  }
+}
+
+.assBts {
+  display: flex;
+
+  >div {
+    width: 24px;
+    height: 24px;
+    cursor: pointer;
+    border-radius: 4px;
+
+    &:hover {
+      box-shadow: 0 0 0 1px #1890ff;
+    }
+
+    >svg {
+      width: 24px;
+      height: 24px;
+    }
+  }
+}
+
+.text {
+  word-wrap: break-word;
+}
+
+.videoPlay {
+  position: relative;
+
+  >span {
+    z-index: 10;
+    background-image: url('../../../../../public/MpaVideoIcon.png');
+    margin-top: -23px;
+    margin-left: -23px;
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    width: 46px;
+    height: 46px;
+    background-size: 46px;
+  }
+}
+
+.floatButtonWrapper {
+  padding: 8px;
+  background-color: #fff;
+}
+
+.floatButton {
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex-align: center;
+  align-items: center;
+  padding-left: 16px;
+  padding-right: 20px;
+  height: 72px;
+  background-color: hsla(0, 0%, 94%, .96);
+  border-radius: 8px;
+}
+
+.floatButtonAvatarPlaceholder {
+  background-color: #c7c7c7;
+}
+
+.floatButtonAvatar,
+.floatButtonAvatarPlaceholder {
+  flex: none;
+  margin-right: 12px;
+  width: 48px;
+  height: 48px;
+  border-radius: 4px;
+}
+
+.floatButtonTexts {
+  flex: 1;
+  min-width: 0;
+}
+
+.floatButtonTitle {
+  font-size: 16px;
+  line-height: 22px;
+  color: #1f1f1f;
+  font-weight: 600;
+}
+
+.floatButtonDesc {
+  margin-top: 1px;
+  font-size: 12px;
+  line-height: 17px;
+  color: #a3a3a3;
+}
+
+.floatButtonDesc,
+.floatButtonTitle {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.floatButtonLink {
+  margin-left: 12px;
+  padding-left: 4px;
+  padding-right: 4px;
+  font-size: 14px;
+  line-height: 32px;
+  color: #fff;
+  font-weight: 500;
+  background-color: #07c160;
+  border-radius: 4px;
+}
+
+.floatType {
+  >.ant-radio-button-wrapper {
+    border-radius: 0;
+    padding: 4.5px 6px;
+    margin-right: 5px;
+  }
+}
+
+.floatTypeInner {
+  width: 59px;
+  display: flex;
+  align-items: center;
+  padding-left: 4px;
+  padding-right: 4px;
+  height: 20px;
+  background-color: hsla(0, 0%, 42%, .25);
+}
+
+.floatTypeAvatar {
+  flex: none;
+  margin-right: 2px;
+  width: 12px;
+  height: 12px;
+  background-color: #6b6b6b;
+}
+
+.floatTypeText_one,
+.floatTypeText_two {
+  flex: 1;
+
+  &::before {
+    display: block;
+    opacity: .23;
+    height: 2px;
+    background-color: #6b6b6b;
+  }
+
+  &::after {
+    display: block;
+    opacity: .23;
+    height: 2px;
+    background-color: #6b6b6b;
+  }
+}
+
+.floatTypeText_one::before,
+.floatTypeText_two::before {
+  content: "";
+}
+
+.floatTypeText_two::after {
+  content: "";
+  margin-top: 2px;
+  width: 70.58824%;
+}
+
+.floatTypeButton {
+  margin-left: 2px;
+  width: 12px;
+  height: 4px;
+  background-color: #6b6b6b;
+}
+
+.aside {
+  position: absolute;
+  top: 0;
+  background-color: #fff;
+  z-index: 10;
+  width: 100%;
+  height: 100%;
+  overflow-y: auto;
+}
+
+.aside-nav {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 16px 16px 16px 12px;
+  box-shadow: 0 1px 0 0 rgba(222, 222, 222, 0.65);
+
+  >button {
+    color: #6b6b6b;
+    font-size: 12px;
+  }
+}
+
+.aside-select {
+  span.ant-select-selection-item {
+    display: flex;
+    align-items: center;
+  }
+
+  .ant-select-item-option-content {
+    display: flex;
+    align-items: center;
+  }
+}
+
+.custorGroup {
+  flex-direction: column;
+}

+ 69 - 0
src/pages/launchSystemNew/components/addLandingPage/inputAcc.tsx

@@ -0,0 +1,69 @@
+import { Button, Dropdown, Menu } from "antd";
+import React, { useEffect, useState } from "react";
+import style from './index.less'
+import SelectAccount from "./selectAccount";
+import { getChannelName } from '@/utils/utils'
+
+type Props = {
+    onChange: (value: { MP?: any, ADQ?: any, id: number, accountName: string }[]) => void,
+    value: { MP?: any, ADQ?: any, id: number, accountName: string }[]
+}
+
+function InputAcc(props: Props) {
+    const { value = [], onChange } = props
+
+    const [selectAccShow, setSelectAccShow] = useState<boolean>(false)
+    const [data, setData] = useState<{ title: String, num: Number }>({ title: '', num: 0 })
+
+    useEffect(() => {
+        if (value.length > 0) {
+            let num = 0
+            let title = ""
+            for (const iterator of value) {
+                if (iterator?.MP?.length > 0) {
+                    title = getChannelName(iterator.accountName) + '-' + iterator?.MP[0].uname
+                    break
+                }
+            }
+            value?.forEach((item: any) => {
+                if(item?.MP?.length > 0) {
+                    num += item?.MP?.length
+                }
+            })
+            setData(() => ({ title, num: num !==0 ? num - 1 : 0 }))
+        }
+    }, [value])
+
+    const menu = () => {
+        return <Menu style={{ width: 220, borderRadius: 4 }}>
+            {value?.map((item: { id: number, accountName: string, MP?: any[], ADQ?: any[] }) => {
+                if ((item?.MP && item?.MP?.length > 0) || (item?.ADQ && item?.ADQ?.length > 0)) {
+                    return <div key={item?.id}>
+                        {item?.MP && item?.MP?.length > 0 && item?.MP?.map((mp: { id: number, uname: string }) => <Menu.Item style={{ cursor: 'default' }} key={mp?.id} className={style.acc}>
+                            <div className={style.cc}>
+                                <span>{getChannelName(item?.accountName)}-{mp?.uname}</span>
+                            </div>
+                        </Menu.Item>)}
+                    </div>
+                } else {
+                    return null
+                }
+            })}
+        </Menu>
+    }
+
+    return <>
+        {selectAccShow && <SelectAccount show={selectAccShow} onClose={() => { setSelectAccShow(false) }} onChange={(e: any) => { onChange(e); setSelectAccShow(false) }} channelAccounts={value} />}
+        {value?.some((item: any) => (item?.MP?.length > 0 || item?.ADQ?.length > 0)) ? <div className={style.inputAcc}>
+            <Dropdown overlay={menu} trigger={['click']}>
+                <div className={style.acc}>
+                    {data.title} {data.num > 0 && <span>+{data.num}</span>}
+                </div>
+            </Dropdown>
+
+            <Button type="text" className={style.edit} size="small" onClick={() => { setSelectAccShow(true) }}>修改</Button>
+        </div> : <Button type="primary" className={style.edit} size="small" onClick={() => { setSelectAccShow(true) }}>选择公众号</Button>}
+    </>
+}
+
+export default React.memo(InputAcc)

+ 91 - 0
src/pages/launchSystemNew/components/addLandingPage/landingPageReducer.ts

@@ -0,0 +1,91 @@
+import { useReducer } from "react"
+import { TopImg, TopVideo, Text, Img as ImgProps, GhButton, TopSlider, LinkButton, Shelfnew, WxAutoButton, Floatbutton } from '../../req'
+
+type Content = {
+    seat: string,
+    type: string | undefined,
+    content: any,
+    comptActive: boolean,
+    title: string
+}
+
+type Props = {
+    visible: boolean,
+    hideModal: () => void,
+    ajax?: any,
+    id?: number
+}
+
+interface State {
+    content: Array<TopImg | TopVideo | Content | Text | ImgProps | GhButton | WxAutoButton | TopSlider | LinkButton | Shelfnew>,    // 内容
+    pageBackColor: string  // 背景色
+}
+interface Action {
+    type: 'setCon' | 'setPageBackColor' | 'init',
+    params?: any
+}
+
+function reducer(state: State, action: Action) {
+    let { type, params } = action
+    switch (type) {
+        case 'setCon':
+            return { ...state, content: params.content }
+        case 'setPageBackColor':
+            return { ...state, pageBackColor: params.pageBackColor }
+        case 'init':
+            return { ...state, content: [params] }
+        default:
+            return { ...state }
+    }
+}
+
+const initState = {
+    content: [
+        {
+            elementType: 'empty'
+        }
+    ],
+    pageBackColor: '#FFFFFF'
+}
+
+interface StateGlobal {
+    componentItem: Array<Floatbutton>,    // 悬浮组件 内容
+}
+interface ActionGlobal {
+    type: 'setConItem' | 'init',
+    params?: any
+}
+
+function reducerGlobal(state: StateGlobal, action: ActionGlobal) {
+    let { type, params } = action
+    switch (type) {
+        case 'setConItem':
+            return { ...state, componentItem: params.componentItem }
+        case 'init':
+            return { ...state, componentItem: [params] }
+        default:
+            return { ...state }
+    }
+}
+const initStateGlobal = {
+    componentItem: []
+}
+
+function landingPageReducer() {
+    const [state, dispatch] = useReducer(reducer, initState)
+    const [stateGlobal, dispatchGlobal] = useReducer(reducerGlobal, initStateGlobal)
+
+    return {
+        state,
+        dispatch,
+        stateGlobal,
+        dispatchGlobal
+    }
+}
+
+export {
+    landingPageReducer,
+    Content,
+    Props,
+    State
+}

+ 369 - 0
src/pages/launchSystemNew/components/addLandingPage/selectAccount.tsx

@@ -0,0 +1,369 @@
+import { Input, message, Modal, Radio, Space, Spin, Table, Tag } from "antd";
+import React, { useCallback, useEffect, useReducer, useState } from "react";
+import { CheckOutlined } from "@ant-design/icons";
+import { columnsMp, columnsAdq } from './tableConfigAd'
+// import '../../adAuthorize/index.less'
+import { useModel } from "umi";
+
+interface State {
+    unames: {
+        MP?: any,
+        ADQ?: any,
+        id: number,
+        accountName: string
+    }[]
+}
+
+interface Action {
+    type: 'init' | 'unames',
+    params?: any
+}
+
+function reducer(state: State, action: Action) {
+    let { type, params } = action
+    switch (type) {
+        case 'unames':
+            const newState = { ...state, unames: [...params.unames] }
+            return newState
+        case 'init':
+            return {
+                unames: []
+            }
+        default:
+            return state
+    }
+}
+
+type Props = {
+    show?: boolean
+    onClose?: () => void,
+    onChange?: (unames: {
+        MP?: any,
+        ADQ?: any,
+        id: number,
+        accountName: string
+    }[]) => void,
+    channelAccounts?: {
+        MP?: any,
+        ADQ?: any,
+        id: number,
+        accountName: string
+    }[], // 回填数据
+    disabled?: boolean,
+    disabledAccounts?: {
+        MP?: any,
+        ADQ?: any
+    }
+}
+function SelectAccount(props: Props) {
+    const { show = false, onClose, onChange, channelAccounts = [], disabled = false, disabledAccounts = [] } = props
+    const initState = {
+        unames: []
+    }
+    const [state, dispatch] = useReducer(reducer, initState)
+    const [ispList, setIspList] = useState<any[]>([]);  // 服务商列表
+    const [selectServer, setSelectServer] = useState<number>(0) // 点击选中的服务商
+    const [channel, setChannel] = useState<string>('MP') // 选择渠道
+    const [paging, setPaging] = useState<{ pageSize: number, pageNum: number, uname?: string }>({ pageSize: 20, pageNum: 1 }) // 用户表格参数
+    const { allOfLoginUser, adsChannelAccountAdq, adsChannelAccountMp, adqSc, mpSc } = useModel('useLaunch.useAdAuthorize')
+
+    useEffect(() => {
+        if (show) {
+            allOfLoginUser.run().then((res: any) => {
+                setIspList(() => res?.data)
+                if (res?.data?.length > 0) {
+                    setSelectServer(() => res?.data[0]?.id)
+                }
+            })
+        }
+    }, [show])
+
+    useEffect(() => {
+        return () => {
+            dispatch({ type: 'unames', params: { unames: [] } })
+        }
+    }, [])
+    /** 回填选中 */
+    useEffect(() => {
+        if (channelAccounts?.length > 0) {
+            dispatch({ type: 'unames', params: { unames: channelAccounts } })
+        }
+    }, [channelAccounts])
+
+    useEffect(() => {
+        if (selectServer > 0) {
+            if (channel === 'MP') {
+                adsChannelAccountMp.run({ ...paging, channelId: selectServer })
+            } else {
+                adsChannelAccountAdq.run({ ...paging, channelId: selectServer })
+            }
+        }
+    }, [channel, selectServer, paging])
+
+    const onSearch = (value: string) => {
+        setPaging({ ...paging, uname: value, pageNum: 1 })
+    }
+
+    const onBack = useCallback(() => {
+        if (state?.unames?.some((item: { ADQ: any[], MP: any[] }) => item?.ADQ?.length > 0 || item?.MP?.length > 0)) {
+            onChange && onChange(state?.unames)
+        } else {
+            // message.error('请选择账号')
+            onChange && onChange([])
+        }
+
+    }, [state?.unames])
+
+    useEffect(() => {
+
+    }, [disabled, disabledAccounts])
+
+
+    const rowSelection = disabled ? {
+        getCheckboxProps: (record: any) => {
+            return {
+                disabled: disabledAccounts?.[channel]?.indexOf(record?.id) !== -1,
+                name: record.name,
+            }
+        },
+        selectedRowKeys: state?.unames?.find((item: any) => item?.id === selectServer)?.[channel]?.map((item: any) => item.id)
+    } : {
+            selectedRowKeys: state?.unames?.find((item: any) => item?.id === selectServer)?.[channel]?.map((item: any) => item.id)
+        }
+    /**收藏*/
+    const collect = useCallback((arg: { id: any, c: boolean, name: 'adq' | 'mp' }) => {
+        let { id, c, name } = arg
+        if (name === 'mp') {
+            mpSc.run({ accountId: id, collect: c }).then((res) => {
+                adsChannelAccountMp.refresh()
+            })
+        } else {
+            adqSc.run({ accountId: id, collect: c }).then((res) => {
+                adsChannelAccountAdq.refresh()
+            })
+        }
+    }, [adsChannelAccountMp, adsChannelAccountAdq])
+    return <Modal
+        title='选择账户'
+        visible={show}
+        onCancel={() => { onClose && onClose() }}
+        maskClosable
+        width={1000}
+        destroyOnClose
+        onOk={onBack}
+        className="launchRefresh"
+    >
+        <div className="refContent">
+            <div className="left">
+                <Spin spinning={allOfLoginUser?.loading}>
+                    <h4>服务商账户</h4>
+                    <div style={{ height: 410, overflowY: 'scroll' }}>
+                        {ispList?.length > 0 && ispList.map((item: any) => {
+                            return <div className={`isp ${item?.id === selectServer ? 'selectServer' : ''}`} key={item?.id} onClick={() => { setSelectServer(item?.id) }}>
+                                <span>{item?.accountName}</span> {(state?.unames?.find((it: any) => it?.id === item?.id)?.MP?.length > 0 || state?.unames?.find((it: any) => it?.id === item?.id)?.ADQ?.length > 0) && <CheckOutlined />}
+                            </div>
+                        })}
+                    </div>
+                </Spin>
+            </div>
+            <div className="right">
+                <Space direction="vertical" style={{ width: '100%' }}>
+                    <Space>
+                        <Radio.Group value={channel} onChange={(e) => { setChannel(e.target.value); setPaging({ pageSize: 20, pageNum: 1 }) }}>
+                            <Radio.Button value="MP">MP {state?.unames?.find((it: any) => it?.id === selectServer)?.MP?.length > 0 && <CheckOutlined />}</Radio.Button>
+                            {/* <Radio.Button value="ADQ">ADQ {state?.unames?.find((it: any) => it?.id === selectServer)?.ADQ?.length > 0 && <CheckOutlined />}</Radio.Button> */}
+                        </Radio.Group>
+                        <Input.Search placeholder="请输入广告主名称" onSearch={onSearch} style={{ width: 200 }} allowClear />
+                    </Space>
+                    {
+                        channel === "MP" ? <Table dataSource={adsChannelAccountMp?.data?.data?.records} scroll={{ x: 660, y: 300 }} loading={adsChannelAccountMp?.loading} columns={columnsMp(collect)} size="small" bordered
+                            pagination={{
+                                position: ['bottomLeft'],
+                                total: adsChannelAccountMp?.data?.data?.total,//总共多少条数据,服务器给,设置后分页自动计算页数
+                                showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>,
+                                showSizeChanger: true,
+                                showLessItems: true,
+                                defaultCurrent: 1,
+                                defaultPageSize: 20,//默认初始的每页条数
+                                current: paging?.pageNum || 1,
+                                pageSize: paging?.pageSize || 20,
+                                onChange: (page, pageSize) => { setPaging({ ...paging, pageNum: page, pageSize: pageSize as number }) }
+                            }}
+                            rowKey={(row) => row.id}
+                            rowSelection={{
+                                onSelectAll: (selected, selectedRows, changeRows) => {
+                                    let index = state?.unames?.findIndex((item: any) => item?.id === selectServer)
+                                    let data = state?.unames
+                                    let server = ispList?.find((item: any) => item?.id === selectServer)
+                                    if (selected) { // 全选
+                                        if (index !== -1) {
+                                            let ch: any = {}
+                                            if (Object.keys(data[index])?.indexOf(channel) !== -1) { // 判断已有
+                                                ch[channel] = [...data[index][channel], ...changeRows]
+                                            } else {
+                                                ch[channel] = changeRows
+                                            }
+                                            ch.id = selectServer
+                                            ch.accountName = server?.accountName
+                                            data[index] = { ...ch }
+                                        } else {
+                                            let ch: any = {}
+                                            ch[channel] = changeRows
+                                            ch.id = selectServer
+                                            ch.accountName = server?.accountName
+                                            data?.push({ ...ch })
+                                        }
+                                    } else { // 全部取消
+                                        let ch: any = {}
+                                        let channelData = data[index][channel]
+                                        changeRows.forEach((item: any) => {
+                                            let channelIndex = channelData?.findIndex((can: any) => can?.id === item?.id)
+                                            channelData?.splice(channelIndex, 1)
+                                        })
+                                        ch[channel] = [...channelData]
+                                        ch.id = selectServer
+                                        ch.accountName = server?.accountName
+                                        data[index] = { ...ch }
+                                    }
+                                    dispatch({ type: 'unames', params: { unames: [...data] } })
+                                },
+                                onSelect: (record, selected, selectedRows, nativeEvent) => {
+                                    selectedRows = selectedRows?.filter((item: any) => item)
+                                    let index = state?.unames?.findIndex((item: any) => item?.id === selectServer)
+                                    let data = state?.unames
+                                    let server = ispList?.find((item: any) => item?.id === selectServer)
+                                    if (selected) { // 增加
+                                        if (index !== -1) {
+                                            let ch: any = {}
+                                            if (Object.keys(data[index])?.indexOf(channel) !== -1) { // 判断已有
+                                                ch[channel] = [...data[index][channel], { ...record }]
+                                            } else {
+                                                ch[channel] = selectedRows
+                                            }
+                                            ch.id = selectServer
+                                            ch.accountName = server?.accountName
+                                            data[index] = { ...ch }
+                                        } else {
+                                            let ch: any = {}
+                                            ch[channel] = selectedRows
+                                            ch.id = selectServer
+                                            ch.accountName = server?.accountName
+                                            data?.push({ ...ch })
+                                        }
+                                    } else { // 减少
+                                        let ch: any = {}
+                                        if (Object.keys(data[index])?.indexOf(channel) !== -1) { // 判断已有
+                                            let channelData = data[index][channel]
+                                            let channelIndex = channelData?.findIndex((can: any) => can?.id === record?.id)
+                                            channelData?.splice(channelIndex, 1)
+                                            ch[channel] = [...channelData]
+                                        }
+                                        ch.id = selectServer
+                                        ch.accountName = server?.accountName
+                                        data[index] = { ...ch }
+                                    }
+                                    dispatch({ type: 'unames', params: { unames: [...data] } })
+                                },
+                                ...rowSelection
+                            }}
+                        /> :
+                            <Table dataSource={adsChannelAccountAdq?.data?.data?.records} scroll={{ x: 660, y: 300 }} loading={adsChannelAccountAdq?.loading} columns={columnsAdq(collect)} size="small" bordered
+                                pagination={{
+                                    position: ['bottomLeft'],
+                                    total: adsChannelAccountAdq?.data?.data?.total,//总共多少条数据,服务器给,设置后分页自动计算页数
+                                    showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>,
+                                    showSizeChanger: true,
+                                    showLessItems: true,
+                                    defaultCurrent: 1,
+                                    defaultPageSize: 20,//默认初始的每页条数
+                                    current: paging?.pageNum || 1,
+                                    pageSize: paging?.pageSize || 20,
+                                    onChange: (page, pageSize) => { setPaging({ ...paging, pageNum: page, pageSize: pageSize as number }) }
+                                }}
+                                rowKey={(row) => row.id}
+                                rowSelection={{
+                                    onSelectAll: (selected, selectedRows, changeRows) => {
+                                        let index = state?.unames?.findIndex((item: any) => item?.id === selectServer)
+                                        let data = state?.unames
+                                        let server = ispList?.find((item: any) => item?.id === selectServer)
+                                        if (selected) { // 全选
+                                            if (index !== -1) {
+                                                let ch: any = {}
+                                                if (Object.keys(data[index])?.indexOf(channel) !== -1) { // 判断已有
+                                                    ch[channel] = [...data[index][channel], ...changeRows]
+                                                } else {
+                                                    ch[channel] = changeRows
+                                                }
+                                                ch.id = selectServer
+                                                ch.accountName = server?.accountName
+                                                data[index] = { ...ch }
+                                            } else {
+                                                let ch: any = {}
+                                                ch[channel] = changeRows
+                                                ch.id = selectServer
+                                                ch.accountName = server?.accountName
+                                                data?.push({ ...ch })
+                                            }
+                                        } else { // 全部取消
+                                            let ch: any = {}
+                                            let channelData = data[index][channel]
+                                            changeRows.forEach((item: any) => {
+                                                let channelIndex = channelData?.findIndex((can: any) => can?.id === item?.id)
+                                                channelData?.splice(channelIndex, 1)
+                                            })
+                                            ch[channel] = [...channelData]
+                                            ch.id = selectServer
+                                            ch.accountName = server?.accountName
+                                            data[index] = { ...ch }
+                                        }
+                                        dispatch({ type: 'unames', params: { unames: [...data] } })
+                                    },
+                                    onSelect: (record, selected, selectedRows, nativeEvent) => {
+                                        selectedRows = selectedRows?.filter((item: any) => item)
+                                        let index = state?.unames?.findIndex((item: any) => item?.id === selectServer)
+                                        let data = state?.unames
+                                        let server = ispList?.find((item: any) => item?.id === selectServer)
+                                        if (selected) { // 增加
+                                            if (index !== -1) {
+                                                let ch: any = {}
+                                                if (Object.keys(data[index])?.indexOf(channel) !== -1) { // 判断已有
+                                                    ch[channel] = [...data[index][channel], { ...record }]
+                                                } else {
+                                                    ch[channel] = selectedRows
+                                                }
+                                                ch.id = selectServer
+                                                ch.accountName = server?.accountName
+                                                data[index] = { ...ch }
+                                            } else {
+                                                let ch: any = {}
+                                                ch[channel] = selectedRows
+                                                ch.id = selectServer
+                                                ch.accountName = server?.accountName
+                                                data?.push({ ...ch })
+                                            }
+                                        } else { // 减少
+                                            let ch: any = {}
+                                            if (Object.keys(data[index])?.indexOf(channel) !== -1) { // 判断已有
+                                                let channelData = data[index][channel]
+                                                let channelIndex = channelData?.findIndex((can: any) => can?.id === record?.id)
+                                                channelData?.splice(channelIndex, 1)
+                                                ch[channel] = [...channelData]
+                                            }
+                                            ch.id = selectServer
+                                            ch.accountName = server?.accountName
+                                            data[index] = { ...ch }
+                                        }
+                                        dispatch({ type: 'unames', params: { unames: [...data] } })
+                                    },
+                                    ...rowSelection
+                                }}
+                            />
+                    }
+                </Space>
+            </div>
+        </div>
+    </Modal>
+}
+
+
+export default React.memo(SelectAccount)

+ 320 - 0
src/pages/launchSystemNew/components/addLandingPage/sortable.tsx

@@ -0,0 +1,320 @@
+import { ArrowDownOutlined, ArrowUpOutlined, BorderRightOutlined, DeleteOutlined, DragOutlined, LinkOutlined, PlusOutlined, RetweetOutlined, UserAddOutlined } from "@ant-design/icons";
+import React from "react";
+import { SortableContainer, SortableElement, SortableHandle } from "react-sortable-hoc";
+import { ReactComponent as Img } from '@/assets/img.svg'
+import { ReactComponent as EditSvg } from '@/assets/edit.svg'
+import { ReactComponent as MyText } from '@/assets/text.svg'
+import { ReactComponent as ImgText } from '@/assets/imgText.svg'
+import { ReactComponent as FollowAcc } from '@/assets/followAcc.svg'
+import { ReactComponent as JumpLink } from '@/assets/jumpLink.svg'
+import { ReactComponent as WxAutoSvg } from '@/assets/wxAutoSvg.svg'
+import './index1.less'
+import { Tooltip } from "antd";
+
+const DragHandle = SortableHandle(() => <button style={{ cursor: 'grab' }} className="handle" onClick={(e) => { e.stopPropagation() }}><BorderRightOutlined /></button>);
+
+
+const ComptEdit = (props: {
+    data: any,
+    pureImageUrl?: string,
+    handleBtn: (type: string, index: number) => void,
+    del: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void,
+    upload?: () => void
+}) => {
+    const { data, handleBtn, del, pureImageUrl, upload } = props
+    return <>
+        <section className="comptEditTrBtns">
+            <div className="comptEditTrBtnsInner">
+                {data.index > 1 && <a onClick={(e) => { e.stopPropagation(); handleBtn('upper', data.index) }}><ArrowUpOutlined /></a>}
+                {data.length !== data.index + 1 && <a onClick={(e) => { e.stopPropagation(); handleBtn('lower', data.index) }}><ArrowDownOutlined /></a>}
+                <Tooltip placement="topRight" color="#FFF" title={<div className="assBts">
+                    <div onClick={(e) => { e.stopPropagation(); handleBtn('IMAGE', data.index) }}><Img /></div>
+                    <div onClick={(e) => { e.stopPropagation(); handleBtn('TEXT', data.index) }}><MyText /></div>
+                    <div onClick={(e) => { e.stopPropagation(); handleBtn('GH', data.index) }}><FollowAcc /></div>
+                    {/* <div onClick={(e) => { e.stopPropagation(); handleBtn('link', data.index) }}><JumpLink /></div>
+                    <div onClick={(e) => { e.stopPropagation(); handleBtn('shelfnew', data.index) }}><ImgText /></div> */}
+                    <div onClick={(e) => { e.stopPropagation(); handleBtn('ENTERPRISE_WX', data.index) }}><WxAutoSvg /></div>
+                </div>}>
+                    <a><PlusOutlined /></a>
+                </Tooltip>
+            </div>
+        </section>
+        <section className={'comptEditBtns'}>
+            <div className={'comptEditBtnsInner'}>
+                {pureImageUrl && <button onClick={() => { upload && upload() }}><EditSvg /></button>}
+                <DragHandle />
+                <button onClick={(e) => { del(e) }}><DeleteOutlined /></button>
+            </div>
+        </section>
+    </>
+}
+
+/** 内容文本 */
+export const SortableItemText = SortableElement(({ item, click, del, pageBackColor, handleBtn, data }: any) => {
+    let { fontSize, fontColor, textAlignment, text, fontStyle, paddingTop, paddingBottom } = item
+    return <div className={`compt componentType1 ${item.comptActive && 'comptActive'}`} onClick={(e) => { click(e) }}>
+        <div className={'componentWrap'}>
+            <div className={'componentContent'} style={{ backgroundColor: pageBackColor }}>
+                <div className={'text'} style={{ lineHeight: fontSize * 1.5 + 'px', fontSize: fontSize, color: fontColor, textAlign: textAlignment === 0 ? 'left' : textAlignment === 1 ? 'center' : 'right', fontWeight: fontStyle === 0 ? 'normal' : 'bold', maxWidth: '100%', display: 'block', marginLeft: 24, marginRight: 24, marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px' }}>
+                    <div>{text ?
+                        text?.split(/[\r\n]/g)?.map((item: any, index: number) => {
+                            if (item) {
+                                return <div key={`item${index}`}>
+                                    {item?.split(' ')?.map((item1: any, ind: number) => {
+                                        if (item1) {
+                                            return <span key={`item1${ind}`}>{item1}</span>
+                                        } else {
+                                            return <span key={`item1${ind}`}>&nbsp;</span>
+                                        }
+                                    })}
+                                </div>
+                            } else {
+                                return <div key={`item${index}`}>&nbsp;</div>
+                            }
+                        })
+                        : '请输入文本内容'}</div>
+                </div>
+                <div className={'textAreaDiv'} style={{ lineHeight: fontSize * 1.5 + 'px', fontSize: fontSize, margin: '11px 24px', marginLeft: 24, marginRight: 24, marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px' }}>
+                    <textarea readOnly value={text} className={'textarea'} placeholder={item.comptActive ? `请在右侧输入文本内容` : '请输入文本内容'} style={{ color: fontColor, fontWeight: fontStyle === 0 ? 'normal' : 'bold', textAlign: textAlignment === 0 ? 'left' : textAlignment === 1 ? 'center' : 'right', backgroundColor: pageBackColor }}></textarea>
+                </div>
+            </div>
+        </div>
+        <ComptEdit data={data} handleBtn={(type, index) => { handleBtn(type, index) }} del={(e) => { del(e) }} />
+    </div>
+});
+/** 内容图片 */
+export const SortableItemImg = SortableElement(({ item, click, del, upload, data, handleBtn }: any) => {
+    let { imageUrl, paddingTop, paddingBottom } = item
+    return <div className={`compt componentType41 ${item.comptActive && 'comptActive'}`} onClick={(e) => { click(e) }}>
+        <div className={'componentWrap'}>
+            <div className={'componentContent'}>
+                {
+                    imageUrl ? <img src={imageUrl} style={{ display: 'block', width: '100%', margin: 0, marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px' }} /> : <div className={'default'} style={{ width: 375, height: 222, margin: 0, marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px' }}>
+                        <div className={'defaultIcon'} style={{ marginTop: 44 }}>
+                            <Img />
+                        </div>
+                    </div>
+                }
+            </div>
+        </div>
+        {!imageUrl && <div className={'comptUpload'} style={{ margin: 0, marginTop: paddingTop / 2, marginBottom: paddingBottom / 2 }}>
+            <button style={{ marginTop: 114 }} className={'comptEditButton'} onClick={() => { upload(1) }}>上传图片</button>
+        </div>}
+        <ComptEdit data={data} handleBtn={(type, index) => { handleBtn(type, index) }} del={(e) => { del(e) }} pureImageUrl={imageUrl} upload={() => { upload(1) }} />
+    </div>
+});
+/** 内容关注公众号按钮 */
+export const SortableItemFollowAcc = SortableElement(({ item, click, del, data, handleBtn }: any) => {
+    let { paddingTop, paddingBottom, btnTitle, fontColor, btnBgColorTheme, btnBorderColorTheme, btnFontType, useIcon } = item
+    return <div className={`compt componentType21 ${item.comptActive && 'comptActive'}`} onClick={(e) => { click(e) }}>
+        <div className={'componentWrap'}>
+            <div className={'componentContent'}>
+                <div style={{ marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px' }}>
+                    <div style={{ textAlign: 'center', lineHeight: 0, maxWidth: '100%', margin: '0 92.5px' }}>
+                        <a style={{
+                            textDecoration: 'none', color: fontColor || 'rgb(255,255,255)', backgroundColor: btnBgColorTheme || 'rgb(7, 193, 96)',
+                            border: ['#FFFFFF', '#ffffff', 'rgb(255, 255, 255)'].indexOf(btnBorderColorTheme) !== -1 ? '0px solid rgb(255, 255, 255)' : `2px solid ${btnBorderColorTheme}`, borderRadius: 4, display: 'flex', alignItems: 'center',
+                            overflow: 'hidden', justifyContent: 'center', whiteSpace: 'pre', fontWeight: btnFontType === 0 ? 'normal' : 'bold',
+                            height: 40, lineHeight: 40, width: '100%', fontSize: 15
+                        }}>{useIcon === 1 && <UserAddOutlined style={{ marginRight: 6 }} />}{btnTitle || ''}</a>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <ComptEdit data={data} handleBtn={(type, index) => { handleBtn(type, index) }} del={(e) => { del(e) }} />
+    </div>
+})
+
+/** 内容添加商家微信按钮 */
+export const SortableItemWxAuto = SortableElement(({ item, click, del, data, handleBtn }: any) => {
+    let { paddingTop, paddingBottom, btnTitle, fontColor, btnBgColorTheme, btnBorderColorTheme, btnFontType, useIcon } = item
+    return <div className={`compt componentType21 ${item.comptActive && 'comptActive'}`} onClick={(e) => { click(e) }}>
+        <div className={'componentWrap'}>
+            <div className={'componentContent'}>
+                <div style={{ marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px' }}>
+                    <div style={{ textAlign: 'center', lineHeight: 0, maxWidth: '100%', margin: '0 92.5px' }}>
+                        <a style={{
+                            textDecoration: 'none', color: fontColor || 'rgb(255,255,255)', backgroundColor: btnBgColorTheme || 'rgb(7, 193, 96)',
+                            border: ['#FFFFFF', '#ffffff', 'rgb(255, 255, 255)'].indexOf(btnBorderColorTheme) !== -1 ? '0px solid rgb(255, 255, 255)' : `2px solid ${btnBorderColorTheme}`, borderRadius: 4, display: 'flex', alignItems: 'center',
+                            overflow: 'hidden', justifyContent: 'center', whiteSpace: 'pre', fontWeight: btnFontType === 0 ? 'normal' : 'bold',
+                            height: 40, lineHeight: 40, width: '100%', fontSize: 15
+                        }}>{useIcon === 1 && <UserAddOutlined style={{ marginRight: 6 }} />}{btnTitle || ''}</a>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <ComptEdit data={data} handleBtn={(type, index) => { handleBtn(type, index) }} del={(e) => { del(e) }} />
+    </div>
+})
+
+/** 内容跳转链接按钮 */
+export const SortableItemJumpLink = SortableElement(({ item, click, del, data, handleBtn }: any) => {
+    let { paddingTop, paddingBottom, btnTitle, fontColor, btnBgColorTheme, btnBorderColorTheme, btnFontType, useIcon } = item
+    return <div className={`compt componentType21 ${item.comptActive && 'comptActive'}`} onClick={(e) => { click(e) }}>
+        <div className={'componentWrap'}>
+            <div className={'componentContent'}>
+                <div style={{ marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px' }}>
+                    <div style={{ textAlign: 'center', lineHeight: 0, maxWidth: '100%', margin: '0 92.5px' }}>
+                        <a style={{
+                            textDecoration: 'none', color: fontColor || 'rgb(255,255,255)', backgroundColor: btnBgColorTheme || 'rgb(7, 193, 96)',
+                            border: ['#FFFFFF', '#ffffff', 'rgb(255, 255, 255)'].indexOf(btnBorderColorTheme) !== -1 ? '0px solid rgb(255, 255, 255)' : `2px solid ${btnBorderColorTheme}`, borderRadius: 4, display: 'flex', alignItems: 'center',
+                            overflow: 'hidden', justifyContent: 'center', whiteSpace: 'pre', fontWeight: btnFontType === '0' ? 'normal' : 'bold',
+                            height: 40, lineHeight: 40, width: '100%', fontSize: 15
+                        }}>{useIcon === '1' && <LinkOutlined style={{ marginRight: 6 }} />}{btnTitle || ''}</a>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <ComptEdit data={data} handleBtn={(type, index) => { handleBtn(type, index) }} del={(e) => { del(e) }} />
+    </div>
+})
+
+/** 图文 */
+export const SortableItemImgText = SortableElement(({ item, click, del, index, data, handleBtn }: any) => {
+    let { paddingTop, paddingBottom, layoutItems, borderColor, bgColor, type, wxad_align } = item
+    if (type === '104') {
+        let componentItem = layoutItems?.componentItem[0]?.layoutItems?.componentItem
+        let otherData = componentItem[1]?.layoutItems?.componentItem
+        return <div className={`compt componentType104 ${item.comptActive && 'comptActive'}`} onClick={(e) => { click(e) }}>
+            <div className={'componentWrap'}>
+                <div className={'componentContent'}>
+                    <div className={'shelf listType'} style={{ marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px', marginLeft: 20, marginRight: 20 }}>
+                        <div className={'shelfItem'} style={{ border: `1px solid ${borderColor || "#e5e5e5"}`, backgroundColor: bgColor || "#ffffff" }}>
+                            <div className={'shelfItemImg'} style={{ marginLeft: 11.5, marginTop: 11.5 }}>
+                                {componentItem[0]?.pureImageUrl ? <img src={componentItem[0]?.pureImageUrl} style={{ display: 'flex', width: '100%', height: '100%' }} /> : <div className={'default'} style={{ width: '100%', height: '100%' }}>
+                                    <div className={'defaultIcon'} style={{ marginTop: 27, width: 36, height: 36 }}>
+                                        <Img />
+                                    </div>
+                                </div>}
+                            </div>
+                            <div className={'shelfItemContent'} style={{ margin: '12px 20px 0 12px' }}>
+                                <p className={'title'} style={{ color: otherData[0]?.fontColor || "#353535", fontSize: 16 }}>{otherData[0]?.content || otherData[0]?.name}</p>
+                                <p className={'desc'} style={{ color: otherData[1]?.fontColor || "#B2B2B2" }}>{otherData[1]?.content || otherData[1]?.name}</p>
+                                <div
+                                    className={'btn'}
+                                    style={{
+                                        color: otherData[2]?.fontColor || 'rgb(255, 255, 255)',
+                                        textDecoration: 'none',
+                                        fontWeight: otherData[2]?.btnFontType === '0' ? 'normal' : 'bold',
+                                        backgroundColor: otherData[2]?.btnBgColorTheme || "#07C160",
+                                        border: ['#FFFFFF', '#ffffff', 'rgb(255, 255, 255)'].indexOf(otherData[2]?.btnBorderColorTheme) !== -1 ? '0px solid rgb(255, 255, 255)' : `2px solid ${otherData[2]?.btnBorderColorTheme}`,
+                                        borderRadius: 4
+                                    }}
+                                >{otherData[2]?.btnTitle}</div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <ComptEdit data={data} handleBtn={(type, index) => { handleBtn(type, index) }} del={(e) => { del(e) }} />
+        </div>
+    } else if (type === '103') {
+        let componentItem = layoutItems?.componentItem
+        return <div className={`compt componentType103 ${item.comptActive && 'comptActive'}`} onClick={(e) => { click(e) }}>
+            <div className={'componentWrap'}>
+                <div className={'componentContent'}>
+                    <div className='shelf gridType' style={{ marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px', marginLeft: 20 }}>
+                        {
+                            componentItem?.map((item: any, index: number) => {
+                                let shelfnewItem = item?.layoutItems?.componentItem[0]?.layoutItems?.componentItem
+                                return <div className='shelfItem-3q' key={index} style={{ borderWidth: 1, borderStyle: 'solid', borderColor: item?.borderColor, backgroundColor: item?.bgColor || 'rgb(255,255,255)', marginLeft: index === 1 ? 11 : 0 }}>
+                                    <div className='shelfItemImg' style={{ marginLeft: 5.5, marginTop: 5.5 }}>
+                                        {shelfnewItem[0]?.pureImageUrl ? <img src={shelfnewItem[0]?.pureImageUrl} style={{ display: 'flex', width: '100%', height: '100%' }} /> : <div className="default" style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
+                                            <div className={'defaultIcon'} style={{ width: 36, height: 36 }}>
+                                                <Img />
+                                            </div>
+                                        </div>}
+                                    </div>
+                                    <div className='shelfItemContent' style={{ marginLeft: 12, textAlign: wxad_align === 0 ? 'left' : 'center' }}>
+                                        <p className='title' style={{ color: shelfnewItem[1]?.fontColor || 'rgb(53, 53, 53)', fontSize: 16, marginBottom: 4 }}>{shelfnewItem[1]?.content || shelfnewItem[1]?.name}</p>
+                                        <p className='desc' style={{ color: shelfnewItem[2]?.fontColor || 'rgb(178, 178, 178)', marginBottom: 14 }}>{shelfnewItem[2]?.content || shelfnewItem[2]?.name}</p>
+                                        <p className='btn' style={{
+                                            textDecoration: 'none',
+                                            fontWeight: shelfnewItem[3]?.btnFontType === '0' ? 400 : 'bold',
+                                            color: shelfnewItem[3]?.fontColor || 'rgb(255, 255, 255)',
+                                            backgroundColor: shelfnewItem[3]?.btnBgColorTheme || 'rgb(7,193,96)',
+                                            borderWidth: shelfnewItem[3]?.borderSize ? Number(shelfnewItem[3]?.borderSize) : 0,
+                                            borderStyle: 'solid',
+                                            borderColor: shelfnewItem[3]?.btnBorderColorTheme,
+                                            borderRadius: 4
+                                        }}>{shelfnewItem[3]?.btnTitle}</p>
+                                    </div>
+                                </div>
+                            })
+                        }
+                    </div>
+                </div>
+            </div>
+            <ComptEdit data={data} handleBtn={(type, index) => { handleBtn(type, index) }} del={(e) => { del(e) }} />
+        </div>
+    } else {
+        return null
+    }
+
+})
+
+/** 悬浮组件 */
+export const SortableItemFloatbutton = SortableElement(({ item, click, del }: any) => {
+    let { titleColor, descColor, componentItem, iconUrl, title, desc, wxad_styleType } = item
+    return <div className={`compt componentType134 comptFixedBottom ${item.comptActive && 'comptActive'}`} onClick={(e) => { click(e) }}>
+        <div className={'componentWrap'}>
+            <div className="componentContent">
+                <div className="floatButtonWrapper">
+                    <div className="floatButton">
+                        {wxad_styleType === '1' && (iconUrl ? <img src={iconUrl} className="floatButtonAvatar"/> : <div className="floatButtonAvatarPlaceholder"></div>)}
+                        <div className="floatButtonTexts">
+                            <div className="floatButtonTitle" style={{ color: titleColor || 'rgb(23, 23, 23)' }}>{title || '标题'}</div>
+                            {(wxad_styleType === '1' || wxad_styleType === '2' ) && <div className="floatButtonDesc" style={{ color: descColor || 'rgb(76, 76, 76)' }}>{desc || '描述'}</div>}
+                        </div>
+                        <div className="floatButtonLink" style={{
+                            color: componentItem?.fontColor || 'rgb(255,255,255)', 
+                            fontWeight: componentItem?.btnFontType === '0' ? 'normal' : 'bold', 
+                            backgroundColor: componentItem?.btnBgColorTheme || 'rgb(7, 193, 96)', 
+                            width: ((componentItem?.layoutWidth || 160) / 2) + 'px',
+                            textAlign: 'center', 
+                            overflow: 'hidden', 
+                            whiteSpace: 'pre'
+                        }}>{componentItem?.btnTitle}</div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <section className={'comptEditBtns'}>
+            <div className={'comptEditBtnsInner'}>
+                <button onClick={(e) => { del(e) }}><DeleteOutlined /></button>
+            </div>
+        </section>
+    </div>
+})
+
+/** 外层 */
+export const SortableList = SortableContainer(({ children, isFloatButton }: { children: any, isFloatButton?: boolean }) => (<div className="page-0" style={isFloatButton ? { paddingBottom: 90, minHeight: 510 } : {}}>{children}</div>));
+
+// 以下是轮播图内容
+const DragSliderHandle = SortableHandle(() => <div style={{ cursor: 'grab' }} className="sliderhandle"><DragOutlined /></div>);
+// 轮播图有图item
+export const SortableItemLi = SortableElement(({ isActive, pureImageUrl, click, setActiveIndex }: any) => {
+    return <li className={`imageUploadItem ${isActive ? 'active' : ''} imageUploadItemInnerDone`} style={{ zIndex: 2000 }}>
+        <div className="upload-img-item">
+            <div className="upload-img-item-inner" style={{ backgroundImage: `url(${pureImageUrl})`, borderStyle: 'solid' }} onClick={setActiveIndex}>
+                <div className='upload-img-item-action' onClick={click}>
+                    <RetweetOutlined />
+                </div>
+                <DragSliderHandle />
+            </div>
+        </div>
+    </li>
+})
+
+// 轮播图无图item
+export const SortableItemNoLi = SortableElement(({ isActive, click }: any) => {
+    return <li className={`imageUploadItem ${isActive ? 'active' : ''}`}>
+        <div className="upload-img-item">
+            <div className="upload-img-item-inner" style={{ backgroundImage: `url("")` }} onClick={click}><PlusOutlined /></div>
+        </div>
+    </li>
+})
+
+
+/** 外层轮播图 */
+export const SortableUlList = SortableContainer(({ children }: { children: any }) => (<ul className="imageUpload">{children}</ul>));

+ 104 - 0
src/pages/launchSystemNew/components/addLandingPage/tableConfigAd.tsx

@@ -0,0 +1,104 @@
+import { HeartFilled, HeartOutlined } from "@ant-design/icons"
+import React from "react"
+
+export function columnsMp(collect: (arg: { id: any, c: boolean, name: 'adq' | 'mp' }) => void): any {
+    return [
+        {
+            title: '收藏',
+            dataIndex: 'cz',
+            align: 'center',
+            key: 'cz',
+            width: 50,
+            fixed: 'left',
+            render: (a: any, b: any) => {
+                return b?.collected ? <a onClick={() => {
+                    collect({ id: b.id, c: false, name: 'mp' })
+                }}><HeartFilled style={{ color: 'red' }} /></a> : <a onClick={() => {
+                    collect({ id: b.id, c: true, name: 'mp' })
+                }}><HeartOutlined /></a>
+            },
+        },
+        {
+            fixed: 'left',
+            title: '广告主名称',
+            dataIndex: 'uname',
+            key: 'uname',
+            align: 'center',
+            ellipsis: true,
+            width: 100
+        },
+        {
+            title: 'AppId',
+            dataIndex: 'appid',
+            key: 'appid',
+            align: 'center',
+            ellipsis: true,
+            width: 160
+        },
+        {
+            title: '原始ID',
+            dataIndex: 'wxname',
+            key: 'wxname',
+            align: 'center',
+            ellipsis: true,
+            width: 140,
+        },
+        {
+            title: '账号ID',
+            dataIndex: 'uid',
+            key: 'uid',
+            align: 'center',
+            ellipsis: true,
+            width: 90,
+        },
+        {
+            title: '所属服务商SPID',
+            dataIndex: 'spid',
+            key: 'spid',
+            width: 120,
+            ellipsis: true,
+            align: 'center',
+        },
+        {
+            title: '主体名称',
+            dataIndex: 'companyName',
+            key: 'companyName',
+            ellipsis: true,
+            width: 300
+        },
+    ]
+}
+export function columnsAdq(collect: (arg: { id: any, c: boolean, name: 'adq' | 'mp' }) => void): any {
+    return [
+        {
+            title: '收藏',
+            dataIndex: 'cz',
+            align: 'center',
+            key: 'cz',
+            width: 50,
+            render: (a: any, b: any) => {
+                return b?.collected ? <a onClick={() => {
+                    collect({ id: b.id, c: false, name: 'adq' })
+                }}><HeartFilled style={{ color: 'red' }} /></a> : <a onClick={() => {
+                    collect({ id: b.id, c: true, name: 'adq' })
+                }}><HeartOutlined /></a>
+            },
+        },
+        {
+            title: '账号ID',
+            dataIndex: 'uid',
+            key: 'uid',
+            align: 'center',
+            ellipsis: true,
+            width: 90,
+        },
+        {
+            title: '广告主名称',
+            dataIndex: 'uname',
+            key: 'uname',
+            align: 'center',
+            ellipsis: true,
+            width: 200
+        }
+    ]
+}

+ 92 - 0
src/pages/launchSystemNew/components/bathLauCopy/index.less

@@ -0,0 +1,92 @@
+.batchCopy {
+  .info {
+    border: 1px solid rgb(226, 226, 226);
+    padding: 0 10px;
+    width: 100%;
+    border-radius: 4px;
+
+    .items {
+      width: 100%;
+      display: flex;
+      justify-content: space-between;
+      margin: 10px 0;
+
+      .item {
+        width: 50%;
+        display: flex;
+        justify-content: flex-start;
+        align-items: center;
+
+        .label {
+          font-size: 14px;
+          font-weight: 500;
+          width: 110px;
+          text-align: right;
+          margin-right: 10px;
+        }
+      }
+    }
+
+    margin-bottom: 20px;
+  }
+
+  .title {
+    font-size: 16px;
+    font-weight: 700;
+    margin-bottom: 20px;
+  }
+
+  .originality {
+    border: 1px dashed rgb(228, 228, 228);
+    padding: 10px;
+    box-sizing: border-box;
+    border-radius: 4px;
+
+    &+div {
+      margin-top: 10px;
+    }
+
+    .head {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      font-weight: 700;
+      font-size: 14px;
+
+      .clear {
+        display: none;
+      }
+
+      &:hover .clear {
+        display: inline-block;
+      }
+    }
+  }
+
+  .materials {
+    display: flex;
+    flex-wrap: wrap;
+
+    .material {
+      width: 50%;
+    }
+
+    .materialImg {
+      width: 25%;
+    }
+
+    .box {
+      display: flex;
+      justify-content: flex-start;
+      align-items: flex-end;
+
+      .del {
+        display: none;
+      }
+
+      &:hover .del {
+        display: block;
+      }
+    }
+  }
+}

+ 543 - 0
src/pages/launchSystemNew/components/bathLauCopy/index.tsx

@@ -0,0 +1,543 @@
+import { Button, Checkbox, InputNumber, message, Modal, Popconfirm, Radio, Select, Space, Spin } from "antd";
+import React, { useCallback, useEffect, useState } from "react";
+import style1 from './index.less'
+import { TopImg, TopVideo, TopSlider } from '../../req'
+import { DeleteOutlined, PlusOutlined, RetweetOutlined } from "@ant-design/icons";
+import { SortableUlList, SortableItemNoLi, SortableItemLi } from '../../components/addLandingPage/sortable'
+import arrayMove from "array-move"
+import SelectCloud from '../selectCloud'
+import { topsliderContent, topimgContent, topvideoNewContent } from '../../components/addLandingPage/content'
+import { BcForm } from "@/pages/launchSystem/launchManage/weChat/components/createPlan/components/AdBasicConfig";
+import { useModel } from "umi";
+import { getTypeKey } from "@/utils/utils";
+import moment from "moment";
+import { bdSysMediaAdd } from '@/services/launchAdq/material';
+
+interface Props {
+    visible: boolean,
+    id: number,
+    onChange?: () => void,
+    onClose?: () => void,
+    ajaxHome?: any
+}
+/**
+ * 批量复制落地页
+ * @param props 
+ * @returns 
+ */
+function BathLauCopy(props: Props) {
+    const { visible, id, onChange, onClose, ajaxHome } = props
+
+    /** ==============数据参数开始=============== */
+    const [elementType, setElementType] = useState<string | undefined>(undefined) // 顶部素材类型
+    const [adLocation, setAdLocation] = useState<string>('sns')  // 广告位
+    const [outerStyle, setOuterStyle] = useState<number>(0)  // 外层样式
+    const [componentCount, setComponentCount] = useState<number>(3)  // 图片数量
+    const [selectImgVisible, setSelectImgVisible] = useState<boolean>(false)   // 选择图片弹窗
+    const [isFootlock, setIsFootlock] = useState<boolean>(false)  // 是否横板视频
+    const [sliderImgContent, setSliderImgContent] = useState<{ url: string, width?: number, height?: number }[]>([])  // 保存回填数据
+    const [groupIndex, setGroupIndex] = useState<number>(-1)  // 保存正在操作的组 下标
+    const [loading, setLoading] = useState<boolean>(false)  // 确定按钮loading
+    const { init, add, get, state: { parentId, belongUser } } = useModel('useLaunchAdq.useBdMediaPup')
+    const [data, setData] = useState<any>({})
+    const [topData, setTopData] = useState<Array<TopImg | TopVideo | TopSlider>>([])  // 顶部素材数据
+    const [sort, setSort] = useState<number>(0) // 排序
+    /** ===============数据参数结束=============== */
+
+    // 回填
+    useEffect(() => {
+        if (id) {
+            get.run({ sysMediaId: id, mediaType: 'PAGE' }).then(res => {
+                if (res) {
+                    setData(res)
+                    const { pageSpecsList } = res
+                    let pageElementsSpec = pageSpecsList[0]?.pageElementsSpecList[0]
+                    setElementType(pageElementsSpec?.elementType)
+                    let typeKey = getTypeKey(pageElementsSpec?.elementType)
+                    setTopData([{
+                        elementType: pageElementsSpec?.elementType as any,
+                        ...pageElementsSpec[typeKey]
+                    }])
+                    if (pageElementsSpec?.elementType === 'TOP_IMAGE' || pageElementsSpec?.elementType === 'TOP_VIDEO') {
+                        setAdLocation(() => pageElementsSpec[typeKey]?.adLocation)
+                        setOuterStyle(() => pageElementsSpec[typeKey]?.outerStyle)
+                        setComponentCount(1)
+                    } else if (pageElementsSpec?.elementType === 'TOP_SLIDER') {
+                        setComponentCount(pageElementsSpec[typeKey]?.imageUrlList?.length || 3)
+                    }
+                    setSort(res?.sort || 0)
+                }
+            })
+        }
+    }, [id])
+
+    // 设置选择的图片参数
+    /** 获取选中内容 */
+    useEffect(() => {
+        if (elementType === 'TOP_IMAGE') {
+            setIsFootlock(() => false)
+            if (adLocation === 'sns') { // 朋友圈信息流
+                if (outerStyle === 0) { // 常规广告
+                    init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: 800, height: 800 }]] })
+                } else { // 卡片广告
+                    init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: 800, height: 450 }]] })
+                }
+            } else { // 公众号及其他
+                init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: 800, height: 800 }], [{ relation: '=', width: 800, height: 450 }], [{ relation: '=', width: 800, height: 640 }], [{ relation: '=', width: 640, height: 800 }]] })
+            }
+        } else if (elementType === 'TOP_SLIDER') { // 轮播图
+            setIsFootlock(() => false)
+            init({ mediaType: 'IMG', num: componentCount, cloudSize: [[{ relation: '=', width: 800, height: 800 }]] })
+        } else if (elementType === 'TOP_VIDEO') { // 视频
+            if (adLocation === 'sns') { // 朋友圈信息流
+                if (outerStyle === 0) { // 常规广告
+                    init({ mediaType: 'VIDEO', cloudSize: [[{ relation: '=', width: 640, height: 480 }], [{ relation: '=', width: 640, height: 360 }], [{ relation: '=', width: 750, height: 1334 }], [{ relation: '=', width: 720, height: 1280 }]] })
+                } else { // 卡片广告
+                    init({ mediaType: 'VIDEO', cloudSize: [[{ relation: '=', width: 750, height: null }, { relation: '<=', width: null, height: 1536 }]] })
+                }
+            } else { // 公众号及其它
+                init({ mediaType: 'VIDEO', cloudSize: [[{ relation: '=', width: 750, height: null }, { relation: '<=', width: null, height: 1536 }]] })
+                setIsFootlock(() => false)
+            }
+        }
+    }, [elementType, adLocation, outerStyle, componentCount])
+
+    // 关闭弹窗
+    const hideHandle = () => {
+        onClose && onClose()
+    }
+
+    const handleOk = useCallback(() => {
+        if (elementType === 'TOP_SLIDER' && !topData?.every((item1: any) => {
+            if (!item1.imageUrlList?.every((item: string) => item)) {
+                return false
+            } else {
+                return true
+            }
+        })) {
+            message.error('顶部轮播图组缺少图片')
+            return
+        } else if (elementType === 'TOP_IMAGE' && !topData?.every((item: any) => item.imageUrl)) {
+            message.error('顶部图片组缺少图片')
+            return
+        } else if (elementType === 'TOP_VIDEO' && !topData?.every((item: any) => item.videoUrl)) {
+            message.error('顶部视频组缺少视频')
+            return
+        }
+
+        setLoading(true)
+
+        // let { descContext, name, novelName, title, lastCanvasType, pageContextList, supportInfoType } = data
+        let ajax = topData?.map((item: any, index: number) => {
+            const { elementType, ...newItem } = item
+            let newTopPage = { elementType }
+            let typeKey = getTypeKey(elementType)
+            newTopPage[typeKey] = newItem
+            let pageSpecsList: any[] = data?.pageSpecsList
+            pageSpecsList[0].pageElementsSpecList[0] = newTopPage
+            let params = {
+                mediaType: 'PAGE',
+                folder: false,
+                parentId,
+                title: `批量复制推广页` + moment().format("YYYYMMDDHHmmss"),
+                pageName: `批量复制推广页` + moment().format("YYYYMMDDHHmmss"),
+                belongUser: belongUser === '0' ? false : true,
+                sort,
+                pageSpecsList,
+                globalSpec: data?.globalSpec,
+                shareContentSpec: data?.shareContentSpec
+            }
+
+            console.log('params---->', params);
+            return bdSysMediaAdd(params as any)
+        })
+        Promise.all(ajax).then((res: any) => {
+            setLoading(false)
+            if (res && res.length > 0) {
+                let data = res[res.length - 1]
+                if (data?.code === 200) {
+                    onClose && onClose()
+                    ajaxHome && ajaxHome?.refresh()
+                    message.success('新增成功')
+                }
+            }
+        }).catch(() => { setLoading(false) })
+    }, [topData, elementType, sort, data, adLocation, outerStyle, loading, componentCount, parentId, belongUser])
+
+    // 切换顶部素材类型
+    const changeType = useCallback((value: string) => {
+        setElementType(value)
+        switch (value) {
+            case 'TOP_IMAGE':
+                setTopData([{ ...topimgContent }])
+                setComponentCount(1)
+                break;
+            case 'TOP_SLIDER':
+                setTopData([{ ...topsliderContent }])
+                setComponentCount(3)
+                break;
+            case 'topvideo':
+                setTopData([{ ...topvideoNewContent }])
+                break;
+        }
+    }, [elementType, topData, componentCount])
+
+    /** 轮播图位置拖动切换顺序 */
+    const onSortEndSlider = useCallback(({ oldIndex, newIndex }: { oldIndex: number, newIndex: number }) => {
+        let topDataNew = JSON.parse(JSON.stringify(topData))
+        let selectCon = topDataNew[groupIndex]
+        let imageUrlList = selectCon?.imageUrlList
+        imageUrlList = arrayMove(imageUrlList, oldIndex, newIndex)
+        selectCon = { ...selectCon, imageUrlList }
+        topDataNew[groupIndex] = selectCon
+        setTopData(topDataNew)
+    }, [groupIndex, topData])
+
+    /** 轮播图选择图片 */
+    const sliderSelect = useCallback((index: number, indexGroup: number) => {
+        let topDataNew = JSON.parse(JSON.stringify(topData))
+        // 保存点击了第几组的选择图片
+        setGroupIndex(indexGroup)
+        // 设置activeIndex
+        activeIndexHancdle(index, indexGroup)
+        // 回填图片
+        let selectCon = topDataNew[indexGroup]
+        let imgList: any[] = []
+        selectCon?.imageUrlList?.forEach((item: string) => {
+            if (item) {
+                imgList.push({ url: item })
+            }
+        })
+        setSliderImgContent(imgList)
+        setSelectImgVisible(true)
+    }, [groupIndex, topData, sliderImgContent, selectImgVisible])
+
+    /** 选择单张图片 */
+    const clickUpdateImg = useCallback((index: number) => {
+        setSliderImgContent([])
+        setSelectImgVisible(true)
+        setGroupIndex(index)
+    }, [sliderImgContent, selectImgVisible, groupIndex])
+
+    /** 添加素材组 */
+    const addTopData = useCallback(() => {
+        switch (elementType) {
+            case 'TOP_IMAGE':
+                topData.push({ ...topimgContent })
+                setTopData([...topData])
+                break
+            case 'TOP_SLIDER':
+                let imageUrlList = Array(Number(componentCount)).fill('').map(() => "")
+                topData.push({
+                    ...topsliderContent,
+                    imageUrlList
+                })
+                setTopData([...topData])
+                break
+            case 'TOP_VIDEO':
+                topData.push({ ...topvideoNewContent })
+                setTopData([...topData])
+                break
+        }
+    }, [elementType, topData, componentCount])
+    /**删除组 */
+    const del = useCallback((index: number) => {
+        let newTopData = topData.filter((item, eq) => eq !== index)
+        setTopData(newTopData)
+    }, [topData])
+    /** 设置轮播图个数 */
+    const setCount = useCallback((value: number) => {
+        setComponentCount(value)
+        if (topData?.length > 0) {
+            let selectCon = topData[0] as TopSlider
+            if (selectCon?.elementType === 'TOP_SLIDER') {
+                let imageUrlList = selectCon?.imageUrlList
+                if (imageUrlList?.length > value) { // 减少
+                    let newTopData = topData.map((item: any) => {
+                        let imageUrlList = item?.imageUrlList
+                        return {
+                            ...item,
+                            imageUrlList: [...imageUrlList?.splice(0, Number(value))]
+                        }
+                    })
+                    setTopData([...newTopData])
+                } else { // 增加
+                    let newList = Array(value - imageUrlList?.length).fill('').map(() => "")
+                    let newTopData = topData.map((item: any) => {
+                        let imageUrlList = item?.imageUrlList
+                        return {
+                            ...item,
+                            imageUrlList: [...imageUrlList, ...newList]
+                        }
+                    })
+                    setTopData([...newTopData])
+                }
+            }
+
+        }
+
+    }, [componentCount, topData])
+
+    /** 设置图片外层样式 */
+    const setImgViewType = useCallback((value: number) => {
+        setOuterStyle(value)
+        setTopData(topData.map((item: any) => ({ ...item, pureImageUrl: '' })))
+    }, [outerStyle, topData])
+
+    // 设置选择的图片
+    const setImg = useCallback((value: any[]) => {
+        let topDataNew = JSON.parse(JSON.stringify(topData))
+        switch (elementType) {
+            case 'TOP_IMAGE':
+                topDataNew[groupIndex] = {
+                    ...topDataNew[groupIndex],
+                    imageUrl: value[0]?.url
+                }
+                break;
+            case 'TOP_SLIDER':
+                let imageUrlList = value?.map((item: { url: string }) => item.url)
+                topDataNew[groupIndex] = {
+                    ...topDataNew[groupIndex],
+                    imageUrlList: topDataNew[groupIndex]?.imageUrlList?.reduce((prev: any[], cur: any, index: number) => {
+                        prev.push(imageUrlList[index] || "")
+                        return prev
+                    }, [])
+                }
+                break;
+            case 'TOP_VIDEO':
+                topDataNew[groupIndex] = {
+                    ...topDataNew[groupIndex],
+                    videoUrl: value[0]?.url
+                }
+                break;
+        }
+        setSelectImgVisible(false)
+        setTopData(topDataNew)
+    }, [elementType, groupIndex, topData, adLocation, outerStyle])
+
+    /** 设置activeIndex */
+    const activeIndexHancdle = useCallback((index: number, indexGroup: number) => {
+        let topDataNew = JSON.parse(JSON.stringify(topData))
+        // 设置activeIndex
+        topDataNew[indexGroup] = {
+            ...topDataNew[indexGroup],
+            activeIndex: index
+        }
+        setTopData(topDataNew)
+    }, [topData])
+
+    return <Modal
+        bodyStyle={{ height: 600, overflowY: 'auto' }}
+        title="批量复制推广页"
+        visible={visible}
+        onCancel={hideHandle}
+        confirmLoading={loading}
+        onOk={handleOk}
+        width={1000}
+        className={style1.batchCopy}
+        maskClosable={false}
+    >
+        {/* 选择素材 */}
+        {selectImgVisible && <SelectCloud visible={selectImgVisible} sliderImgContent={sliderImgContent} onClose={() => setSelectImgVisible(false)} onChange={setImg} />}
+        <Spin spinning={get.loading}>
+            <div className={style1.info}>
+                <div className={style1.items}>
+                    <div className={style1.item}>
+                        <label className={style1.label}>推广页名称:</label>
+                        <span>{data?.title}</span>
+                    </div>
+                </div>
+                <div className={style1.items}>
+                    <div className={style1.item}>
+                        <label className={style1.label}>分享标题:</label>
+                        <span>{data?.shareContentSpec?.shareTitle}</span>
+                    </div>
+                    <div className={style1.item}>
+                        <label className={style1.label}>分享描述:</label>
+                        <span>{data?.shareContentSpec?.shareDescription}</span>
+                    </div>
+                </div>
+            </div>
+
+            <div className={style1.title}>顶部素材设置</div>
+            <BcForm name="顶部素材类型">
+                <Select value={elementType} style={{ width: 180 }} onChange={(e) => { changeType(e) }} placeholder="请选择顶部素材类型">
+                    <Select.Option value="TOP_IMAGE">顶部图片</Select.Option>
+                    <Select.Option value="TOP_SLIDER">顶部轮播图</Select.Option>
+                    <Select.Option value="TOP_VIDEO">顶部视频</Select.Option>
+                </Select>
+            </BcForm>
+            {elementType === "TOP_IMAGE" ? <>
+                <div className={style1.title}>广告位与样式</div>
+                <BcForm name="广告位">
+                    <Radio.Group onChange={(e) => {
+                        setAdLocation(e.target.value)
+                        if (topData?.length > 0) {
+                            setTopData(topData?.map((item: any) => ({ ...item, imageUrl: '' })))
+                        }
+                    }} value={adLocation} size='small'>
+                        <Radio value='sns'>朋友圈信息流</Radio>
+                        <Radio value='gh'>公众号及其他</Radio>
+                    </Radio.Group>
+                </BcForm>
+                {adLocation === 'sns' && <BcForm name="外层样式">
+                    <Select value={outerStyle} style={{ width: 100 }} onChange={(e) => { setImgViewType(e) }}>
+                        <Select.Option value={0}>常规广告</Select.Option>
+                        <Select.Option value={1}>卡片广告</Select.Option>
+                    </Select>
+                </BcForm>}
+            </> : elementType === "TOP_SLIDER" ? <>
+                <div className={style1.title}>素材设置</div>
+                <BcForm name="图片数量">
+                    <Radio.Group onChange={(e) => { setCount(e.target.value) }} value={componentCount} size='small'>
+                        <Radio.Button value={3}>3张</Radio.Button>
+                        <Radio.Button value={4}>4张</Radio.Button>
+                        <Radio.Button value={6}>6张</Radio.Button>
+                    </Radio.Group>
+                </BcForm>
+            </> : elementType === "TOP_VIDEO" ? <>
+                <div className={style1.title}>广告位与样式</div>
+                <BcForm name="广告位">
+                    <Radio.Group onChange={(e) => {
+                        setAdLocation(e.target.value);
+                        if (topData?.length > 0) {
+                            setTopData(topData?.map((item: any) => ({ ...item, videoUrl: '' })))
+                        }
+                        setOuterStyle(0);
+                    }} value={adLocation} size='small'>
+                        <Radio value='sns'>朋友圈信息流</Radio>
+                        <Radio value='gh'>公众号及其他</Radio>
+                    </Radio.Group>
+                </BcForm>
+                {adLocation === 'sns' && <BcForm name="外层样式">
+                    <Select value={outerStyle} style={{ width: 100 }} onChange={(e) => { setImgViewType(e) }}>
+                        <Select.Option value={0}>常规广告</Select.Option>
+                        <Select.Option value={1} disabled>卡片广告</Select.Option>
+                    </Select>
+                </BcForm>}
+            </> : <></>
+            }
+            <BcForm name="排序">
+                <InputNumber placeholder='输入排序' min={0} value={sort} onChange={(e) => { setSort(e) }} />
+            </BcForm>
+
+            <div style={{ height: 1, backgroundColor: '#e2e2e2', marginBottom: 10 }}></div>
+
+            {
+                elementType === "TOP_IMAGE" || elementType === "TOP_SLIDER" || elementType === "TOP_VIDEO" ? <>
+                    <div className={style1.title} style={{ display: 'flex', justifyContent: 'space-between' }}>
+                        <span>
+                            顶部素材
+                            <span style={{ color: 'red' }}>*</span>
+                            <span style={{ fontSize: 14, color: '#696969', marginLeft: 10, fontWeight: 'normal' }}>素材组数量:{topData.length} 组</span>
+                        </span>
+                        <Button type="primary" icon={<PlusOutlined />} onClick={() => { addTopData() }}>添加素材组</Button>
+                    </div>
+                    {elementType === "TOP_IMAGE" ?
+                        <div className={style1.materials}>
+                            {
+                                topData?.map((item: any, index: number) => {
+                                    return <div key={index} className={style1.materialImg}>
+                                        <BcForm
+                                            name={<div>
+                                                <span>素材组{index >= 9 ? (index + 1).toString() : '0' + (index + 1).toString()}</span>
+                                                <div style={{ fontSize: 12, color: '#169BD5', textAlign: 'center' }}>图片({item.imageUrl ? 1 : 0}/1)</div>
+                                            </div>}
+                                            flex='flex-start'
+                                        >
+                                            <div className={style1.box}>
+                                                <div className="adui-form-control">
+                                                    <div className={`upload-img-item ${item.imageUrl ? 'upload-img-item_uploaded' : ''}`}>
+                                                        {item.imageUrl ? <div className="upload-img-item-inner" style={{ backgroundImage: `url(${item.imageUrl})` }}>
+                                                            <div className='upload-img-item-action' onClick={() => { clickUpdateImg(index) }}>
+                                                                <RetweetOutlined />
+                                                            </div>
+                                                        </div>
+                                                            :
+                                                            <div className="upload-img-item-inner" onClick={() => { clickUpdateImg(index) }}>
+                                                                <PlusOutlined />
+                                                            </div>}
+                                                    </div>
+                                                </div>
+                                                {topData?.length > 1 && <Popconfirm placement="topLeft" title="确定删除当前素材组?" onConfirm={() => { del(index) }} okText="Yes" cancelText="No">
+                                                    <Button type="link" danger icon={<DeleteOutlined />} size='small' className={style1.del}></Button>
+                                                </Popconfirm>}
+                                            </div>
+                                        </BcForm>
+                                    </div>
+                                })
+                            }
+                        </div>
+                        : elementType === "TOP_SLIDER" ?
+                            <div className={style1.materials}>
+                                {topData.map((item1: any, index1: number) => {
+                                    return <div className={style1.material} key={index1} onMouseDown={() => { setGroupIndex(index1) }}>
+                                        <BcForm
+                                            name={<div>
+                                                <span>素材组{index1 >= 9 ? (index1 + 1).toString() : '0' + (index1 + 1).toString()}</span>
+                                                <div style={{ fontSize: 12, color: '#169BD5', textAlign: 'center' }}>图片({item1?.imageUrlList?.reduce((prev: number, cur: { url: string }) => cur?.url ? prev + 1 : prev, 0)}/{componentCount})</div>
+                                            </div>}
+                                            flex='flex-start'
+                                        >
+                                            <div className={style1.box}>
+                                                <SortableUlList axis='xy' onSortEnd={onSortEndSlider} useDragHandle>
+                                                    {item1?.imageUrlList?.map((item: any, index: number) => {
+                                                        if (item) {
+                                                            return <SortableItemLi key={`slider-${index}`} index={index} isActive={item1?.activeIndex === index} pureImageUrl={item} click={() => { sliderSelect(index, index1) }} setActiveIndex={() => { activeIndexHancdle(index, index1) }}></SortableItemLi>
+                                                        } else {
+                                                            return <SortableItemNoLi key={`slider-${index}`} index={index} isActive={item1?.activeIndex === index} click={() => { sliderSelect(index, index1) }}></SortableItemNoLi>
+                                                        }
+                                                    })}
+                                                </SortableUlList>
+                                                {topData?.length > 1 && <Popconfirm placement="topLeft" title="确定删除当前素材组?" onConfirm={() => { del(index1) }} okText="Yes" cancelText="No">
+                                                    <Button type="link" danger icon={<DeleteOutlined />} size='small' className={style1.del}></Button>
+                                                </Popconfirm>}
+                                            </div>
+                                        </BcForm>
+                                    </div>
+                                })}
+                            </div> : <>
+                                {/* TOP_VIDEO */}
+                                <div className={style1.materials}>
+                                    {
+                                        topData?.map((item: any, index: number) => {
+                                            return <div key={index} className={style1.materialImg}>
+                                                <BcForm
+                                                    name={<div>
+                                                        <span>素材组{index >= 9 ? (index + 1).toString() : '0' + (index + 1).toString()}</span>
+                                                        <div style={{ fontSize: 12, color: '#169BD5', textAlign: 'center' }}>视频({item.videoUrl ? 1 : 0}/1)</div>
+                                                    </div>}
+                                                    flex='flex-start'
+                                                >
+                                                    <div className={style1.box}>
+                                                        <div className="adui-form-control">
+                                                            <div className={`upload-img-item ${item.pureImageUrl ? 'upload-img-item_uploaded' : ''}`}>
+                                                                {item.videoUrl ? <div className="upload-img-item-inner">
+                                                                    <video src={item.videoUrl} style={{ maxWidth: 70, maxHeight: 70 }}></video>
+                                                                    <div className='upload-img-item-action' onClick={() => { clickUpdateImg(index) }}>
+                                                                        <RetweetOutlined />
+                                                                    </div>
+                                                                </div>
+                                                                    :
+                                                                    <div className="upload-img-item-inner" onClick={() => { clickUpdateImg(index) }}>
+                                                                        <PlusOutlined />
+                                                                    </div>}
+                                                            </div>
+                                                        </div>
+                                                        {topData?.length > 1 && <Popconfirm placement="topLeft" title="确定删除当前素材组?" onConfirm={() => { del(index) }} okText="Yes" cancelText="No">
+                                                            <Button type="link" danger icon={<DeleteOutlined />} size='small' className={style1.del}></Button>
+                                                        </Popconfirm>}
+                                                    </div>
+                                                </BcForm>
+                                            </div>
+                                        })
+                                    }
+                                </div>
+                            </>}
+                </> : <></>
+            }
+        </Spin>
+    </Modal>
+}
+
+export default React.memo(BathLauCopy)

+ 8 - 0
src/pages/launchSystemNew/components/lookLanding/index.less

@@ -0,0 +1,8 @@
+.page{
+    margin: 0 auto;
+    width: 375px;
+    min-height: 667px;
+    background-color: rgb(255, 255, 255);
+    position: relative;
+    box-shadow: 0 3px 5px 0 rgba(0, 0, 0, 3%), 0 6px 15px 0 rgba(0, 0, 0, 4%), 0 0 0 1px hsl(0deg, 0%, 87%, 60%);
+}

+ 320 - 0
src/pages/launchSystemNew/components/lookLanding/index.tsx

@@ -0,0 +1,320 @@
+import { Carousel, Drawer, Spin } from "antd";
+import React, { useEffect, useMemo, useReducer, useState } from "react"
+import { ReactComponent as Topimg } from '@/assets/topimg.svg'
+import { ReactComponent as Img } from '@/assets/img.svg'
+import { ReactComponent as SliderImgSvg } from '@/assets/sliderImgSvg.svg'
+
+import style from './index.less'
+import '../addLandingPage/index1.less'
+import { UserAddOutlined } from "@ant-design/icons";
+import { useModel } from "umi";
+import { getTypeKey } from "@/utils/utils";
+
+type Props = {
+    visible?: boolean,
+    onClose?: () => void,
+    id: number
+}
+
+/**
+ * 查看落地页
+ * @param props 
+ * @returns 
+ */
+function LookLanding(props: Props) {
+    let { visible, onClose, id } = props
+
+    const [pageBackColor, setPageBackColor] = useState<string>('#FFFFFF') // 背景颜色
+    const [content, setContent] = useState<any[]>([])  // 内容
+    const [globalData, setGlobalData] = useState<any>([])  // 浮窗
+    const { get } = useModel('useLaunchAdq.useBdMediaPup')
+
+    useEffect(() => {
+        if (id) {
+            get.run({ sysMediaId: id, mediaType: 'PAGE' }).then(res => {
+                if (res) {
+                    const { pageSpecsList } = res
+                    setPageBackColor(pageSpecsList[0]?.bgColor)
+                    let pageElementsSpecList = pageSpecsList[0]?.pageElementsSpecList
+                    setContent(pageElementsSpecList?.map((item: any) => {
+                        let typeKey = getTypeKey(item?.elementType)
+                        if (typeKey) {
+                            let data = item[typeKey] || {}
+                            return {
+                                elementType: item?.elementType,
+                                ...data
+                            }
+                        }
+                        return item
+                    }))
+                }
+            })
+        }
+    }, [id])
+
+    console.log('content---->', content)
+
+
+    // 顶部组件
+    const topCon = useMemo(() => {
+
+        if (content?.length > 0) {
+            let { imageUrl, elementType, imageUrlList, videoUrl } = content[0]
+            return <>
+                {
+                    elementType === 'TOP_IMAGE' ? <>
+                        <div className={`compt componentType41 ${content[0]?.comptActive && 'comptActive'}`}>
+                            <div className={'componentWrap'}>
+                                <div className={'componentContent'}>
+                                    {
+                                        imageUrl ? <img src={imageUrl} style={{ display: 'block', width: '100%', margin: 0 }} /> : <div className={'default'} style={{ width: 375, height: 300, margin: 0 }}>
+                                            <div className={'defaultIcon'} style={{ marginTop: 80 }}>
+                                                <Topimg />
+                                            </div>
+                                        </div>
+                                    }
+                                </div>
+                            </div>
+                        </div>
+                    </> :
+                        elementType === 'TOP_SLIDER' ? <>
+                            <div className={`compt componentType101 ${content[0]?.comptActive && 'comptActive'}`}>
+                                <div className={'componentWrap'}>
+                                    <div className={'componentContent'}>
+                                        <Carousel autoplay style={{ width: 375, height: 375 }}>
+                                            {imageUrlList?.map((item: any, index: number) => {
+                                                return <div style={{ width: 375, height: 375 }} key={index}><img style={{ width: 375, height: 375 }} src={item} /></div>
+                                            })}
+                                        </Carousel>
+                                    </div>
+                                </div>
+                            </div>
+                        </> :
+                            elementType === 'TOP_VIDEO' ? <>
+                                <div className={`compt componentType61 ${content[0]?.comptActive && 'comptActive'}`}>
+                                    <div className={'componentWrap'}>
+                                        <div className={'componentContent'}>
+                                            {
+                                                videoUrl && <div className="videoPlay">
+                                                    <video src={videoUrl} style={{ display: 'block', width: '100%', margin: 0 }} />
+                                                    <span></span>
+                                                </div>
+                                            }
+                                        </div>
+                                    </div>
+                                </div>
+                            </> : null
+                }
+            </>
+        } else {
+            return null
+        }
+    }, [content])
+
+    const comptCon = () => {
+        if (content?.length === 0) {
+            return null
+        } else {
+            return <div className="page-0" style={globalData?.some((item: any) => item?.elementType === 'FLOAT_BUTTON') ? { paddingBottom: 90, minHeight: 510 } : {}}>
+                {content.map((value: any, index: number) => {
+                    if (value.elementType === 'IMAGE') {
+                        let { imageUrl, paddingTop, paddingBottom } = value
+                        return <div className={`compt componentType41`} key={index}>
+                            <div className={'componentWrap'}>
+                                <div className={'componentContent'}>
+                                    {
+                                        imageUrl ? <img src={imageUrl} style={{ display: 'block', width: '100%', margin: 0, marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px' }} /> : <div className={'default'} style={{ width: 375, height: 222, margin: 0, marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px' }}>
+                                            <div className={'defaultIcon'} style={{ marginTop: 44 }}>
+                                                <Img />
+                                            </div>
+                                        </div>
+                                    }
+                                </div>
+                            </div>
+                        </div>
+                    } else if (value.elementType === 'TEXT') {
+                        let { fontSize, fontColor, textAlignment, text, fontStyle, paddingTop, paddingBottom } = value
+                        return <div className={`compt componentType1`} key={index}>
+                            <div className={'componentWrap'}>
+                                <div className={'componentContent'} style={{ backgroundColor: pageBackColor }}>
+                                    <div className={'text'} style={{ lineHeight: fontSize * 1.5 + 'px', fontSize: Number(fontSize), color: fontColor, textAlign: textAlignment === 0 ? 'left' : textAlignment === 1 ? 'center' : 'right', fontWeight: fontStyle === 0 ? 'normal' : 'bold', maxWidth: '100%', display: 'block', marginLeft: 24, marginRight: 24, marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px' }}>
+                                        <div>{text ?
+                                            text?.split(/[\r\n]/g)?.map((item: any, index: number) => {
+                                                if (item) {
+                                                    return <div key={`item${index}`}>
+                                                        {item?.split(' ')?.map((item1: any, ind: number) => {
+                                                            if (item1) {
+                                                                return <span key={`item1${ind}`}>{item1}</span>
+                                                            } else {
+                                                                return <span key={`item1${ind}`}>&nbsp;</span>
+                                                            }
+                                                        })}
+                                                    </div>
+                                                } else {
+                                                    return <div key={`item${index}`}>&nbsp;</div>
+                                                }
+                                            })
+                                            : '请输入文本内容'}</div>
+                                    </div>
+                                    <div className={'textAreaDiv'} style={{ lineHeight: fontSize * 1.5 + 'px', fontSize: Number(fontSize), margin: '11px 24px', marginLeft: 24, marginRight: 24, marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px' }}>
+                                        <textarea readOnly value={text} className={'textarea'} style={{ color: fontColor, fontWeight: fontStyle === 0 ? 'normal' : 'bold', textAlign: textAlignment === 0 ? 'left' : textAlignment === 1 ? 'center' : 'right', backgroundColor: pageBackColor }}></textarea>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    } else if (value.elementType === 'GH' || value.elementType === 'ENTERPRISE_WX') {
+                        let { paddingTop, paddingBottom, btnTitle, fontColor, btnBgColorTheme, btnBorderColorTheme, btnFontType, useIcon } = value
+                        return <div className={`compt componentType21`} key={index}>
+                            <div className={'componentWrap'}>
+                                <div className={'componentContent'}>
+                                    <div style={{ marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px' }}>
+                                        <div style={{ textAlign: 'center', lineHeight: 0, maxWidth: '100%', margin: '0 92.5px' }}>
+                                            <a style={{
+                                                textDecoration: 'none', color: fontColor || 'rgb(255,255,255)', backgroundColor: btnBgColorTheme || 'rgb(7, 193, 96)',
+                                                border: ['#FFFFFF', '#ffffff', 'rgb(255, 255, 255)'].indexOf(btnBorderColorTheme) !== -1 ? '0px solid rgb(255, 255, 255)' : `2px solid ${btnBorderColorTheme}`, borderRadius: 4, display: 'flex', alignItems: 'center',
+                                                overflow: 'hidden', justifyContent: 'center', whiteSpace: 'pre', fontWeight: btnFontType === 0 ? 'normal' : 'bold',
+                                                height: 40, lineHeight: 40, width: '100%', fontSize: 15
+                                            }}>{useIcon === '1' && <UserAddOutlined style={{ marginRight: 6 }} />}{btnTitle || ''}</a>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    } else if (value?.elementType === 'shelfnew') {
+                        let { paddingTop, paddingBottom, layoutItems, borderColor, bgColor, type, wxad_align, id } = value
+                        if (type === '104') {
+                            let componentItem = layoutItems?.componentItem[0]?.layoutItems?.componentItem
+                            let otherData = componentItem[1]?.layoutItems?.componentItem
+                            return <div className={`compt componentType104`} key={id}>
+                                <div className={'componentWrap'}>
+                                    <div className={'componentContent'}>
+                                        <div className={'shelf listType'} style={{ marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px', marginLeft: 20, marginRight: 20 }}>
+                                            <div className={'shelfItem'} style={{ border: `1px solid ${borderColor || "#e5e5e5"}`, backgroundColor: bgColor || "#ffffff" }}>
+                                                <div className={'shelfItemImg'} style={{ marginLeft: 11.5, marginTop: 11.5 }}>
+                                                    {componentItem[0]?.pureImageUrl ? <img src={componentItem[0]?.pureImageUrl} style={{ display: 'flex', width: '100%', height: '100%' }} /> : <div className={'default'} style={{ width: '100%', height: '100%' }}>
+                                                        <div className={'defaultIcon'} style={{ marginTop: 27, width: 36, height: 36 }}>
+                                                            <Img />
+                                                        </div>
+                                                    </div>}
+                                                </div>
+                                                <div className={'shelfItemContent'} style={{ margin: '12px 20px 0 12px' }}>
+                                                    <p className={'title'} style={{ color: otherData[0]?.fontColor || "#353535", fontSize: 16 }}>{otherData[0]?.content || otherData[0]?.name}</p>
+                                                    <p className={'desc'} style={{ color: otherData[1]?.fontColor || "#B2B2B2" }}>{otherData[1]?.content || otherData[1]?.name}</p>
+                                                    <div
+                                                        className={'btn'}
+                                                        style={{
+                                                            color: otherData[2]?.fontColor || 'rgb(255, 255, 255)',
+                                                            textDecoration: 'none',
+                                                            fontWeight: otherData[2]?.btnFontType === '0' ? 'normal' : 'bold',
+                                                            backgroundColor: otherData[2]?.btnBgColorTheme || "#07C160",
+                                                            border: ['#FFFFFF', '#ffffff', 'rgb(255, 255, 255)'].indexOf(otherData[2]?.btnBorderColorTheme) !== -1 ? '0px solid rgb(255, 255, 255)' : `2px solid ${otherData[2]?.btnBorderColorTheme}`,
+                                                            borderRadius: 4
+                                                        }}
+                                                    >{otherData[2]?.btnTitle}</div>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        } else if (type === '103') {
+                            let componentItem = layoutItems?.componentItem
+                            return <div className={`compt componentType103`} key={id}>
+                                <div className={'componentWrap'}>
+                                    <div className={'componentContent'}>
+                                        <div className='shelf gridType' style={{ marginTop: paddingTop / 2 + 'px', marginBottom: paddingBottom / 2 + 'px', marginLeft: 20 }}>
+                                            {
+                                                componentItem?.map((item: any, index: number) => {
+                                                    let shelfnewItem = item?.layoutItems?.componentItem[0]?.layoutItems?.componentItem
+                                                    return <div className='shelfItem-3q' key={index} style={{ borderWidth: 1, borderStyle: 'solid', borderColor: item?.borderColor, backgroundColor: item?.bgColor || 'rgb(255,255,255)', marginLeft: index === 1 ? 11 : 0 }}>
+                                                        <div className='shelfItemImg' style={{ marginLeft: 5.5, marginTop: 5.5 }}>
+                                                            {shelfnewItem[0]?.pureImageUrl ? <img src={shelfnewItem[0]?.pureImageUrl} style={{ display: 'flex', width: '100%', height: '100%' }} /> : <div className="default" style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
+                                                                <div className={'defaultIcon'} style={{ width: 36, height: 36 }}>
+                                                                    <Img />
+                                                                </div>
+                                                            </div>}
+                                                        </div>
+                                                        <div className='shelfItemContent' style={{ marginLeft: 12, textAlign: wxad_align === 0 ? 'left' : 'center' }}>
+                                                            <p className='title' style={{ color: shelfnewItem[1]?.fontColor || 'rgb(53, 53, 53)', fontSize: 16, marginBottom: 4 }}>{shelfnewItem[1]?.content || shelfnewItem[1]?.name}</p>
+                                                            <p className='desc' style={{ color: shelfnewItem[2]?.fontColor || 'rgb(178, 178, 178)', marginBottom: 14 }}>{shelfnewItem[2]?.content || shelfnewItem[2]?.name}</p>
+                                                            <p className='btn' style={{
+                                                                textDecoration: 'none',
+                                                                fontWeight: shelfnewItem[3]?.btnFontType === '0' ? 400 : 'bold',
+                                                                color: shelfnewItem[3]?.fontColor || 'rgb(255, 255, 255)',
+                                                                backgroundColor: shelfnewItem[3]?.btnBgColorTheme || 'rgb(7,193,96)',
+                                                                borderWidth: shelfnewItem[3]?.borderSize ? Number(shelfnewItem[3]?.borderSize) : 0,
+                                                                borderStyle: 'solid',
+                                                                borderColor: shelfnewItem[3]?.btnBorderColorTheme,
+                                                                borderRadius: 4
+                                                            }}>{shelfnewItem[3]?.btnTitle}</p>
+                                                        </div>
+                                                    </div>
+                                                })
+                                            }
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        } else {
+                            return null
+                        }
+                    } else {
+                        return null
+                    }
+                })}
+                {globalData.map((value: any, index: number) => {
+                    if (value?.elementType === 'FLOAT_BUTTON') {
+                        let { titleColor, descColor, componentItem, iconUrl, title, desc, wxad_styleType } = value
+                        return <div className={`compt componentType134 comptFixedBottom`} key={'floatbutton' + index}>
+                            <div className={'componentWrap'}>
+                                <div className="componentContent">
+                                    <div className="floatButtonWrapper">
+                                        <div className="floatButton">
+                                            {wxad_styleType === '1' && (iconUrl ? <img src={iconUrl} className="floatButtonAvatar" /> : <div className="floatButtonAvatarPlaceholder"></div>)}
+                                            <div className="floatButtonTexts">
+                                                <div className="floatButtonTitle" style={{ color: titleColor || 'rgb(23, 23, 23)' }}>{title || '标题'}</div>
+                                                {(wxad_styleType === '1' || wxad_styleType === '2') && <div className="floatButtonDesc" style={{ color: descColor || 'rgb(76, 76, 76)' }}>{desc || '描述'}</div>}
+                                            </div>
+                                            <div className="floatButtonLink" style={{
+                                                color: componentItem?.fontColor || 'rgb(255,255,255)',
+                                                fontWeight: componentItem?.btnFontType === '0' ? 'normal' : 'bold',
+                                                backgroundColor: componentItem?.btnBgColorTheme || 'rgb(7, 193, 96)',
+                                                width: ((componentItem?.layoutWidth || 160) / 2) + 'px',
+                                                textAlign: 'center',
+                                                overflow: 'hidden',
+                                                whiteSpace: 'pre'
+                                            }}>{componentItem?.btnTitle}</div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    }
+                    return null
+                })}
+            </div>
+        }
+    }
+
+    return <Drawer
+        title="查看"
+        placement="right"
+        closable={false}
+        onClose={() => { onClose && onClose() }}
+        visible={visible}
+        width={420}
+    >
+        <Spin spinning={get.loading}>
+            <div className={style.page} style={{ backgroundColor: pageBackColor || '#FFFFFF' }}>
+                {/* 头部 */}
+                {topCon}
+                {/* 内容*/}
+                <div className={`comptPlaceholder lastChild`} id="comptCon">
+                    {comptCon()}
+                </div>
+            </div>
+        </Spin>
+    </Drawer>
+}
+
+export default React.memo(LookLanding)

+ 22 - 0
src/pages/launchSystemNew/components/selectCloud/index.less

@@ -0,0 +1,22 @@
+.selected{
+    height: 35px;
+    .antImg{
+        display: inline-block;
+        margin-right: 15px;
+        position: relative;
+        border-radius: 4px;
+        .clear{
+            width: 20px;
+            height: 20px;
+            position: absolute;
+            top: -7px;
+            right: -12px;
+            z-index: 10;
+            color: rgb(155, 155, 155);
+            cursor: pointer;
+        }
+        .imgAnt{
+            border-radius: 4px;
+        }
+    }
+}

+ 95 - 0
src/pages/launchSystemNew/components/selectCloud/index.tsx

@@ -0,0 +1,95 @@
+import { Image, message, Modal, Space, Tabs } from "antd"
+import React, { useEffect, useState } from "react"
+import { useModel } from "umi"
+import FileBoxAD from "@/components/FileBoxAD"
+import { CloseCircleFilled } from "@ant-design/icons"
+import style from './index.less'
+
+interface Props {
+    visible?: boolean,
+    onChange?: (content: string[]) => void,
+    onClose?: () => void,
+    sliderImgContent?: { url: string, width?: number, height?: number }[]
+}
+/**
+ * 选择素材
+ * @returns 
+ */
+const SelectCloud: React.FC<Props> = (props) => {
+
+    /**================================**/
+    const { visible, onChange, onClose, sliderImgContent } = props
+    const { state, set, getList } = useModel('useLaunchAdq.useBdMediaPup')
+    const { mediaType, belongUser, parentId, selectItem, num } = state
+    /**================================**/
+
+    useEffect(() => {
+        if (sliderImgContent && sliderImgContent?.length > 0) {
+            setTimeout(() => {
+                set({ selectItem: sliderImgContent })
+            }, 100)
+        } else {
+            setTimeout(() => {
+                set({ selectItem: [] })
+            }, 100)
+        }
+    }, [])
+
+    const handleOk = () => {
+        if (selectItem && selectItem?.length > 0) {
+            onChange && onChange(selectItem)
+        } else {
+            message.error('请选择')
+        }
+    }
+
+    /**加载组件或数据更新执行请求列表 */
+    useEffect(() => {
+        if (belongUser === '1' || belongUser === '0') {
+            getList()
+        }
+    }, [mediaType, belongUser, parentId])
+
+    const clearImg = (id: number) => {
+        set({ selectItem: selectItem?.filter(item => item.id !== id) })
+    }
+
+    return <Modal
+        title={<Space>
+            <span>选择素材</span>
+            {(selectItem && num && selectItem?.length > 0 && num > 1) && <div className={style.selected}>
+                <Image.PreviewGroup>
+                    {
+                        selectItem?.map((item: any, index: number) => (
+                            <div key={index} className={style.antImg}>
+                                <CloseCircleFilled className={style.clear} onClick={() => { clearImg(item?.id) }} />
+                                <Image
+                                    className={style.imgAnt}
+                                    width={35}
+                                    height={35}
+                                    src={item?.url}
+                                />
+                            </div>
+                        ))
+                    }
+
+                </Image.PreviewGroup>
+            </div>}
+        </Space>}
+        visible={visible}
+        onOk={handleOk}
+        onCancel={() => { onClose && onClose() }}
+        width={1200}
+        destroyOnClose
+        maskClosable={false}
+        bodyStyle={{ padding: 0 }}
+    >
+        <Tabs onChange={(activeKey: any) => { set({ belongUser: activeKey }) }} activeKey={belongUser} style={{ margin: '0 24px' }}>
+            <Tabs.TabPane tab={'个人本地'} key={1} />
+            <Tabs.TabPane tab={'公共本地'} key={0} />
+        </Tabs>
+        <FileBoxAD isAll={false} noFile={true} isBack={true} />
+    </Modal>
+}
+
+export default React.memo(SelectCloud)

+ 30 - 4
src/pages/launchSystemNew/material/cloud/index.tsx

@@ -1,16 +1,27 @@
 import FlieBox from '@/components/FileBoxAD'
 import HocError from '@/Hoc/HocError'
 import { Tabs } from 'antd'
-import React, { useEffect } from 'react'
+import React, { useEffect, useRef, useState } from 'react'
 import { useModel } from 'umi'
+import AddLandingPage from '../../components/addLandingPage'
+import BathLauCopy from '../../components/bathLauCopy'
+import LookLanding from '../../components/lookLanding'
 import style from './index.less'
 
 
 const { TabPane } = Tabs;
 
 function Cloud() {
-    const { state, set, getList, init, typeEnum } = useModel('useLaunchAdq.useBdMedia')
+
+    /**********************/
+    const { state, set, getList, init, typeEnum, list } = useModel('useLaunchAdq.useBdMedia')
     const { mediaType, belongUser, parentId } = state
+    const [visible, setVisible] = useState<boolean>(false)
+    const [lookVisible, setLookVisible] = useState<boolean>(false)
+    const [copyVisible, setCopyVisible] = useState<boolean>(false)
+    const [id, setId] = useState<number>(0)
+    /**********************/
+
     /**加载组件或数据更新执行请求列表 */
     useEffect(() => {
         if (belongUser === '1' || belongUser === '0') {
@@ -28,17 +39,32 @@ function Cloud() {
             activeKey={mediaType}
         >
             {
-                ['IMG', 'VIDEO'].map((key: any) => {
+                ['IMG', 'VIDEO', 'PAGE'].map((key: any) => {
                     return <TabPane tab={typeEnum[key]} key={key} style={{ backgroundColor: '#fff', padding: '0 15px' }} >
                         <Tabs onChange={(activeKey: any) => { set({ belongUser: activeKey }) }} activeKey={belongUser}>
                             <TabPane tab={'个人本地'} key={1} />
                             <TabPane tab={'公共本地'} key={0} />
                         </Tabs>
-                        <FlieBox />
+                        <FlieBox setPage={(type, data) => {
+                            setId(data || 0)
+                            if (type === 0 || type === 2) {
+                                setVisible(true)
+                            } else if (type === 1) {
+                                setLookVisible(true)
+                            } else if (type) {
+                                setCopyVisible(true)
+                            }
+                        }} />
                     </TabPane>
                 })
             }
         </Tabs>
+        {/* 落地页新增 复制 */}
+        {visible && <AddLandingPage visible={visible} hideModal={() => setVisible(false)} ajax={list} id={id}/>}
+        {/* 查看落地页 */}
+        {lookVisible && <LookLanding visible={lookVisible} onClose={() => setLookVisible(false)} id={id} />}
+        {/* 批量复制 */}
+        {copyVisible && <BathLauCopy visible={copyVisible} onClose={() => setCopyVisible(false)} id={id} ajaxHome={list}/>}
     </div>
 
 }

+ 379 - 0
src/pages/launchSystemNew/req.ts

@@ -0,0 +1,379 @@
+
+export interface ElementType {
+    elementType: 'TOP_IMAGE' | 'TOP_SLIDER' | 'TOP_VIDEO' | 'IMAGE' | 'SLIDER' | 'VIDEO' | 'TEXT' | 'APP_DOWNLOAD' | 'WEAPP' | 'GH' | 'ENTERPRISE_WX' | 'FLOAT_BUTTON',
+    comptActive?: boolean
+}
+
+/**边距 */
+export interface Padding {
+    paddingTop: number,//边距 顶部轮播不存在
+    paddingBottom: number,//边距 顶部轮播不存在
+}
+/**文本 */
+export interface Text extends ElementType, Padding {
+    text: string,
+    fontSize: 14 | 15 | 16 | 18 | 20 | 24 | 36,
+    fontColor: string,
+    textAlignment: 0 | 1 | 2,  // 取值 0: left, 1: middle, 2: right. 默认 0
+    fontStyle: 0 | 1,  // 0: 常规, 1: 加粗. 默认 0
+}
+/**公众号按钮*/
+export interface GhButton extends ElementType, Padding {
+    fastFollow: 0 | 1// 是否开启一键关注,取值 0: 关闭, 1: 开启. 默认 1
+    btnTitle: string, // use_icon 为 0 时,长度限制 1-10 ;use_icon 为 1 时,长度限制 1-8 ; 默认:关注公众号
+    fontColor: string, // 按钮文案颜色 默认#FFFFFF
+    btnBgColorTheme: string, // 按钮填充色 默认#07C160
+    btnBorderColorTheme: string,  // 边框色,#000000-#FFFFFF,默认#FFFFFF
+    btnFontType: 0 | 1,  // 按钮文案配置 取值 0: 常规, 1: 加粗. 默认 0
+    useIcon: 0 | 1, // 是否使用图标 取值 0: 不使用图标, 1: 使用图标. 默认 0
+}
+
+/**添加商家微信按钮*/
+export interface WxAutoButton extends Padding, ElementType {
+    cropId?: string,             // 绑定的企业 id
+    groupId?: number,            // 客服组 id
+    btnTitle: string,            // 按钮文案,
+    fontColor: string,           // 按钮文案颜色,#000000-#FFFFFF,默认#FFFFFF
+    btnBgColorTheme: string,     // 按钮填充色,#000000-#FFFFFF,默认#07C160
+    btnBorderColorTheme: string, // 边框色,#000000-#FFFFFF,默认#FFFFFF
+    btnFontType: 0 | 1,          // 字体粗细 0常用 1加粗
+    useIcon: 0 | 1,              // 图标开启关闭 取值 0: 不使用图标, 1: 使用图标. 默认 0
+}
+
+/**跳转链接按钮*/
+export interface LinkButton extends Padding {
+    widgetTypeV2: "link",
+    widgetType: "button",
+    type: "21",
+    name: "跳转链接",
+    btnTitle: string,
+    subType: "0",
+    fontSize: "30",
+    fontColor: string,
+    btnType: "0",
+    borderSize: "0",
+    btnHeight: "80",
+    btnBgColorTheme: string,
+    btnBorderColorTheme: string,
+    btnFontType: '0' | '1',
+    btnStyle: "1",
+    cornerRadius: 4,
+    useIcon: '0' | '1',
+    id: "widget_1632641482474_3",
+    mpJumpType: "1",
+    origBtnJumpUrl: string,
+    btnJumpUrl: ""
+}
+
+/**img */
+export interface Img extends ElementType, Padding {
+    imageUrl: string,
+    width: number,
+    height: number,
+}
+/**top_img */
+export interface TopImg extends ElementType {
+    imageUrl: string,
+    width: number,
+    height: number,
+    adLocation: 'sns' | 'gzh',  // sns 代表朋友圈, gzh 代表公众号
+    outerStyle?: 0 | 1, //ad_location 为 sns 时必填. 取值 0: 常规广告, 1: 卡片广告
+}
+/**top_Video */
+export interface TopVideo extends ElementType {
+    videoUrl: string,
+    width: number,
+    height: number,
+    adLocation: 'sns' | 'gzh',
+    outerStyle?: 0 | 1 | 2, //为 sns 时必填 0: 常规广告, 1: 基础卡片广告, 2: 全幅卡片广告
+    usedForOuter?: 0 | 1,  //0:顶部素材用于广告外层, 1:顶部素材不用于广告外层. 默认 0. 仅微信平台账号有效. outer_style=1 时选填
+}
+
+/** 图文复合组件标题类型 */
+export interface ImageTextContentTitle {
+    name: "标题",
+    content: string,
+    fontColor: string,
+    fontSize: "32",
+    layoutHeight: "44",
+    layoutWidth: "410",
+    maxLines: "1",
+    showType: "1",
+    type: "1",
+    wxad_IsSubNode: "1"
+}
+/** 图文复合描述标题类型 */
+export interface ImageTextContentDesc {
+    name: string,  // text = 描述 price = 价格
+    content: string, // 内容
+    fontColor: string,  // 字体颜色
+    fontSize: string,  // 字体大小
+    layoutHeight: "40",
+    layoutWidth: "410",
+    paddingTop: "8",
+    type: "1",
+    wxad_IsSubNode: "1",
+    showType?: "1",  // 选择价格才有
+}
+/** 图文复合组件跳转链接类型 */
+export interface ImageTextContentLink {
+    widgetTypeV2: "link",
+    widgetType: "button",
+    borderSize: "2",
+    btnBgColorTheme: string,  // "#07C160"
+    btnBorderColorTheme: string,   // "#FFFFFF"
+    btnFontType: "0",
+    btnHeight: "60",
+    btnJumpUrl: "",
+    btnStyle: "1",
+    btnTitle: string,   // "了解详情"
+    btnType: "0",
+    fontColor: string,    // "#FFFFFF"
+    fontSize: "26",
+    name: string,   // "跳转链接"
+    origBtnJumpUrl: "",
+    layoutWidth: "152",
+    layoutHeight: "60",
+    paddingBottom: "0",
+    paddingLeft: "0",
+    paddingRight: "0",
+    paddingTop: "28",
+    subType: "0",
+    mpJumpType: "1",
+    type: "21",
+    cornerRadius: "4"
+}
+/** 图文复合组件关注公众号类型 */
+export interface ImageTextContentGh {
+    widgetTypeV2: string, //"gh",
+    widgetType: string, //"button",
+    type: string, //"21",
+    borderSize: string, //"0",
+    name: string, //"商品关注公众号",
+    btnTitle: string,  // "关注公众号"
+    subType: '17' | '1',
+    fontSize: string, //"26",
+    fontColor: string,  // "#FFFFFF"
+    btnType: string, //"0",
+    btnBgColorTheme: string,  // "#07C160"
+    btnBorderColorTheme: string,  // "#FFFFFF"
+    btnFontType: '0' | '1',
+    btnStyle: string, //"1",
+    cornerRadius: number | string, //4,
+    useIcon: string, //"0",
+    field21_1: {
+        origBtnJumpUrl: string,
+        wxad_guide_group_id: string
+    },
+    id: string,   // "widget_1634288817126_3"
+    layoutWidth: string, //"152",
+    layoutHeight: string, //"60",
+    btnHeight?: string,   //"60",
+    paddingTop?: string,  //"28",
+    paddingBottom?: string, //"0",
+    paddingRight?: string,  // "0"
+    paddingLeft?: string,  // "0"
+    horizontalAlignment?: string  // "1"
+}
+/** 图文复合组件添加商家微信按钮 */
+export interface ImageTextContentWxAuto {
+    widgetTypeV2: string, // "enterprise_wx_auto"
+    widgetType: string, // "button"
+    type: string, // "21"
+    name: string, // "商品添加商家微信"
+    btnTitle: string, //"联系商家",
+    subType: string, //"15"
+    fontSize: string,//"26",
+    fontColor: string,//"#FFFFFF",
+    btnType: string, // "0"
+    borderSize: string, // "0"
+    btnBgColorTheme: string, // "#07C160",
+    btnBorderColorTheme: string, //"#FFFFFF",
+    btnFontType: string, // "0"
+    btnStyle: string, // "1"
+    cornerRadius: number | string, //4,
+    useIcon: string, //"0",
+    mpJumpType: string, //"1",
+    origBtnJumpUrl: string,
+    btnJumpUrl: string, //"",
+    corpid: string,//"wp7eXwDAAA4NWRcT0uN05GKQesjmgB0g",
+    groupid: number | null,
+    h5_config_id: string, //"6eeeabc55c3087ea251505ec8560371e",
+    wxapp_config_id: string, //"bee1788a8cbf0bbab69d3bfc5917231e",
+    qrUrl: string,//"https://wework.qpic.cn/wwpic/87591_JQGte4L-Tc21VZW_1645432835/0",
+    needUpdateQrUrl: number, //0,
+    qrExtInfo: string,//"{\"qrType\":1,\"corpid\":\"wp7eXwDAAA4NWRcT0uN05GKQesjmgB0g\",\"groupid\":215982,\"useSet\":0}",
+    serviceType: string, //"1",
+    chatGroupName: string,
+    id: string,
+    layoutWidth: string, //"152",
+    layoutHeight: string, //"60",
+    btnHeight?: string,//"60",
+    paddingTop?: string, //"28",
+    paddingBottom?: string, //"0",
+    paddingRight?: string, //"0",
+    paddingLeft?: string, //"0",
+}
+
+
+/** 图文复合组件单个内容 */
+export interface ImageText1Content {
+    layoutHeight: string,  // "228" 
+    layoutWidth: string,  // "670"
+    type: string,  // "103"
+    subType: string,  // "1"
+    jumpMode: string,  // "btn_jump" "total_jump"
+    layoutItems: {
+        componentItem: [{
+            name: "图片",
+            layoutHeight: "180",
+            layoutWidth: "180",
+            paddingTop: "24",
+            paddingLeft: "24",
+            pureImageUrl: string,
+            type: "41",
+            imageMd5: string,
+            cornerRadius: "4",
+            materialId: "2276131704",
+            wxad_IsSubNode: "1"
+        }, {
+            layoutHeight: "228",
+            layoutWidth: "470",
+            paddingLeft: "24",
+            paddingTop: "24",
+            paddingRight: "40",
+            subType: "0",
+            type: "103",
+            descType: string,  // 文字 text 价格 price
+            wxad_IsSubNode: "1",
+            layoutItems: {
+                componentItem: Array<ImageTextContentTitle | ImageTextContentDesc | ImageTextContentLink | ImageTextContentGh>
+            }
+        }]
+    }
+}
+
+export interface ImageTextOsButtonLink {
+    widgetTypeV2: "link",
+    widgetType: "button",
+    borderSize: "0",
+    btnBgColorTheme: "clear",
+    btnBorderColorTheme: "clear",
+    btnFontType: "0",
+    btnHeight: "228",
+    btnJumpUrl: "",
+    btnStyle: "1",
+    btnTitle: "",
+    btnType: "0",
+    fontColor: "clear",
+    fontSize: "26",
+    name: string,   // "跳转链接"
+    origBtnJumpUrl: string,
+    layoutWidth: "750",
+    layoutHeight: "228",
+    paddingBottom: "0",
+    paddingLeft: "0",
+    paddingRight: "0",
+    paddingTop: "0",
+    subType: "0",
+    fengyeId: "0",
+    fengyeUrl: "",
+    mpJumpType: "1",
+    type: "21",
+    cornerRadius: "4"
+}
+
+export interface ImageTextOsButtonGh {
+    widgetTypeV2: "gh",
+    widgetType: "button",
+    type: "21",
+    name: "商品关注公众号",
+    btnTitle: "",
+    subType: "17",
+    wxad_guide_group_status: false,
+    fontSize: "26",
+    fontColor: "clear",
+    btnType: "0",
+    borderSize: "0",
+    btnHeight: "228",
+    btnBgColorTheme: "clear",
+    btnBorderColorTheme: "clear",
+    btnFontType: "0",
+    btnStyle: "1",
+    paddingTop: "0",
+    paddingBottom: "0",
+    paddingRight: "0",
+    paddingLeft: "0",
+    cornerRadius: 4,
+    useIcon: "0",
+    field21_1: {
+        origBtnJumpUrl: "",
+        wxad_guide_group_id: ""
+    },
+    id: "widget_1634288817126_3",
+    layoutWidth: "750",
+    layoutHeight: "228"
+}
+
+/** 图文复合组件单个 */
+export interface Shelfnew extends Padding {
+    widgetTypeV2: string,   // 图文复合组件 shelfnew
+    widgetType: string,     // 图文复合组件 shelfnew
+    name: string,    // "图文复合组件"
+    type: string,     // 图文复合组件 单个 "104" 多个 "103"
+    id: string,       // "widget_1634190280938_1"
+    layoutHeight: string, // "228"
+    layoutWidth: string,  // "670"
+    // paddingTop: "20",
+    // paddingBottom: "20",
+    // paddingLeft: "40",
+    // paddingRight: "40",
+    borderColor: string,   // "#e5e5e5"
+    bgColor: string,   // #FFFFFF
+    borderSize: number,  // 1
+    cornerRadius: number,  // 8
+    isChooseJump: number,  // 1
+    layoutItems: {
+        componentItem: Array<ImageText1Content | ImageTextOsButtonLink | ImageTextOsButtonGh>
+    }
+}
+
+
+
+/** 悬浮组件 */
+export interface Floatbutton extends Padding {
+    widgetTypeV2: string, //"floatbutton",
+    widgetType: string, //"float_button",
+    type: string, //"134",
+    name: string, // "悬浮组件",
+    wxad_styleType: string, //"2",
+    onlyShowInTimelineAd: string, //"0",
+    backgroundImg: string, //"",
+    backgroundColor: string,//"#F0F0F0",
+    backgroundColorAlpha: string, //"0.96",
+    backgroundBlurEffect: string,  //"1",
+    backgroundBlurEffectColor: string,  //"#F0F0F0",
+    backgroundBlurEffectColorAlpha: string,  //"0.5",
+    titleColor: string,  //"#171717",
+    titleColorAlpha: string,//"1",
+    descColor: string, //"#4c4c4c",
+    descColorAlpha: string, //"0.5",
+    imageMd5: string, // 图片传
+    materialId: string | number, // 有图片传
+    title: string,  // 标题传
+    desc: string,   // 描述
+    isFullClickable: string, //"1",
+    appearPaddingTop: string | number, //"0", 出现方式 进入页面出现 0 滑动页面时出现 100
+    appearPaddingBottom: string | number, //"0", 消失方式 不消失 0  消失 80
+    id: string,
+    componentItem: ImageTextContentWxAuto | ImageTextContentGh,
+    iconUrl?: string
+    comptActive?: boolean
+}
+/**top_slider 顶部轮播*/
+export interface TopSlider extends ElementType {
+    imageUrlList: string[],
+    width: number,
+    height: number,
+    activeIndex?: number
+}

+ 81 - 43
src/services/launchAdq/material.ts

@@ -74,127 +74,165 @@ export async function getFileUrl(params: { type: string, fileType: 'video' | 'im
 }
 
 /** 删除本地素材*/
-export async function delMediaList(params: { id: string, mediaType: 'IMG' | 'VIDEO' }) {
+export async function delMediaList(params: { id: string, mediaType: 'IMG' | 'VIDEO' | "PAGE" }) {
   if (params.mediaType === 'IMG') {
     return request(`${api}/adq/sysMediaImage/${params.id}`, {
       method: 'DELETE',
     });
-  } else {
+  } else if (params.mediaType === 'VIDEO') {
     return request(`${api}/adq/sysMediaVideo/${params.id}`, {
       method: 'DELETE',
     });
+  } else {
+    return request(`${api}/adq/sysWechatComponentPage/${params.id}`, {
+      method: 'DELETE',
+    });
   }
 }
 
 //=========================新接口===================
 // belongUser 0 公共 1 个人
-/**获取本地素材图片列表 */
-export async function bdSysMediaListImg(params: { parentId?: any, belongUser: boolean, pageNum: number, pageSize: number }) {
-  const { belongUser, ...param } = params
-  return request(api + `/adq/sysMediaImage/list/${belongUser}`, {
-    method: 'POST',
-    data: param,
-  });
-}
-/**获取本地素材视频列表 */
-export async function bdSysMediaListVideo(params: { parentId?: any, belongUser: boolean, pageNum: number, pageSize: number }) {
-  const { belongUser, ...param } = params
-  return request(api + `/adq/sysMediaVideo/list/${belongUser}`, {
-    method: 'POST',
-    data: param,
-  });
+/**获取本地素材列表 */
+export async function bdSysMediaList(params: { parentId?: any, belongUser: boolean, pageNum: number, pageSize: number, mediaType: "IMG" | "VIDEO" | "PAGE" }) {
+  const { belongUser, mediaType, ...param } = params
+  if (mediaType === 'IMG') {
+    return request(api + `/adq/sysMediaImage/list/${belongUser}`, {
+      method: 'POST',
+      data: param,
+    });
+  } else if (mediaType === 'VIDEO') {
+    return request(api + `/adq/sysMediaVideo/list/${belongUser}`, {
+      method: 'POST',
+      data: param,
+    });
+  } else {
+    return request(api + `/adq/sysWechatComponentPage/list/${belongUser}`, {
+      method: 'POST',
+      data: param,
+    });
+  }
+  
 }
 /**修改本地素材 */
-export async function bdSysMediaEdit(params: { belongUser: boolean, sysMediaId: number, mediaType: "IMG" | "VIDEO" }) {
+export async function bdSysMediaEdit(params: { belongUser: boolean, sysMediaId: number, mediaType: "IMG" | "VIDEO" | 'PAGE' }) {
   const { belongUser, sysMediaId, mediaType, ...param } = params
   if (mediaType === 'IMG') {
     return request(api + `/adq/sysMediaImage/${sysMediaId}`, {
       method: 'PUT',
       data: param
-    });
-  } else {
+    })
+  } else if (mediaType === 'VIDEO') {
     return request(api + `/adq/sysMediaVideo/${sysMediaId}`, {
       method: 'PUT',
       data: param
-    });
+    })
+  } else {
+    return request(api + `/adq/sysWechatComponentPage/${sysMediaId}`, {
+      method: 'PUT',
+      data: param
+    })
   }
 }
 /**新增本地素材 */
-export async function bdSysMediaAdd(params: { belongUser: boolean, parentId: number, mediaType: "IMG" | "VIDEO" }) {
+export async function bdSysMediaAdd(params: { belongUser: boolean, parentId: number, mediaType: "IMG" | "VIDEO" | 'PAGE' }) {
   const { belongUser, mediaType, ...param } = params
   if (mediaType === 'IMG') {
     return request(api + `/adq/sysMediaImage/${belongUser}`, {
       method: 'POST',
       data: param,
     });
-  } else {
+  } else if (mediaType === 'VIDEO') {
     return request(api + `/adq/sysMediaVideo/${belongUser}`, {
       method: 'POST',
       data: param,
     });
+  } else {
+    return request(api + `/adq/sysWechatComponentPage/${belongUser}`, {
+      method: 'POST',
+      data: param,
+    });
   }
 
 }
 /**删除本地素材 */
-export async function delMedia(params: { sysMediaId: any, mediaType: 'IMG' | 'VIDEO' }) {
+export async function delMedia(params: { sysMediaId: any, mediaType: 'IMG' | 'VIDEO' | 'PAGE' }) {
   if (params.mediaType === 'IMG') {
     return request(`${api}/adq/sysMediaImage/${params.sysMediaId}`, {
       method: 'DELETE',
     });
-  } else {
+  } else if (params.mediaType === 'VIDEO') {
     return request(`${api}/adq/sysMediaVideo/${params.sysMediaId}`, {
       method: 'DELETE',
     });
+  } else {
+    return request(`${api}/adq/sysWechatComponentPage/${params.sysMediaId}`, {
+      method: 'DELETE',
+    });
   }
 }
 /**获取本地素材详情*/
-export async function getMedia(params: { sysMediaId: any, mediaType: 'IMG' | 'VIDEO' }) {
+export async function getMedia(params: { sysMediaId: any, mediaType: 'IMG' | 'VIDEO' | 'PAGE' }) {
   if (params.mediaType === 'IMG') {
     return request(`${api}/adq/sysMediaImage/${params.sysMediaId}`, {
       method: 'GET',
     });
-  } else {
+  } else if (params.mediaType === 'VIDEO') {
     return request(`${api}/adq/sysMediaVideo/${params.sysMediaId}`, {
       method: 'GET',
     });
+  } else {
+    return request(`${api}/adq/sysWechatComponentPage/${params.sysMediaId}`, {
+      method: 'GET',
+    });
   }
 }
 /**获取图片素材文件夹目录树*/
-export async function getFolderTreeImg(props: { belongUser: any }) {
-  let { belongUser } = props
-  return request(api + `/adq/sysMediaImage/folderTree/${belongUser}`, {
-    method: 'GET'
-  })
-}
-/**获取视频素材文件夹目录树*/
-export async function getFolderTreeVideo(props: { belongUser: any }) {
-  let { belongUser } = props
-  return request(api + `/adq/sysMediaVideo/folderTree/${belongUser}`, {
-    method: 'GET'
-  })
+export async function getFolderTree(props: { belongUser: any, mediaType: 'IMG' | 'VIDEO' | 'PAGE' }) {
+  let { belongUser, mediaType } = props
+  if (mediaType === 'IMG') {
+    return request(api + `/adq/sysMediaImage/folderTree/${belongUser}`, {
+      method: 'GET'
+    })
+  } else if (mediaType === 'VIDEO') {
+    return request(api + `/adq/sysMediaVideo/folderTree/${belongUser}`, {
+      method: 'GET'
+    })
+  } else {
+    return request(api + `/adq/sysWechatComponentPage/folderTree/${belongUser}`, {
+      method: 'GET'
+    })
+  }
 }
 /*改变文件位置*/
-export async function editMediaFolder(params: { sysMediaId: number, folderId: number, mediaType: 'IMG' | 'VIDEO' }) {
+export async function editMediaFolder(params: { sysMediaId: number, folderId: number, mediaType: 'IMG' | 'VIDEO' | 'PAGE' }) {
   const { folderId, sysMediaId, mediaType } = params
   if (mediaType === 'IMG') {
     return request(api + `/adq/sysMediaImage/modifyParentFolder/${folderId}/${sysMediaId}`, {
       method: 'PUT',
     });
-  } else {
+  } else if (mediaType === 'VIDEO') {
     return request(api + `/adq/sysMediaVideo/modifyParentFolder/${folderId}/${sysMediaId}`, {
       method: 'PUT',
     });
+  } else {
+    return request(api + `/adq/sysWechatComponentPage/modifyParentFolder/${folderId}/${sysMediaId}`, {
+      method: 'PUT',
+    });
   }
 }
 /** 排序 */
-export async function configSortApi({ sysMediaId, sort, mediaType }: { sysMediaId: number, sort: number, mediaType: 'IMG' | 'VIDEO' }) {
+export async function configSortApi({ sysMediaId, sort, mediaType }: { sysMediaId: number, sort: number, mediaType: 'IMG' | 'VIDEO' | 'PAGE' }) {
   if (mediaType === 'IMG') {
     return request(api + `/adq/sysMediaImage/configMediaSort/${sysMediaId}/${sort}`, {
       method: 'PUT'
     });
-  } else {
+  } else if (mediaType === 'VIDEO') {
     return request(api + `/adq/sysMediaVideo/modifySort/${sysMediaId}/${sort}`, {
       method: 'PUT'
     });
+  } else {
+    return request(api + `/adq/sysWechatComponentPage/modifySort/${sysMediaId}/${sort}`, {
+      method: 'PUT'
+    });
   }
 }

+ 21 - 0
src/utils/utils.ts

@@ -94,4 +94,25 @@ export const getImgSize = (file: RcFile): Promise<{ width: number, height: numbe
       reject()
     }
   })
+}
+
+// 返回落地页组件key
+export const getTypeKey = (key: string): string => {
+  switch (key) {
+      case 'TOP_IMAGE':
+          return 'topImageSpec'
+      case 'TOP_SLIDER':
+          return 'topSliderSpec'
+      case 'TOP_VIDEO':
+          return 'topVideoSpec'
+      case 'IMAGE':
+          return 'imageSpec'
+      case 'TEXT':
+          return 'textSpec'
+      case 'GH':
+          return 'ghSpec'
+      case 'ENTERPRISE_WX':
+          return 'enterpriseWxSpec'
+  }
+  return ''
 }