| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539 |
- 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, get } = useModel('useLaunchAdq.useBdMediaPup')
- const { state: { parentId, belongUser } } = useModel('useLaunchAdq.useBdMedia')
- 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 ajax = topData?.map((item: any, index: number) => {
- const { elementType, ...newItem } = item
- let newTopPage = { elementType }
- let typeKey = getTypeKey(elementType)
- newTopPage[typeKey] = { ...newItem }
- let pageSpecsList: any[] = JSON.parse(JSON.stringify(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
- }
- 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)
|