wjx 4 órája
szülő
commit
3ed81e0a54

+ 1 - 0
build/index.html

@@ -5,6 +5,7 @@
     <link rel="icon" href="%PUBLIC_URL%/qclogo.png" />
     <meta name="viewport" content="width=device-width, initial-scale=1" />
     <meta name="theme-color" content="#000000" />
+    <!-- <script src="https://unpkg.com/mediainfo.js" type="text/javascript"></script> -->
     <meta
       name="description"
       content="Web site created using create-react-app"

+ 1 - 0
package.json

@@ -95,6 +95,7 @@
     "spark-md5": "^3.0.2",
     "streamsaver": "^2.0.6",
     "style-loader": "^3.3.1",
+    "swiper": "^12.1.3",
     "tailwindcss": "^3.0.2",
     "terser-webpack-plugin": "^5.2.5",
     "typescript": "^4.9.5",

+ 9 - 3
src/pages/weComTask/components/materialMould/uploadLoad.tsx

@@ -5,11 +5,12 @@ import React from "react"
 import { saveMediaApi } from "../../API/weMaterial/weMaterial";
 import { useOss } from "@/Hook/useOss";
 import dayjs from 'dayjs'
+import { videoMessage } from "@/utils/compress";
 
 interface Props {
     uploadButton?: JSX.Element
     type?: 'image' | 'video',
-    onChange?: (data: string) => void
+    onChange?: (data: string, videoInfo?: { width: number, height: number, videoLength: number }) => void
 }
 
 const UploadLoad: React.FC<Props> = ({ uploadButton, onChange, type = 'image' }) => {
@@ -21,11 +22,16 @@ const UploadLoad: React.FC<Props> = ({ uploadButton, onChange, type = 'image' })
 
     const upload = (file: RcFile) => {
         const name = dayjs().valueOf().toString()
-        ossUpload.run(file, name).then(res => {
+        ossUpload.run(file, name).then(async (res) => {
             if (res?.data) {
                 const [fileName, nameSuffix] = file.name.split('.')
                 saveMedia.run({ fileName: name || fileName, mediaType: type || 'image', suffix: nameSuffix, url: res?.data })
-                onChange?.(res.data)
+                if (type === 'video') {
+                    const videoInfo: { width: number, height: number, videoLength: number }[] = await videoMessage([file])
+                    onChange?.(res.data, videoInfo[0])
+                } else {
+                    onChange?.(res.data)
+                }
             }
         })
     }

+ 0 - 1
src/pages/weComTask/page/businessPlan/create/components/friends/strategy.tsx

@@ -6,7 +6,6 @@ import { PlusOutlined, DeleteOutlined } from '@ant-design/icons'
 import SendTimeSet from '@/pages/weComTask/components/sendTimeSet';
 import style from '../massSending/index.less';
 import dayjs from 'dayjs';
-import FilterUser from '@/pages/weComTask/components/filterUser';
 
 /**
  * 朋友圈策略配置

+ 16 - 14
src/pages/weComTask/page/miniProgramPages/center.tsx

@@ -9,7 +9,7 @@ const Center: React.FC = () => {
 
     /**************************************/
     const { pageSpecs, setPageSpecs } = useContext(DispatchMiniPageCreate)!;
-    const { bgColor } = pageSpecs
+    const { bgColor, pageType } = pageSpecs
     /**************************************/
 
     const installActiveNull = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
@@ -22,20 +22,22 @@ const Center: React.FC = () => {
     return <Col flex="auto" className={style.center} onClick={installActiveNull}>
         <div className={style.page} style={{ backgroundColor: bgColor || '#FFFFFF' }}>
             <div><CenterTop /></div>
-            <div className={`comptPlaceholder lastChild`} id="comptCon">
-                <CenterCompt />
-            </div>
-            <div className={style.sidebar}>
-                <div>
-                    <ColorPicker
-                        onChange={(_, css) => {
-                            setPageSpecs({ ...pageSpecs, bgColor: css })
-                        }}
-                        value={bgColor}
-                    />
-                    <div style={{ marginTop: 4 }}>背景</div>
+            {pageType === 'LANDING_PAGE' && <>
+                <div className={`comptPlaceholder lastChild`} id="comptCon">
+                    <CenterCompt />
                 </div>
-            </div>
+                <div className={style.sidebar}>
+                    <div>
+                        <ColorPicker
+                            onChange={(_, css) => {
+                                setPageSpecs({ ...pageSpecs, bgColor: css })
+                            }}
+                            value={bgColor}
+                        />
+                        <div style={{ marginTop: 4 }}>背景</div>
+                    </div>
+                </div>
+            </>}
         </div>
     </Col>
 }

+ 47 - 5
src/pages/weComTask/page/miniProgramPages/centerTop.tsx

@@ -2,9 +2,10 @@ import React, { useContext } from "react"
 import { DispatchMiniPageCreate } from "./drawerMini";
 import { useDrop } from "ahooks";
 import './global.less'
-import { topImageContent, topVideoContent } from "./const";
+import { topImageContent, topShortVideoContent, topVideoContent } from "./const";
 import { DeleteOutlined } from '@ant-design/icons';
-
+import { Swiper, SwiperSlide } from 'swiper/react'
+import 'swiper/css'
 import { ReactComponent as TopNullBack } from '../../../../public/svg/topNullBack.svg'
 import { ReactComponent as TopImgSvg } from '../../../../public/svg/topimg.svg'
 import { ReactComponent as EditSvg } from '../../../../public/svg/edit.svg'
@@ -16,8 +17,8 @@ import UploadLoad from "../../components/materialMould/uploadLoad";
 const CenterTop: React.FC = () => {
 
     /**************************************/
-    const { dragging, pageSpecs, setDragging, setPageSpecs, installActive, setTopUrl } = useContext(DispatchMiniPageCreate)!;
-    const { pageElementsSpecList, globalElementsSpecList } = pageSpecs
+    const { dragging, pageSpecs, setDragging, setPageSpecs, installActive, setTopUrl, setTopShortVideoUrl } = useContext(DispatchMiniPageCreate)!;
+    const { pageElementsSpecList, globalElementsSpecList, pageType } = pageSpecs
     const topSpec = pageElementsSpecList?.[0]
     /**************************************/
 
@@ -30,6 +31,8 @@ const CenterTop: React.FC = () => {
                 newPageElementsSpecList[0] = { ...topImageContent, comptActive: true }
             } else if (key === 'TOP_VIDEO') {
                 newPageElementsSpecList[0] = { ...topVideoContent, comptActive: true }
+            } else if (key === 'TOP_SHORT_VIDEO') {
+                newPageElementsSpecList[0] = { ...topShortVideoContent, comptActive: true }
             } else {
                 return
             }
@@ -113,8 +116,38 @@ const CenterTop: React.FC = () => {
                         </div>
                     </section>
                 </div>
+            case "TOP_SHORT_VIDEO":
+                return <div className={`compt componentType62 ${topSpec?.comptActive && 'comptActive'}`} onClick={(e) => { installActive(e, 0) }}>
+                    <Swiper
+                        direction="vertical"
+                        slidesPerView={1}
+                        spaceBetween={0}
+                        style={{ height: '750px' }}
+                    >
+                        {topSpec?.data?.map((item, index) => <SwiperSlide key={index}>
+                            <div className={'componentWrap'}>
+                                <div className={'componentContent'} style={{ lineHeight: 'normal' }}>
+                                    {item?.url ? <div className="videoPlay">
+                                        <VideoNews src={item.url} style={{ display: 'block', width: '100%', margin: 0, height: 750 }} maskImgStyle={{ position: 'absolute', top: '50%', left: '50%', width: 40, height: 40, transform: 'translate(-50%, -50%)', zIndex: 10 }} />
+                                    </div> : <div className={'default'} style={{ width: 375, height: 750, margin: 0 }}>
+                                        <div className={'defaultIcon'} style={{ marginTop: 280 }}>
+                                            <TopVideoSvg />
+                                        </div>
+                                    </div>}
+                                </div>
+                            </div>
+                            {!item?.url && <div className={'comptUpload'} style={{ margin: 0 }}><UploadLoad
+                                type='video'
+                                uploadButton={<button style={{ marginTop: 360 }} className={'comptEditButton'}>上传视频</button>}
+                                onChange={(value, videoInfo) => {
+                                    setTopShortVideoUrl(value, videoInfo, index)
+                                }}
+                            /></div>}
+                        </SwiperSlide>)}
+                    </Swiper>
+                </div>
             default:
-                return <div className={`compt topComptArea ${dragging ? 'dragging' : ''}`} {...dropProps}>
+                return pageType === 'LANDING_PAGE' ? <div className={`compt topComptArea ${dragging ? 'dragging' : ''}`} {...dropProps}>
                     <TopNullBack />
 
                     {dragging ? <div className="topAreaTitle" style={{ marginTop: 30 }}>
@@ -123,6 +156,15 @@ const CenterTop: React.FC = () => {
                         <p className={'topAreaTitle'}>顶部组件区</p>
                         <div className={'desc'}>在左上方,选择顶部组件添加到此处</div>
                     </>}
+                </div> : <div style={{ minHeight: 648 }} className={`compt topComptArea topShortVideo ${dragging ? 'dragging' : ''}`} {...dropProps}>
+                    <TopNullBack />
+
+                    {dragging ? <div className="topAreaTitle" style={{ marginTop: 30 }}>
+                        拖至此处
+                    </div> : <>
+                        <p className={'topAreaTitle'}>短视频组件区</p>
+                        <div className={'desc'}>在左上方,选择顶部组件添加到此处</div>
+                    </>}
                 </div>
         }
     }

+ 11 - 0
src/pages/weComTask/page/miniProgramPages/const.ts

@@ -11,6 +11,17 @@ export const topVideoContent: TASK_MINI_PAGE_CREATE.TopVideo = {
     url: ''
 }
 
+// 顶部短视频
+export const topShortVideoContent: TASK_MINI_PAGE_CREATE.TopShortVideo = {
+    elementType: 'TOP_SHORT_VIDEO',
+    url: '',
+    data: [{
+        url: '',
+        duration: 0
+    }],
+    tipText: ''
+}
+
 // 图片内容
 export const imgContent: TASK_MINI_PAGE_CREATE.Image = {
     elementType: 'IMAGE',

+ 33 - 3
src/pages/weComTask/page/miniProgramPages/drawerMini.tsx

@@ -5,8 +5,6 @@ import Left from "./left"
 import Center from "./center"
 import Right from "./right"
 import { SwapRightOutlined } from '@ant-design/icons';
-import { useAjax } from "@/Hook/useAjax"
-import { createLandingPageApi, editLandingPageApi } from "../../API/miniProgramPages"
 import Submit from "./submit"
 
 interface Props {
@@ -27,6 +25,7 @@ const DrawerMini: React.FC<Props> = ({ visible, onChange, onClose, groupList, co
     const { message } = App.useApp();
     const [pageSpecs, setPageSpecs] = useState<TASK_MINI_PAGE_CREATE.PageSpecsProps>({
         bgColor: '#FFFFFF',
+        pageType: 'LANDING_PAGE',
         pageElementsSpecList: [{ elementType: 'empty' }]
     })
     const [dragging, setDragging] = useState<string | null>(null);
@@ -64,6 +63,23 @@ const DrawerMini: React.FC<Props> = ({ visible, onChange, onClose, groupList, co
         setPageSpecs({ ...pageSpecs, pageElementsSpecList: newPageElementsSpecList })
     }
 
+    // 设置顶部短视频素材链接
+    const setTopShortVideoUrl = (url: string, videoInfo: { width: number, height: number, videoLength: number }, index: number) => {
+        const { pageElementsSpecList } = pageSpecs
+        const newPageElementsSpecList = JSON.parse(JSON.stringify(pageElementsSpecList))
+        if (index === -1) {
+            // Handle the case where we want to add a new video item
+            const data = newPageElementsSpecList[0].data || [];
+            data.push({ url, duration: videoInfo.videoLength || 0 });
+            newPageElementsSpecList[0].data = data;
+        } else {
+            newPageElementsSpecList[0].data[index].url = url
+            newPageElementsSpecList[0].data[index].duration = videoInfo?.videoLength || 0
+        }
+
+        setPageSpecs({ ...pageSpecs, pageElementsSpecList: newPageElementsSpecList })
+    }
+
     // 设置内容
     const setCompt = (key: string, value: any) => {
         const { pageElementsSpecList } = pageSpecs
@@ -100,6 +116,18 @@ const DrawerMini: React.FC<Props> = ({ visible, onChange, onClose, groupList, co
                     message.error('顶部组件请上传素材')
                     return true
                 }
+            } else if (item.elementType === 'TOP_SHORT_VIDEO') {
+                if (item?.data?.every(i => i.url)) {
+                    if (item?.url && item?.tipText) {
+                        return false
+                    } else {
+                        message.error('请完善转化链接和提示语')
+                        return true
+                    }
+                } else {
+                    message.error('顶部短视频组件请上传素材')
+                    return true
+                }
             } else if (item.elementType === 'IMAGE') {
                 if (item.url) {
                     return false
@@ -162,7 +190,9 @@ const DrawerMini: React.FC<Props> = ({ visible, onChange, onClose, groupList, co
                 installActive,
                 setTopUrl,
                 setCompt,
-                setGlobal
+                setGlobal,
+                setTopShortVideoUrl,
+                isNewAdd: !(initialValues?.id || initialValues?.isCopy)
             }}
         >
             <div className={style.boxCont}>

+ 4 - 0
src/pages/weComTask/page/miniProgramPages/global.less

@@ -344,6 +344,10 @@
     margin-top: 35px;
   }
 
+  &.topShortVideo svg {
+    margin-top: 210px;
+  }
+
   .desc {
     font-size: 12px;
     line-height: 20px;

+ 47 - 14
src/pages/weComTask/page/miniProgramPages/index.tsx

@@ -14,6 +14,7 @@ import { randomString } from "@/utils/utils";
 import DrawerMini from "./drawerMini";
 import { getCorpUserListApi, getGenerateUrllinkApi } from "../../API/global";
 import ShowQrCode from "./showQrCode";
+import { url } from "inspector";
 
 
 /**
@@ -66,26 +67,57 @@ const MiniProgramPages: React.FC = () => {
     }, [queryParamsNew])
 
     const handleEdit = (d: Record<string, any>, isCopy?: boolean) => {
-        const { content, name, id, projectGroupIdList, bookName, remark, corpId, corpUserList, previewAppId } = d
-        const { bgColor, pageName, elementsSpecList } = JSON.parse(content)
+        const { content, name, id, projectGroupIdList, bookName, remark, corpId, corpUserList, previewAppId, type, h5VisitSwitch } = d
+
         const pageElementsSpecList: TASK_MINI_PAGE_CREATE.PageElementsSpecListProps = []
         const globalElementsSpecList: TASK_MINI_PAGE_CREATE.GlobalElementsSpecListProps = []
-        elementsSpecList.forEach(item => {
-            if (item.elementType === 'FLOAT_BUTTON') {
-                globalElementsSpecList.push({ ...item, comptActive: false })
-            } else {
-                pageElementsSpecList.push({ ...item, comptActive: false })
+
+        let newPageName = ''
+        let pageSpecs: Partial<TASK_MINI_PAGE_CREATE.PageSpecsProps> = {}
+        if (type === 'LANDING_PAGE') {
+            const { bgColor, pageName, elementsSpecList } = JSON.parse(content)
+            elementsSpecList.forEach(item => {
+                if (item.elementType === 'FLOAT_BUTTON') {
+                    globalElementsSpecList.push({ ...item, comptActive: false })
+                } else {
+                    pageElementsSpecList.push({ ...item, comptActive: false })
+                }
+            })
+            pageSpecs = {
+                bgColor,
+                pageElementsSpecList,
+                globalElementsSpecList,
+                pageType: type || 'LANDING_PAGE'
+            }
+            newPageName = pageName
+        } else {
+            const data = JSON.parse(content)
+            newPageName = data?.[0]?.user?.nick || ''
+            const { url, tipText } = data?.[0] || {}
+            const newData = data.map(item => {
+                return {
+                    url: item.v_url,
+                    duration: item.du
+                }
+            })
+            pageElementsSpecList.push({
+                elementType: 'TOP_SHORT_VIDEO',
+                data: newData,
+                url,
+                tipText
+            })
+            pageSpecs = {
+                bgColor: '#000000',
+                pageElementsSpecList,
+                pageType: type || 'VIDEO'
             }
-        })
-        const pageSpecs = {
-            bgColor,
-            pageElementsSpecList,
-            globalElementsSpecList
         }
 
+
+
         const newInitialValues = {
             name,
-            pageName,
+            pageName: newPageName,
             bookName,
             corpId,
             id,
@@ -94,7 +126,8 @@ const MiniProgramPages: React.FC = () => {
             corpUserIdList: corpUserList.map(item => item.corpUserId),
             projectGroupIdList: projectGroupIdList.map(item => item.projectGroupId),
             remark,
-            isCopy: isCopy || false
+            isCopy: isCopy || false,
+            h5VisitSwitch: h5VisitSwitch || false
         }
         console.log(newInitialValues)
         if (isCopy) {

+ 38 - 19
src/pages/weComTask/page/miniProgramPages/left.tsx

@@ -1,4 +1,4 @@
-import { Col } from "antd"
+import { Col, Radio } from "antd"
 import React, { useContext } from "react"
 import style from './index.less'
 import { useDrag } from "ahooks";
@@ -14,7 +14,8 @@ import DragItem from "./dragItem";
 const Left: React.FC = () => {
 
     /**************************************/
-    const { setDragging, setDraggingCon, pageSpecs: { pageElementsSpecList, globalElementsSpecList } } = useContext(DispatchMiniPageCreate)!;
+    const { setDragging, setDraggingCon, pageSpecs, setPageSpecs, isNewAdd } = useContext(DispatchMiniPageCreate)!;
+    const { pageElementsSpecList, globalElementsSpecList, pageType } = pageSpecs
     /**************************************/
 
     const getDragProps = useDrag({
@@ -38,23 +39,41 @@ const Left: React.FC = () => {
 
 
     return <Col flex="320px" className={style.right}>
-        <div className={style.title}>顶部组件</div>
-        <div className={style.assembly}>
-            <DragItem icon={<TopImgSvg />} title="图片" dragProps={getDragProps(`TOP_IMAGE`)} disabled={pageElementsSpecList?.[0].elementType !== 'empty'} />
-            <DragItem icon={<TopVideoSvg />} title="视频" dragProps={getDragProps(`TOP_VIDEO`)} disabled={pageElementsSpecList?.[0].elementType !== 'empty'} />
-        </div>
-
-        <div className={style.title}>基础组件</div>
-        <div className={style.assembly}>
-            <DragItem icon={<ImgSvg />} title="图片" dragProps={getDragPropsCon(`IMAGE`)} />
-            <DragItem icon={<TextSvg />} title="文字" dragProps={getDragPropsCon(`TEXT`)} />
-        </div>
-
-        <div className={style.title}>营销组件</div>
-        <div className={style.assembly}>
-            <DragItem icon={<FloatbuttonSvg />} title="悬浮组件" dragProps={getDragPropsCon(`FLOAT_BUTTON`)} disabled={globalElementsSpecList?.some(item => item.elementType === 'FLOAT_BUTTON')} />
-            <DragItem icon={<WxAutoSvg />} title="添加商家微信" dragProps={getDragPropsCon(`QR_CODE`)} />
-        </div>
+        <div className={style.title}>落地页类型</div>
+        <Radio.Group style={{ marginBottom: 15 }} disabled={!isNewAdd} value={pageType} buttonStyle="solid" onChange={(e) => {
+            setPageSpecs({ 
+                bgColor: e.target.value === 'VIDEO' ? '#000000' : '#FFFFFF',
+                pageType: e.target.value,
+                pageElementsSpecList: [{ elementType: 'empty' }]
+            });
+        }}>
+            <Radio.Button value="LANDING_PAGE">小说落地页</Radio.Button>
+            <Radio.Button value="VIDEO">视频落地页</Radio.Button>
+        </Radio.Group>
+        {pageType === 'LANDING_PAGE' ? <>
+            <div className={style.title}>顶部组件</div>
+            <div className={style.assembly}>
+                <DragItem icon={<TopImgSvg />} title="图片" dragProps={getDragProps(`TOP_IMAGE`)} disabled={pageElementsSpecList?.[0].elementType !== 'empty'} />
+                <DragItem icon={<TopVideoSvg />} title="视频" dragProps={getDragProps(`TOP_VIDEO`)} disabled={pageElementsSpecList?.[0].elementType !== 'empty'} />
+            </div>
+
+            <div className={style.title}>基础组件</div>
+            <div className={style.assembly}>
+                <DragItem icon={<ImgSvg />} title="图片" dragProps={getDragPropsCon(`IMAGE`)} />
+                <DragItem icon={<TextSvg />} title="文字" dragProps={getDragPropsCon(`TEXT`)} />
+            </div>
+
+            <div className={style.title}>营销组件</div>
+            <div className={style.assembly}>
+                <DragItem icon={<FloatbuttonSvg />} title="悬浮组件" dragProps={getDragPropsCon(`FLOAT_BUTTON`)} disabled={globalElementsSpecList?.some(item => item.elementType === 'FLOAT_BUTTON')} />
+                <DragItem icon={<WxAutoSvg />} title="添加商家微信" dragProps={getDragPropsCon(`QR_CODE`)} />
+            </div>
+        </> : <>
+            <div className={style.title}>顶部组件</div>
+            <div className={style.assembly}>
+                <DragItem icon={<TopVideoSvg />} title="短视频播放" dragProps={getDragProps(`TOP_SHORT_VIDEO`)} disabled={pageElementsSpecList?.[0].elementType !== 'empty'} />
+            </div>
+        </>}
     </Col>
 }
 

+ 76 - 2
src/pages/weComTask/page/miniProgramPages/right.tsx

@@ -5,12 +5,12 @@ import './global.less'
 import { DispatchMiniPageCreate } from './drawerMini';
 import { RetweetOutlined, PlusOutlined, AlignLeftOutlined, AlignCenterOutlined, AlignRightOutlined, MenuOutlined } from '@ant-design/icons';
 import UploadLoad from '../../components/materialMould/uploadLoad';
-import { replaceSpecialTxt } from '@/utils/utils';
+import { getVideoImgUrl, replaceSpecialTxt } from '@/utils/utils';
 
 const Right: React.FC = () => {
 
     /**************************************/
-    const { pageSpecs, setTopUrl, setCompt, setGlobal } = useContext(DispatchMiniPageCreate)!;
+    const { pageSpecs, setTopUrl, setCompt, setGlobal, setTopShortVideoUrl } = useContext(DispatchMiniPageCreate)!;
     const { pageElementsSpecList, globalElementsSpecList } = pageSpecs
     const activeSpec = pageElementsSpecList?.find((item: any) => item?.comptActive) || globalElementsSpecList?.find((item: any) => item?.comptActive)
     /**************************************/
@@ -48,6 +48,80 @@ const Right: React.FC = () => {
                 <div className="caption section">
                     <div className="caption-title">顶部组件:视频</div>
                 </div>
+            </div> : activeSpec?.elementType === 'TOP_SHORT_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" style={{ alignItems: 'flex-start' }}>
+                        <div className="adui-form-label">视频素材</div>
+                        <div className="adui-form-control" style={{ display: 'flex', flexWrap: 'wrap', gap: 10 }}>
+                            {activeSpec?.data?.map((item: any, index: number) => <UploadLoad
+                                key={index}
+                                type='video'
+                                uploadButton={<div className={`upload-img-item ${item?.url ? 'upload-img-item_uploaded' : ''}`}>
+                                    {item?.url ? <div
+                                        className="upload-img-item-inner"
+                                        style={{ backgroundImage: `url(${getVideoImgUrl(item?.url)})` }}
+                                    >
+                                        <div className='upload-img-item-action'>
+                                            <RetweetOutlined />
+                                        </div>
+                                    </div> : <div className="upload-img-item-inner">
+                                        <PlusOutlined />
+                                    </div>}
+                                </div>}
+                                onChange={(value, videoInfo) => {
+                                    setTopShortVideoUrl(value, videoInfo, index)
+                                }}
+                            />)}
+                            {activeSpec?.data?.every(item => item?.url) && <UploadLoad
+                                type='video'
+                                uploadButton={<div className={`upload-img-item`}>
+                                    <div className="upload-img-item-inner">
+                                        <PlusOutlined />
+                                    </div>
+                                </div>}
+                                onChange={(value, videoInfo) => {
+                                    setTopShortVideoUrl(value, videoInfo, -1)
+                                }}
+                            />}
+                        </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">
+                            <UploadLoad
+                                type='image'
+                                uploadButton={<div className={`upload-img-item ${activeSpec?.url ? 'upload-img-item_uploaded' : ''}`}>
+                                    {activeSpec?.url ? <div className="upload-img-item-inner" style={{ backgroundImage: `url(${activeSpec?.url ? activeSpec?.url : ""})` }}>
+                                        <div className='upload-img-item-action'>
+                                            <RetweetOutlined />
+                                        </div>
+                                    </div> : <div className="upload-img-item-inner">
+                                        <PlusOutlined />
+                                    </div>}
+                                </div>}
+                                onChange={(value) => {
+                                    setCompt('url', value)
+                                }}
+                            />
+                        </div>
+                    </div>
+                </div>
+                <div className="form section">
+                    <div className="form-caption">转化文案设置</div>
+                    <Input.TextArea
+                        placeholder={`请输入(不包含<>&'"/\以及TAB、换行、回车键)`}
+                        autoSize={{ minRows: 4, maxRows: 6 }}
+                        value={activeSpec?.tipText}
+                        onChange={(e) => { setCompt('tipText', replaceSpecialTxt(e.target.value)) }}
+                    />
+                </div>
             </div> : activeSpec?.elementType === 'TEXT' ? <div className="widget">
                 <div className="caption section">
                     <div className="caption-title">文本</div>

+ 61 - 31
src/pages/weComTask/page/miniProgramPages/submit.tsx

@@ -38,37 +38,61 @@ const Submit: React.FC<Props> = ({ corpList, visible, pageSpecs, onChange, onClo
 
     const handleOk = () => {
         form.validateFields().then(valid => {
-            const { bgColor, globalElementsSpecList, pageElementsSpecList } = pageSpecs
-            let pageSpecsList = []
-            let qrCodeFloatList: { siteId: number, urlList: string[] }[] = []
-            if (globalElementsSpecList?.length > 0) {
-                pageSpecsList = [...pageElementsSpecList.map(item => {
-                    delete (item as any)?.comptActive
-                    return item
-                }), ...globalElementsSpecList.map(item => {
-                    delete item.comptActive
-                    return item
-                })]
-                const floatButton = globalElementsSpecList?.find(item => item.elementType === 'FLOAT_BUTTON')
-                if (floatButton) {
-                    qrCodeFloatList = [{
-                        siteId: floatButton.id,
-                        urlList: floatButton.qrCodeFloatList
-                    }]
+            const { bgColor, globalElementsSpecList, pageElementsSpecList, pageType } = pageSpecs
+            let params: Record<string, unknown> = {}
+            if (pageType === "LANDING_PAGE") {
+                let pageSpecsList = []
+                let qrCodeFloatList: { siteId: number, urlList: string[] }[] = []
+                if (globalElementsSpecList?.length > 0) {
+                    pageSpecsList = [...pageElementsSpecList.map(item => {
+                        delete (item as any)?.comptActive
+                        return item
+                    }), ...globalElementsSpecList.map(item => {
+                        delete item.comptActive
+                        return item
+                    })]
+                    const floatButton = globalElementsSpecList?.find(item => item.elementType === 'FLOAT_BUTTON')
+                    if (floatButton) {
+                        qrCodeFloatList = [{
+                            siteId: floatButton.id,
+                            urlList: floatButton.qrCodeFloatList
+                        }]
+                    }
+                }
+                const qrCodeList = pageElementsSpecList.filter(item => item.elementType === 'QR_CODE')?.map((item: TASK_MINI_PAGE_CREATE.QrCode) => ({ siteId: item.id, urlList: item.imageList }))
+                const { pageName, ...v } = valid
+                params = {
+                    content: JSON.stringify({
+                        pageName,
+                        bgColor,
+                        elementsSpecList: pageSpecsList
+                    }),
+                    qrCodeFloatList,
+                    qrCodeList,
+                    type: 'LANDING_PAGE',
+                    ...v
+                }
+            } else {
+                const { pageName, ...v } = valid
+                const { data, url, tipText } = pageElementsSpecList[0] as TASK_MINI_PAGE_CREATE.TopShortVideo
+                params = {
+                    content: JSON.stringify(data.map(item => {
+                        return {
+                            v_url: item.url,
+                            du: item.duration,
+                            user: {
+                                hurl: "https://appresource.zanxiangnet.com/old-videos/6975eba200000150123b9894/user_avatar.jpg",
+                                nick: pageName
+                            },
+                            url,
+                            tipText
+                        }
+                    })),
+                    type: 'VIDEO',
+                    ...v
                 }
             }
-            const qrCodeList = pageElementsSpecList.filter(item => item.elementType === 'QR_CODE')?.map((item: TASK_MINI_PAGE_CREATE.QrCode) => ({ siteId: item.id, urlList: item.imageList }))
-            const { pageName, ...v } = valid
-            const params = {
-                content: JSON.stringify({
-                    pageName,
-                    bgColor,
-                    elementsSpecList: pageSpecsList
-                }),
-                qrCodeFloatList,
-                qrCodeList,
-                ...v
-            }
+
             if (initialValues?.id) {
                 editLandingPage.run({ ...params, id: initialValues.id }).then(res => {
                     if (res?.data) {
@@ -110,7 +134,7 @@ const Submit: React.FC<Props> = ({ corpList, visible, pageSpecs, onChange, onClo
                 message.error(errorFields?.[0]?.errors?.[0])
             }}
             onFinish={handleOk}
-            initialValues={initialValues}
+            initialValues={initialValues || { h5VisitSwitch: false }}
         >
             <Form.Item label={<strong>落地页名称</strong>} name="name" rules={[{ required: true, message: '请输入落地页名称!' }]}>
                 <Input placeholder="请输入落地页名称" />
@@ -127,7 +151,7 @@ const Submit: React.FC<Props> = ({ corpList, visible, pageSpecs, onChange, onClo
             <Form.Item label={<strong>小程序预览AppId</strong>} name="previewAppId">
                 <Input placeholder="请输入小程序预览AppId" />
             </Form.Item>
-            
+
             <Form.Item label={<strong>企微主体</strong>} name="corpId" rules={[{ required: true, message: '请选择主体!' }]}>
                 <Select
                     placeholder='请选择主体'
@@ -166,6 +190,12 @@ const Submit: React.FC<Props> = ({ corpList, visible, pageSpecs, onChange, onClo
                     }
                 />
             </Form.Item>
+            <Form.Item label={<strong>开启H5访问限制?</strong>} name="h5VisitSwitch" rules={[{ required: true, message: '请选择开启H5访问限制!' }]}>
+                <Radio.Group buttonStyle="solid">
+                    <Radio.Button value={true}>是</Radio.Button>
+                    <Radio.Button value={false}>否</Radio.Button>
+                </Radio.Group>
+            </Form.Item>
         </Form>
     </Modal>
 }

+ 19 - 2
src/pages/weComTask/page/miniProgramPages/tableConfig.tsx

@@ -1,5 +1,5 @@
 import { ColumnsType } from "antd/es/table"
-import { Flex, Popconfirm, Image } from "antd"
+import { Flex, Popconfirm, Image, Tag } from "antd"
 import { copy } from "@/utils/utils"
 
 
@@ -24,6 +24,14 @@ export function TableConfig(
             width: 150,
             ellipsis: true
         },
+        {
+            title: '落地页类型',
+            dataIndex: 'type',
+            key: 'type',
+            width: 85,
+            align: 'center',
+            render: (v: string) => v === 'LANDING_PAGE' ? <Tag color="blue">小说落地页</Tag> : v === 'VIDEO' ? <Tag color="green">视频落地页</Tag> : '--'
+        },
         {
             title: 'Sign',
             dataIndex: 'sign',
@@ -38,7 +46,8 @@ export function TableConfig(
             width: 150,
             ellipsis: true,
             render(_, record) {
-                return JSON.parse(record?.content || '{}')?.pageName || ''
+                const data = JSON.parse(record?.content || '{}')
+                return data?.pageName || data?.[0]?.user?.nick || '--'
             },
         },
         {
@@ -68,6 +77,14 @@ export function TableConfig(
             ellipsis: true,
             render: (v: string) => v || '--'
         },
+        {
+            title: '是否开启H5访问限制',
+            dataIndex: 'h5VisitSwitch',
+            key: 'h5VisitSwitch',
+            width: 70,
+            align: 'center',
+            render: (v: boolean) => v ?  <Tag color="green">开启</Tag> : <Tag color="red">关闭</Tag>
+        },
         {
             title: '备注',
             dataIndex: 'remark',

+ 19 - 2
src/pages/weComTask/page/miniProgramPages/typings.d.ts

@@ -18,7 +18,13 @@ declare namespace TASK_MINI_PAGE_CREATE {
         installActive: (e: React.MouseEvent<HTMLDivElement, MouseEvent>, index: number) => void
         setTopUrl: (url: string) => void
         setCompt: (key: string, value: any) => void,
-        setGlobal: (key: string, value: any) => void
+        setGlobal: (key: string, value: any) => void,
+        setTopShortVideoUrl: (url: string, videoInfo: {
+            width: number;
+            height: number;
+            videoLength: number;
+        }, index: number) => void
+        isNewAdd: boolean
     }
 
     interface Text extends ActiveType, Padding {
@@ -40,6 +46,16 @@ declare namespace TASK_MINI_PAGE_CREATE {
         elementType: 'TOP_VIDEO'
     }
 
+    interface TopShortVideo extends ActiveType {
+        data: {
+            url: string,
+            duration: number
+        }[],
+        url: string,
+        tipText: string,
+        elementType: 'TOP_SHORT_VIDEO'
+    }
+
     interface Image extends ActiveType, Padding {
         url: string,
         elementType: 'IMAGE'
@@ -63,11 +79,12 @@ declare namespace TASK_MINI_PAGE_CREATE {
         elementType: 'FLOAT_BUTTON',
         id: number
     }
-    type PageElementsSpecListProps = Array<TopImage | TopVideo | Image | Text | QrCode | Empty>
+    type PageElementsSpecListProps = Array<TopImage | TopVideo | TopShortVideo | Image | Text | QrCode | Empty>
     type GlobalElementsSpecListProps = Array<FloatButton>
 
     interface PageSpecsProps {
         bgColor: string,
+        pageType: 'VIDEO' | 'LANDING_PAGE',
         pageElementsSpecList: PageElementsSpecListProps
         globalElementsSpecList?: GlobalElementsSpecListProps
     }

+ 5 - 5
src/utils/utils.ts

@@ -415,9 +415,9 @@ export const removeEmptyValues = (obj: { [x: string]: any }) => {
  * @returns 
  */
 export const replaceSpecialTxt = (text: string | number | null | undefined) => {
-    if (text) {
-        return text.toString().replace(/[<>]/ig, '')
-    } else {
-        return text
-    }
+  if (text) {
+    return text.toString().replace(/[<>]/ig, '')
+  } else {
+    return text
+  }
 }

+ 5 - 0
yarn.lock

@@ -10824,6 +10824,11 @@ svgo@^2.7.0:
     picocolors "^1.0.0"
     stable "^0.1.8"
 
+swiper@^12.1.3:
+  version "12.1.3"
+  resolved "https://registry.yarnpkg.com/swiper/-/swiper-12.1.3.tgz#0fd256e6ee043c85c077b0fa48faef7ed6acb051"
+  integrity sha512-XcWlVmkHFICI4fuoJKgbp8PscDcS4i7pBH8nwJRBi3dpQvhCySwsWRYm4bOf/BzKVWkHOYaFw7qz9uBSrY3oug==
+
 symbol-tree@^3.2.4:
   version "3.2.4"
   resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"