ソースを参照

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

shenwu 2 年 前
コミット
d283ed4f70

+ 2 - 2
src/components/FileBoxAD/index.less

@@ -396,8 +396,8 @@
 .wxSelect {
   position: absolute;
   top: 0;
-  right: 20px;
-  width: 200px;
+  right: 0;
+  // width: 200px;
 
   >div {
     margin-top: 0 !important;

+ 3 - 4
src/components/FileBoxAD/index.tsx

@@ -59,7 +59,6 @@ function FlieBox(props: Props) {
     const [moveId, setMoveId] = useState<any>('')//移动的素材ID
     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(() => {
@@ -184,7 +183,7 @@ function FlieBox(props: Props) {
             },
         }
     }, [folderId, moveId, mediaType])
-    
+
 
     return <div style={{ display: 'flex', flexFlow: 'row' }}>
         {get_folder_tree?.data?.length > 0 && <div style={{ flexShrink: 0 }}>
@@ -194,7 +193,7 @@ function FlieBox(props: Props) {
             <div className={style.files} onContextMenu={rightMenu} style={height ? { height } : {}}>
                 {/* 关联公众号筛选 */}
                 <div className={style.wxSelect}>
-
+                    
                 </div>
                 {/* 层级路径 */}
                 <div className={style.path} >
@@ -296,7 +295,7 @@ function FlieBox(props: Props) {
                                             topName = "顶部视频"
                                             El = <div className={style.pageVideo}>
                                                 <span className={style.pagePreview} onClick={(e) => { e.stopPropagation(); setPage && setPage(1, item.id) }}><EyeOutlined /> 预览</span>
-                                                <video src={topPageElements?.topVideoSpec?.videoUrl} style={{ width: 130, height: 100 }} controls/>
+                                                <video src={topPageElements?.topVideoSpec?.videoUrl} style={{ width: 130, height: 100 }} controls />
                                             </div>
                                             break
                                     }

+ 4 - 4
src/pages/launchSystemNew/components/adPopover/index.tsx

@@ -1,13 +1,13 @@
 import { useAjax } from "@/Hook/useAjax"
 import { BidModeEnum, BidStrategyEnum, OptimizationGoalEnum, PromotedObjectType, SiteSetEnum } from "@/services/launchAdq/enum"
 import { getSysAdgroupsInfo } from "@/services/launchAdq/localAd"
-import { EyeOutlined } from "@ant-design/icons"
 import { Popover, Spin } from "antd"
 import React, { useState } from "react"
 import style from '../targetingPopover/index.less'
 
 interface Props {
-    id: number
+    id: number,
+    name: string
 }
 
 /**
@@ -17,7 +17,7 @@ interface Props {
 const AdPopover: React.FC<Props> = (props) => {
 
     /*************************/
-    const { id } = props
+    const { id, name } = props
     const [visible, setVisible] = useState<boolean>(false)
 
     const getSysAdgroups = useAjax((params) => getSysAdgroupsInfo(params))
@@ -55,7 +55,7 @@ const AdPopover: React.FC<Props> = (props) => {
         onVisibleChange={handleVisibleChange}
     >
         {/* 查看广告 */}
-        <a style={{ color: '#1890ff', fontSize: 12 }}><EyeOutlined /></a>
+        <a style={{ color: '#1890ff', fontSize: 12 }}>{name || id}</a>
     </Popover>
 }
 

+ 4 - 3
src/pages/launchSystemNew/components/adcreativePopover/index.tsx

@@ -8,7 +8,8 @@ import style from '../targetingPopover/index.less'
 
 
 interface Props {
-    id: number
+    id: number,
+    name: string
 }
 /**
  * 表格查看创意基本信息
@@ -17,7 +18,7 @@ interface Props {
 const AdcreativePopover: React.FC<Props> = (props) => {
 
     /*************************/
-    const { id } = props
+    const { id, name } = props
     const [visible, setVisible] = useState<boolean>(false)
 
     const getSysAdcreative = useAjax((params) => getSysAdcreativeInfo(params))
@@ -42,7 +43,7 @@ const AdcreativePopover: React.FC<Props> = (props) => {
         onVisibleChange={handleVisibleChange}
     >
         {/* 查看创意 */}
-        <a style={{ color: '#1890ff', fontSize: 12 }}><EyeOutlined /></a>
+        <a style={{ color: '#1890ff', fontSize: 12 }}>{name || id}</a>
     </Popover>
 }
 

+ 1 - 2
src/pages/launchSystemNew/components/lookLanding/index.tsx

@@ -1,8 +1,7 @@
 import { Carousel, Drawer, Spin } from "antd";
-import React, { useEffect, useMemo, useReducer, useState } from "react"
+import React, { useEffect, useMemo, 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'

+ 1 - 1
src/pages/launchSystemNew/components/pageModal/tableConfig.tsx

@@ -16,7 +16,7 @@ let columns = () => [
         title: '落地页名称',
         dataIndex: 'pageName',
         key: 'pageName',
-        align: 'center',
+        ellipsis: true
     },
     {
         title: '落地页类型',

+ 11 - 4
src/pages/launchSystemNew/components/selectCloud/index.tsx

@@ -1,8 +1,8 @@
-import { Image, message, Modal, Space, Tabs } from "antd"
-import React, { useEffect, useState } from "react"
+import { Button, Image, message, Modal, Space, Tabs, Tooltip } from "antd"
+import React, { useEffect } from "react"
 import { useModel } from "umi"
 import FileBoxAD from "@/components/FileBoxAD"
-import { CloseCircleFilled } from "@ant-design/icons"
+import { CloseCircleFilled, QuestionCircleFilled } from "@ant-design/icons"
 import style from './index.less'
 
 interface Props {
@@ -85,7 +85,14 @@ const SelectCloud: React.FC<Props> = (props) => {
         maskClosable={false}
         bodyStyle={{ padding: 0 }}
     >
-        <Tabs onChange={(activeKey: any) => { set({ belongUser: activeKey }) }} activeKey={belongUser} style={{ margin: '0 24px' }}>
+        <Tabs onChange={(activeKey: any) => { set({ belongUser: activeKey }) }} activeKey={belongUser} style={{ margin: '0 24px' }} tabBarExtraContent={<Space>
+            {
+                mediaType === 'PAGE' ? null : <Button size='small' type="primary" onClick={() => { set({ imgVisrible: true }) }}>新建素材</Button>
+            }
+            <Tooltip title="功能都在下方内容区右键" placement="left">
+                <QuestionCircleFilled />
+            </Tooltip>
+        </Space>}>
             <Tabs.TabPane tab={'个人本地'} key={1} />
             <Tabs.TabPane tab={'公共本地'} key={0} />
         </Tabs>

+ 20 - 0
src/pages/launchSystemNew/components/textAideInput/index.less

@@ -0,0 +1,20 @@
+.textAideInputPopover {
+  .ant-popover-inner-content {
+    padding: 0;
+    cursor: pointer;
+  }
+  .crt {
+    display: inline-flex;
+    align-items: center;
+    width: auto;
+    margin-left: 8px;
+    padding: 1px 4px;
+    height: 16px;
+    border-radius: 3px;
+    font-size: 12px;
+    color: #fff;
+    border: 1px solid #296bef;
+    background-color: #296bef;
+    line-height: normal;
+  }
+}

+ 79 - 0
src/pages/launchSystemNew/components/textAideInput/index.tsx

@@ -0,0 +1,79 @@
+import { useAjax } from "@/Hook/useAjax"
+import { getText } from "@/services/launchAdq/global"
+import { Input, List, Popover, Space } from "antd";
+import React, { useEffect } from "react"
+import { useState } from "react";
+import './index.less'
+
+
+interface Props {
+    value?: any,
+    onChange?: (value: any) => void,
+    style?: React.CSSProperties,
+    placeholder?: string;
+    maxTextLength?: number
+}
+/**
+ * 带文案助手输入框
+ * @returns 
+ */
+const TextAideInput: React.FC<Props> = (props) => {
+
+    /************************/
+    const { value, onChange, style, placeholder, maxTextLength = 10 } = props
+    const [text, setText] = useState<any>(value)
+    const [descriptionShow, setDescriptionshow] = useState(false)
+
+    const getTextLsit = useAjax((params) => getText(params))
+    /************************/
+
+    // 文案助手
+    const textList = (keyword?: any) => {
+        getTextLsit.run({ keyword, maxTextLength })
+    }
+
+    return <div className="textAideInput">
+        <Space>
+            <Popover placement="topLeft" overlayClassName="textAideInputPopover" style={{ minWidth: 600 }} visible={descriptionShow} content={<>
+                {
+                    descriptionShow && <List
+                        loading={getTextLsit?.loading}
+                        size="small"
+                        style={{ maxHeight: 300, overflowX: 'auto', minWidth: 600 }}
+                        dataSource={getTextLsit?.data?.returnTexts}
+                        renderItem={(item: any) => <List.Item onClick={(e: any) => {
+                            setText(item.text)
+                            setDescriptionshow(false)
+                            onChange && onChange(item.text)
+                        }}><span >{item.text}{item.tag && <span className="crt">{'CTR 高'}</span>}</span></List.Item>}
+                    />
+                }
+            </>}>
+                <Input
+                    placeholder={placeholder}
+                    style={style}
+                    value={text}
+                    onFocus={() => {
+                        setDescriptionshow(true)
+                        textList()
+                    }}
+                    onBlur={() => {
+                        setTimeout(() => { setDescriptionshow(false) }, 100)
+                    }}
+                    onChange={(e) => {
+                        let value = e.target.value
+                        setText(value)
+                        textList(value)
+                        onChange && onChange(value)
+                    }}
+                    allowClear
+                />
+            </Popover>
+
+            <span>{`${text?.length ?? 0}/${maxTextLength}`}</span>
+        </Space>
+    </div>
+}
+
+
+export default React.memo(TextAideInput)

+ 1 - 3
src/pages/launchSystemNew/launchManage/adAuthorize/tableConfig.tsx

@@ -80,9 +80,7 @@ export function columnsMp(edit:(params: any)=>void): any {
             align: 'center',
             render: (a: any) => {
                 return <Tooltip title={a}>
-                    <div className="name-wrapper">
-                        <p>{ a }</p>
-                    </div>
+                    <div className="name-wrapper">{ a }</div>
                 </Tooltip>
             }
         },

+ 1 - 1
src/pages/launchSystemNew/launchManage/localAd/creative/modal.tsx

@@ -132,7 +132,7 @@ function CreativeModal(props: Props) {
             if(newValues?.overrideCanvasHeadOption?.length === 1 && newValues?.overrideCanvasHeadOption[0] === 'OPTION_CREATIVE_OVERRIDE_CANVAS'){
                 newValues.overrideCanvasHeadOption = 'OPTION_CREATIVE_OVERRIDE_CANVAS'
             }
-            // callback(newValues)
+            callback(newValues)
         })
         // PupFn({ visible: false })
     }, [form, materialConfig])

+ 99 - 0
src/pages/launchSystemNew/launchManage/taskList/batchCreativeCopy.tsx

@@ -0,0 +1,99 @@
+import { useAjax } from "@/Hook/useAjax"
+import { getSysAdcreativeInfo } from "@/services/launchAdq/creative"
+import { PromotedObjectType, SiteSetEnum } from "@/services/launchAdq/enum"
+import { PlusOutlined } from "@ant-design/icons"
+import { Button, Modal } from "antd"
+import React, { useEffect, useRef, useState } from "react"
+import CreativeForm from "./creativeForm"
+import style from './index.less'
+
+interface Props {
+    publicData: any,
+    data: any,
+    visible?: boolean,
+    onClose?: () => void,
+    onChange?: () => void
+}
+/**
+ * 批量复制
+ * @returns 
+ */
+const BatchCreativeCopy: React.FC<Props> = (props) => {
+
+    /*******************************/
+    const { publicData, visible, onClose, onChange } = props
+    const [oriData, setOriData] = useState<any[]>([])  // 创意数据
+    const [template, setTemplate] = useState<{ siteSet: string[], promotedObjectType: string }>({ siteSet: [], promotedObjectType: '' })
+    const itemsRef: any = useRef([]);
+
+    const getSysAdcreative = useAjax((params) => getSysAdcreativeInfo(params))
+    /*******************************/
+
+    // 获取创意详情
+    useEffect(() => {
+        getSysAdcreative.run(publicData?.sysAdcreativeId).then(res => {
+            console.log(111111, res);
+            oriData[0] = { data: res, isOpen: true }
+            setOriData(JSON.parse(JSON.stringify(oriData)))
+            setTemplate({ siteSet: res?.siteSet, promotedObjectType: res?.promotedObjectType })
+        })
+    }, [])
+
+    const handleOk = () => {
+        console.log('itemsRef---->', itemsRef);
+        let current = (itemsRef?.current as any[])?.filter(item => item)
+        console.log('itemsRef?.current---->', current);
+        Promise.all(current?.map((item: { handleOk: any }) => item.handleOk())).then(res => {
+            console.log(1111111, res);
+            
+        }).catch(err => {
+            console.log(22222222, err);
+        })
+    }
+
+    // 删除创意
+    const delOri = (index: number) => {
+        oriData?.splice(index, 1)
+        setOriData([...oriData])
+    }
+
+    // 新增创意
+    const addOri = () => {
+        let newOriData = [...oriData]
+        newOriData.push({ isOpen: true })
+        setOriData(newOriData)
+    }
+
+    return <Modal title="批量复制" visible={visible} onOk={handleOk} width={1000} onCancel={() => { onClose && onClose() }} className={style.batchCopy}>
+        <div className={style.info}>
+            <div className={style.items}>
+                <div className={style.item}>
+                    <label className={style.label}>计划名称:</label>
+                    <span>{publicData?.campaignName}</span>
+                </div>
+                <div className={style.item}>
+                    <label className={style.label}>计划类型:</label>
+                    <span>{publicData?.campaignType === 'CAMPAIGN_TYPE_NORMAL' ? '普通展示广告' : '微信朋友圈广告'}</span>
+                </div>
+            </div>
+            <div className={style.items}>
+                <div className={style.item}>
+                    <label className={style.label}>推广目标:</label>
+                    <span>{PromotedObjectType[publicData?.promotedObjectType]}</span>
+                </div>
+                <div className={style.item}>
+                    <label className={style.label}>广告版位:</label>
+                    <span>{getSysAdcreative?.data?.siteSet?.map((item: string) => SiteSetEnum[item]).toString()}</span>
+                </div>
+            </div>
+        </div>
+
+        {oriData?.length > 0 && oriData?.map((item: any, index: number) => {
+            return <CreativeForm data={item.data} template={template} ref={el => { itemsRef.current[index] = el }} index={index + 1} key={index} delOri={() => { delOri(index) }} isDel={oriData?.length > 1}/>
+        })}
+
+        <Button type="link" style={{ padding: '4px 0', marginTop: 10 }} onClick={addOri}><PlusOutlined /> 新增创意</Button>
+    </Modal>
+}
+
+export default React.memo(BatchCreativeCopy)

+ 371 - 0
src/pages/launchSystemNew/launchManage/taskList/creativeForm.tsx

@@ -0,0 +1,371 @@
+import { useAjax } from "@/Hook/useAjax";
+import { AdcreativeTemplate, AdcreativeTemplateList } from "@/services/launchAdq";
+import { get_adcreative_template, get_adcreative_template_list } from "@/services/launchAdq/global";
+import { mySet } from "@/utils/arrFn";
+import { DeleteOutlined, DownOutlined, UpOutlined } from "@ant-design/icons";
+import { Button, Form, Popconfirm, Radio, Spin } from "antd"
+import React, { forwardRef, useEffect, useImperativeHandle, useState } from "react"
+import { useModel } from "umi";
+import SelectCloud from "../../components/selectCloud";
+import TextAideInput from "../../components/textAideInput";
+import { outAdcreativeTemplateIdFun } from "../localAd/adenum";
+import style from './index.less'
+
+interface Props {
+    template: { siteSet: string[], promotedObjectType: string },
+    index: number,
+    isDel: boolean,
+    delOri?: () => void
+    data?: any
+}
+/**
+ * 批量Form
+ * @returns 
+ */
+const CreativeForm = forwardRef((props: Props, ref) => {
+
+    /**************************/
+    const { data, template, index, delOri, isDel } = props
+    const [form] = Form.useForm();
+    let adcreativeElementsType = Form.useWatch('adcreativeElementsType', form)
+    let adcreativeTemplateId = Form.useWatch('adcreativeTemplateId', form)
+
+    const [adcreative_template_list, set_adcreative_template_list] = useState<AdcreativeTemplateList[]>([])
+    const [adcreative_template, set_adcreative_template] = useState<AdcreativeTemplate>()
+    const [conversionList, setConversionList] = useState<any>(null)
+    const [materialConfig, setMaterialConfig] = useState<{ adcreativeTemplateId?: number, type: string, cloudSize: { relation: string, width: number, height: number }[], list: any[], max: number }>({
+        type: '',//类型
+        cloudSize: [],//素材搜索条件
+        list: [],//素材
+        max: 1,//素材数量
+    })//素材配置
+    const [pupState, setPupState] = useState({
+        kp_show: false,
+        xd_show: false,
+        sj_show: false,
+        bq_show: false,
+        sp_show: false
+    })
+    const [isOpen, setIsOpen] = useState<boolean>(true)
+    const [isErr, setIsErr] = useState<boolean>(false)
+    const [selectImgVisible, set_selectImgVisible] = useState(false)
+
+
+    const { init } = useModel('useLaunchAdq.useBdMediaPup')
+
+    const getAdcreativeTemplateList = useAjax((params) => get_adcreative_template_list(params))
+    const getAdcreativeTemplate = useAjax((params) => get_adcreative_template(params))
+    /**************************/
+
+    //子组件暴露方法
+    useImperativeHandle(ref, () => ({
+        handleOk
+    }));
+
+    const handleOk = () => {
+        return new Promise((resolve: (value: unknown) => void, reject: (reason?: any) => void) => {
+            form.validateFields().then(values => {
+                setIsErr(false)
+                resolve(values)
+            }).catch(err => {
+                setIsErr(true)
+                reject(err)
+            })
+        })
+    }
+
+    // 获取创意形式列表
+    useEffect(() => {
+        if (template && template?.siteSet?.length > 0 && template?.promotedObjectType) {
+            getAdcreativeTemplateList.run({
+                siteSet: template?.siteSet,
+                promotedObjectType: template?.promotedObjectType,
+                campaignType: 'CAMPAIGN_TYPE_NORMAL',
+            }).then(res => {
+                let newArr: any = []
+                // 过滤掉相同的和即将下线的
+                if (!res) {
+                    return
+                }
+                Object.values(res)?.forEach((arr: any) => {
+                    Array.isArray(arr) && arr?.forEach((item: any) => {
+                        if (newArr.length > 0) {
+                            if (outAdcreativeTemplateIdFun(item.adcreativeTemplateId) && newArr.every((i: { adcreativeTemplateId: any }) => i.adcreativeTemplateId !== item.adcreativeTemplateId)) {
+                                newArr.push(item)
+                            } else {
+                                // 找出通用创意
+                                newArr = newArr?.map((arr: { adcreativeTemplateId: any }) => {
+                                    if (arr.adcreativeTemplateId === item.adcreativeTemplateId) {
+                                        return { ...arr, isGeneral: true }
+                                    }
+                                    return arr
+                                })
+                            }
+                        } else {
+                            if (outAdcreativeTemplateIdFun(item.adcreativeTemplateId)) {
+                                newArr.push(item)
+                            }
+                        }
+                    })
+                })
+                set_adcreative_template_list(newArr)
+            })
+        }
+    }, [template, form])
+
+    // 获取创意形式详情
+    useEffect(() => {
+        // CAMPAIGN_TYPE_NORMAL
+        if (template?.siteSet?.length > 0 && template?.promotedObjectType && adcreativeTemplateId) {
+            if (adcreativeTemplateId) {
+                getAdcreativeTemplate.run({
+                    siteSet: template?.siteSet,
+                    promotedObjectType: template?.promotedObjectType,
+                    adcreativeTemplateId
+                }).then(res => {
+                    if (res?.length > 0) {
+                        set_adcreative_template(res[0])
+                    }
+                })
+            }
+        }
+    }, [template?.siteSet, template?.promotedObjectType, adcreativeTemplateId])
+
+    //每次选中创意设置该展示的界面
+    useEffect(() => {
+        let states = {
+            kp_show: false,
+            xd_show: true,
+            sj_show: false,
+            bq_show: false,
+            sp_show: false
+        }
+        let values: any = { pageType: 'PAGE_TYPE_CANVAS_WECHAT', }
+        if (adcreative_template) {
+            let pageList = adcreative_template?.landingPageConfig?.supportPageTypeList
+            let pageType = pageList?.length ? pageList[0]?.pageType : null
+            //数据展示组件
+            if (adcreative_template.adcreativeAttributes.some(item => item.name === 'conversion_data_type' || item.name === 'conversion_target_type')) {
+                let arr = adcreative_template.adcreativeAttributes?.filter((item: { name: string; }) => item.name === 'conversion_data_type' || item.name === 'conversion_target_type')
+                let newObj: any = {}
+                arr.forEach((item) => {
+                    let arr: any[] = mySet(item.propertyDetail.enumDetail.enumeration)
+                    newObj[item.name] = arr
+                })
+                setConversionList(newObj)
+
+                states = { ...states, sj_show: true }
+                if (newObj.conversion_data_type) {
+                    values = { ...values, conversionDataType: newObj.conversion_data_type[0].value }
+                }
+                if (newObj.conversion_target_type) {
+                    values = { ...values, conversionTargetType: newObj.conversion_target_type[0].value }
+                }
+            }
+            //行动按钮组件存在
+            if (states.xd_show) {
+                let linkNameList = (pageList?.filter((item: { pageType: any; }) => item.pageType === pageType)[0] as any)?.supportLinkNameType?.list
+                let linkPageList = (pageList?.filter((item: { pageType: any; }) => item.pageType === pageType)[0] as any)?.supportLinkPageType?.list
+                if (linkNameList) {
+                    let linkNameType = linkNameList[0]?.linkNameType
+                    let linkPageType = linkPageList[0]?.linkPageType
+                    values = { ...values, linkNameType, linkPageType }
+                } else {
+                    states = { ...states, xd_show: false }
+                }
+            }
+            // 视频结束页 end_page
+            if (adcreative_template.adcreativeElements.some(item => item.name === 'end_page')) {
+                // let endPageType =adcreative_template?.adcreativeElements?.filter(item=>item.name === 'end_page_type')[0]?.enumProperty?.enumeration
+                values = { ...values, endPageType: 'END_PAGE_AVATAR_NICKNAME_HIGHLIGHT' }
+                states = { ...states, sp_show: true }
+            }
+            setPupState(states)
+            form.setFieldsValue(values)
+        }
+    }, [adcreative_template])
+
+    // 版位改变清空数据
+    useEffect(() => {
+        if (materialConfig.adcreativeTemplateId && adcreativeTemplateId !== materialConfig.adcreativeTemplateId) {
+            setMaterialConfig({ ...materialConfig, adcreativeTemplateId: undefined, list: [] })
+        }
+    }, [adcreativeTemplateId, materialConfig])
+
+    // 切换创意形式默认选中第一个
+    useEffect(() => {
+        // 设置默认选中第一个
+        if (adcreativeElementsType && adcreative_template_list?.length > 0) {
+            let adcreativeTemplateIdArr = adcreative_template_list?.filter(item => item.adcreativeTemplateStyle === adcreativeElementsType)
+            form.setFieldsValue({ adcreativeTemplateId: adcreativeTemplateIdArr[0].adcreativeTemplateId })
+        }
+    }, [adcreativeElementsType, adcreative_template_list])
+
+    return <div className={style.originality} key={index} style={isOpen ? { borderColor: isErr ? 'red' : '#e4e4e4' } : { height: 44, overflow: 'hidden', borderColor: isErr ? 'red' : '#e4e4e4' }}>
+        <div className={style.head} onClick={() => { setIsOpen(!isOpen) }}>
+            <div>创意{index}</div>
+            <div>
+                {isDel && <Popconfirm placement="top" title="是否放弃该创意" onConfirm={(e) => { e?.stopPropagation(); delOri && delOri() }} okText="Yes" cancelText="No">
+                    <Button type="link" size='small' className={style.clear} style={{ color: 'red' }} onClick={(e) => { e?.stopPropagation() }}><DeleteOutlined /></Button>
+                </Popconfirm>}
+                <Button
+                    type="link"
+                    size='small'
+                    style={{ color: '#000' }}
+                >
+                    {isOpen ? <UpOutlined /> : <DownOutlined />}
+                </Button>
+            </div>
+        </div>
+        <div>
+            <div style={{ height: 20 }}></div>
+            <Form
+                form={form}
+                labelCol={{ span: 4 }}
+                labelWrap={true}
+                labelAlign="left"
+                initialValues={
+                    {
+                        adcreativeElementsType: '视频'
+                    }
+                }
+            >
+                <Form.Item label="创意形式" name='adcreativeElementsType'>
+                    <Radio.Group >
+                        <Radio.Button value="视频">视频</Radio.Button>
+                        <Radio.Button value="图片">图片</Radio.Button>
+                    </Radio.Group>
+                </Form.Item>
+
+                <Spin tip="Loading..." spinning={getAdcreativeTemplateList?.loading} style={{ width: '100%' }}>
+                    <Form.Item style={{ marginLeft: 155 }} name='adcreativeTemplateId'>
+                        <Radio.Group className={style.adcreative_template}>
+                            {adcreative_template_list?.filter(item => item.adcreativeTemplateStyle === adcreativeElementsType)?.map((item: any) => {
+                                return <Radio.Button value={item.adcreativeTemplateId} key={item.adcreativeTemplateId}>
+                                    <div className={style.adcreative_template_item}>
+                                        {item.isGeneral && <span style={{ color: '#4080ff', fontSize: 10 }}>所选版位通投</span>}
+                                        <img src={item.adcreativeSampleImage} />
+                                        <span style={{ fontSize: 12, height: 20, lineHeight: '20px' }}>{item.adcreativeTemplateAppellation}</span>
+                                        <span style={{ fontSize: 12, height: 20, lineHeight: '20px' }}>{item.adcreativeTemplateId}</span>
+                                    </div>
+                                </Radio.Button>
+                            })}
+                        </Radio.Group>
+                    </Form.Item>
+
+                    {/* 优先展示视频或图片 */}
+                    {adcreative_template?.adcreativeElements?.filter(item => item.required && item.name === 'image_list' || item.name === 'short_video1' || item.name === 'video' || item.name === 'image').map(item => {
+                        return <Form.Item label={item.description} rules={[{ required: true, message: '请选择素材!' }]} key={item.name} name={item.name}>
+                            {/* 视频 */}
+                            {
+                                (item.name === 'short_video1' || item.name === 'video') && <div className={`${style.box} ${style.video}`} onClick={() => {
+                                    init({ mediaType: 'VIDEO', cloudSize: adcreativeTemplateId === 1708 ? [[{ relation: '=', width: 1280, height: 720 }]] : [[{ relation: '=', width: item.restriction.videoRestriction.minWidth, height: item.restriction.videoRestriction.minHeight }]], maxSize: item.restriction.videoRestriction.fileSize * 1024 })
+                                    setTimeout(() => {
+                                        set_selectImgVisible(true)
+                                        setMaterialConfig({
+                                            ...materialConfig,
+                                            type: item.name,
+                                            max: 1,
+                                            adcreativeTemplateId
+                                        })
+                                    }, 100)
+                                }}>
+                                    <p>
+                                        {
+                                            materialConfig?.list[0] ? <video src={materialConfig?.list[0].url} controls /> : <>
+                                                <span>{`推荐尺寸(${adcreativeTemplateId === 1708 ? 1280 : item.restriction.videoRestriction.minWidth} x ${adcreativeTemplateId === 1708 ? 720 : item.restriction.videoRestriction.minHeight})`}</span>
+                                                <span>{`${item.restriction.videoRestriction.fileFormat?.map(str => str?.replace('MEDIA_TYPE_', ''))};< ${item.restriction.videoRestriction.fileSize / 1024}M;时长 ≥ ${item.restriction.videoRestriction.minDuration}s,≤ ${item.restriction.videoRestriction.maxDuration}s,必须带有声音`}</span>
+                                            </>
+                                        }
+                                    </p>
+                                </div>
+                            }
+                            {/* 单图 */}
+                            {
+                                item.name === 'image' && <div className={`${style.box} ${style.image}`} onClick={() => {
+                                    init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: item.restriction.imageRestriction.width, height: item.restriction.imageRestriction.height }]], maxSize: item.restriction.imageRestriction.fileSize * 1024 })
+                                    setTimeout(() => {
+                                        set_selectImgVisible(true)
+                                        setMaterialConfig({
+                                            ...materialConfig,
+                                            type: item.name,
+                                            max: 1,
+                                            adcreativeTemplateId
+                                        })
+                                    }, 100)
+
+                                }}>
+                                    <p>
+                                        {materialConfig?.list[0] ? <img src={materialConfig?.list[0].url} /> : <>
+                                            <span>{`推荐尺寸(${item.restriction.imageRestriction.width} x ${item.restriction.imageRestriction.height})`}</span>
+                                            <span>{`${item.restriction.imageRestriction.fileFormat?.map(str => str?.replace('IMAGE_TYPE_', ''))};小于 ${item.restriction.imageRestriction.fileSize}KB`}</span>
+                                        </>
+                                        }
+                                    </p>
+                                </div>
+                            }
+                            {/* 多图 */}
+                            {
+                                item.name === 'image_list' && <div className={`${style.box} ${item.arrayProperty.maxNumber >= 3 ? style.image_list : style.image}`} onClick={() => {
+                                    init({ mediaType: 'IMG', num: item.arrayProperty.maxNumber, cloudSize: [[{ relation: '=', width: item.restriction.imageRestriction.width, height: item.restriction.imageRestriction.height }]], maxSize: item.restriction.imageRestriction.fileSize * 1024 })
+                                    setTimeout(() => {
+                                        set_selectImgVisible(true)
+                                        setMaterialConfig({
+                                            ...materialConfig,
+                                            type: item.name,
+                                            max: item.arrayProperty.maxNumber,
+                                            adcreativeTemplateId
+                                        })
+                                    }, 100)
+                                }}>
+                                    {
+                                        Array(item.arrayProperty.maxNumber).fill('').map((arr, index) => {
+                                            return <p key={index}>
+                                                {
+                                                    materialConfig?.list[index] ? <img src={materialConfig?.list[index].url} /> : <>
+                                                        <span>{`推荐尺寸(${item.restriction.imageRestriction.width} x ${item.restriction.imageRestriction.height})`}</span>
+                                                        <span>{`${item.restriction.imageRestriction.fileFormat?.map(str => str?.replace('IMAGE_TYPE_', ''))};小于 ${item.restriction.imageRestriction.fileSize}KB`}</span>
+                                                    </>
+                                                }
+
+                                            </p>
+                                        })
+                                    }
+                                </div>
+                            }
+                        </Form.Item>
+                    })}
+
+                    {/* 过滤了不必传和品牌名称,品牌标识图(外部传)短视频结构(组装使用) */}
+                    {adcreative_template?.adcreativeElements?.filter(item => item.required && item.name === 'description').map(item => {
+                        let maxNum = adcreativeTemplateId === 1708 ? pupState.xd_show ? 10 : item.restriction.textRestriction.maxLength : item.restriction.textRestriction.maxLength
+                        return <div key={item.fieldType}>
+                            <Form.Item label={item.description} name={item.name} rules={[{ required: true, pattern: RegExp(item.restriction.textRestriction.textPattern?.replace(/\+/ig, `{1,${maxNum}}`)), message: '请输入正确的' + item.description }]}>
+                                <TextAideInput placeholder={'请输入' + item.description} style={{ width: 500 }} maxTextLength={maxNum} />
+                            </Form.Item>
+                        </div>
+                    })}
+                </Spin>
+            </Form>
+        </div>
+
+
+        {/* 选择素材 */}
+        {selectImgVisible && <SelectCloud
+            visible={selectImgVisible}
+            onClose={() => set_selectImgVisible(false)}
+            sliderImgContent={materialConfig.list}
+            onChange={(content) => {
+                if (content.length > 0) {
+                    form.setFieldsValue({ [materialConfig.type]: materialConfig.type })
+                }
+                setMaterialConfig({ ...materialConfig, list: content })
+                set_selectImgVisible(false)
+                console.log(content)
+            }} />
+        }
+    </div>
+})
+
+
+export default React.memo(CreativeForm)

+ 151 - 0
src/pages/launchSystemNew/launchManage/taskList/index.less

@@ -0,0 +1,151 @@
+.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;
+      }
+    }
+  }
+}
+
+.adcreative_template {
+  width: 100%;
+  overflow-y: auto;
+  display: flex;
+  height: 173px;
+
+  >label {
+    height: 100%;
+    margin-right: 15px;
+  }
+}
+
+.adcreative_template_item {
+  width: 150px;
+  height: 160px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-flow: column;
+}
+
+.video {}
+
+.box {
+  width: 60%;
+  height: 200px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: #f5f7fa;
+  flex-direction: column;
+  color: rgba(0, 0, 0, .3);
+  border-radius: 5px;
+
+  >p {
+    display: flex;
+    align-items: center;
+    flex-flow: column;
+    font-size: 10px;
+    cursor: pointer;
+    max-height: 150px;
+    margin-bottom: 0;
+
+    img {
+      height: 100%;
+    }
+
+    video {
+      height: 100%;
+    }
+  }
+}
+
+.image_list {
+  flex-flow: row wrap;
+  background-color: transparent;
+  height: auto;
+  justify-content: flex-start;
+
+  >p {
+    width: 150px;
+    background-color: #f5f7fa;
+    height: 150px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border: 1px solid #e6e8ed;
+    margin: 0;
+  }
+}
+
+.crt {
+  display: inline-flex;
+  align-items: center;
+  width: auto;
+  margin-left: 8px;
+  padding: 1px 4px;
+  height: 16px;
+  border-radius: 3px;
+  font-size: 12px;
+  color: #fff;
+  border: 1px solid #296bef;
+  background-color: #296bef;
+  line-height: normal;
+}

+ 6 - 15
src/pages/launchSystemNew/launchManage/taskList/index.tsx

@@ -1,23 +1,19 @@
 import { useAjax } from "@/Hook/useAjax"
-import { getListByCorpAccount } from "@/services/enterpriseWeChat/userMange"
 import { PromotedObjectType } from "@/services/launchAdq/enum"
 import { getTaskListApi, TaskListProps } from "@/services/launchAdq/taskList"
-import { Button, Card, Input, Select, Space } from "antd"
+import { Input, Select, Space } from "antd"
 import React, { useEffect, useState } from "react"
-import LookLanding from "../../components/lookLanding"
 import TableData from "../../components/TableData"
 import Log from "./log"
 import tableConfig from './tableConfig'
 
-
-
 const TaskList: React.FC = () => {
 
     /*************************/
     const [queryForm, setQueryForm] = useState<TaskListProps>({ pageSize: 20, pageNum: 1 })
     const [logVisible, setLogVisible] = useState<boolean>(false)
-    const [lookVisible, setLookVisible] = useState<boolean>(false)
     const [logData, setLogData] = useState<{ taskId: number, campaignName: string } | number>({ taskId: 0, campaignName: '' })
+    const [allData, setAllData] = useState<any>({})
 
     const getTaskList = useAjax((params) => getTaskListApi(params), { formatResult: true })
     /*************************/
@@ -38,15 +34,12 @@ const TaskList: React.FC = () => {
         getTaskList.run(queryForm)
     }
 
-    const callback = (data: any, type: 'log' | 'page') => {
+    const callback = (data: any, type: 'log' | 'page', allData?: any) => {
         switch (type) {
             case 'log':
                 setLogData({ ...data })
                 setLogVisible(true)
-                break
-            case 'page':
-                setLogData(data)
-                setLookVisible(true)
+                setAllData(allData)
                 break
         }
     }
@@ -83,15 +76,13 @@ const TaskList: React.FC = () => {
                 </Select>
             </Space>}
             onChange={(props: any) => {
-                let { sortData, pagination } = props
+                let { pagination } = props
                 let { current, pageSize } = pagination
                 setQueryForm({ ...queryForm, pageNum: current, pageSize })
             }}
         />
         {/* 日志 */}
-        {logVisible && <Log {...logData as any} visible={logVisible} onClose={() => setLogVisible(false)} />}
-        {/* 查看落地页 */}
-        {lookVisible && <LookLanding visible={lookVisible} onClose={() => setLookVisible(false)} id={logData as any} />}
+        {logVisible && <Log {...logData as any} visible={logVisible} onClose={() => setLogVisible(false)} allData={allData}/>}
     </>
 }
 

+ 56 - 5
src/pages/launchSystemNew/launchManage/taskList/log.tsx

@@ -3,12 +3,15 @@ import { getTaskLogListApi, TaskLogListProps } from "@/services/launchAdq/taskLi
 import { Button, Drawer, Select, Space } from "antd"
 import React, { useEffect, useState } from "react"
 import { useModel } from "umi"
+import LookLanding from "../../components/lookLanding"
 import TableData from "../../components/TableData"
+import BatchCreativeCopy from "./batchCreativeCopy"
 import tableConfig from './logTableConfig'
 
 interface Props {
     taskId: number,  // 任务ID
     campaignName: string, // 计划名称
+    allData: any, // 所有数据
     visible?: boolean,
     onClose?: () => void
 }
@@ -19,8 +22,13 @@ interface Props {
 const Log: React.FC<Props> = (props) => {
 
     /*****************************/
-    const { taskId, campaignName, visible, onClose } = props
+    const { taskId, campaignName, allData, visible, onClose } = props
     const [queryForm, setQueryForm] = useState<TaskLogListProps>({ pageNum: 1, pageSize: 20, taskId })
+    const [selectedRowKeys, setSelectedRowKeys] = useState<any[]>([])
+    const [visibleCopy, setVisibleCopy] = useState<boolean>(false)
+    const [data, setData] = useState<any>({})
+    const [lookVisible, setLookVisible] = useState<boolean>(false)
+    const [logData, setLogData] = useState<{ taskId: number, campaignName: string } | number>({ taskId: 0, campaignName: '' })
 
     const { getAdAccount } = useModel('useLaunchAdq.useAdAuthorize')
     const getTaskLogList = useAjax((params) => getTaskLogListApi(params), { formatResult: true })
@@ -34,20 +42,58 @@ const Log: React.FC<Props> = (props) => {
         getList()
     }, [queryForm])
 
+    /** 获取列表 */
     const getList = () => {
         getTaskLogList.run(queryForm)
     }
 
-    return <Drawer bodyStyle={{ padding: 0 }} title={campaignName + ' 日志'} width={1000} placement="right" onClose={() => { onClose && onClose() }} visible={visible}>
+    /** 批量复制 */
+    const copyCreative = (data: any) => {
+        console.log(data);
+        setData(data)
+        setVisibleCopy(true)
+    }
+
+    const callback = (data: any) => {
+        setLogData(data)
+        setLookVisible(true)
+    }
+
+    return <Drawer bodyStyle={{ padding: 0 }} title={campaignName + ' 日志'} width={1400} placement="right" onClose={() => { onClose && onClose() }} visible={visible}>
+        <Space>
+            <Button></Button>
+        </Space>
         <TableData
-            columns={() => tableConfig()}
+            columns={() => tableConfig(copyCreative, callback)}
             ajax={getTaskLogList}
             dataSource={getTaskLogList?.data?.data?.records}
             loading={getTaskLogList?.loading}
-            scroll={{ y: 600 }}
+            scroll={{ x: 1200, y: 600 }}
             total={getTaskLogList?.data?.data?.total}
             page={getTaskLogList?.data?.data?.current}
             pageSize={getTaskLogList?.data?.data?.size}
+            // rowSelection={{
+            //     type: 'checkbox',
+            //     selectedRowKeys: selectedRowKeys?.map((item: {id: number}) => item.id.toString()),
+            //     onSelect: (record: any, selected: boolean, selectedRows: any, nativeEvent: any) => {
+            //         let oldSelectedRowKeys: any[] = JSON.parse(JSON.stringify(selectedRowKeys))
+            //         if (selected) { // 新增
+            //             oldSelectedRowKeys.push(record)
+            //         } else { // 减少
+            //             oldSelectedRowKeys = oldSelectedRowKeys.filter((item: {id: number}) => item.id != record.id)
+            //         }
+            //         setSelectedRowKeys(oldSelectedRowKeys)
+            //     },
+            //     onSelectAll: (selected: boolean, selectedRows: any, changeRows: any) => {
+            //         let oldSelectedRowKeys: any[] = JSON.parse(JSON.stringify(selectedRowKeys))
+            //         if (selected) {
+            //             oldSelectedRowKeys = [...oldSelectedRowKeys, ...changeRows]
+            //         } else {
+            //             oldSelectedRowKeys = oldSelectedRowKeys.filter((item: {id: number}) => !changeRows?.some((item1: {id: number}) => item1.id == item.id))
+            //         }
+            //         setSelectedRowKeys(oldSelectedRowKeys)
+            //     }
+            // }}
             leftChild={<Space>
                 <Select
                     placeholder="媒体账户"
@@ -69,11 +115,16 @@ const Log: React.FC<Props> = (props) => {
                 </Select>
             </Space>}
             onChange={(props: any) => {
-                let { sortData, pagination } = props
+                let { pagination } = props
                 let { current, pageSize } = pagination
                 setQueryForm({ ...queryForm, pageNum: current, pageSize })
             }}
         />
+
+        {/* 批量复制 */}
+        {visibleCopy && <BatchCreativeCopy data={data} publicData={allData} visible={visibleCopy} onClose={() => setVisibleCopy(false)} onChange={() => { }} />}
+        {/* 查看落地页 */}
+        {lookVisible && <LookLanding visible={lookVisible} onClose={() => setLookVisible(false)} id={logData as any} />}
     </Drawer>
 }
 

+ 83 - 15
src/pages/launchSystemNew/launchManage/taskList/logTableConfig.tsx

@@ -1,23 +1,16 @@
-import { Badge, message } from "antd"
+import { copy } from "@/utils/utils";
+import { DownOutlined, EyeOutlined } from "@ant-design/icons";
+import { Badge, Button, Dropdown, Menu, Space } from "antd"
 import React from "react"
-function tableConfig(): any {
-    const copy = (str: string) => {
-        let element = document.createElement("textarea");
-        element.id = 'myTextarea'
-        element.textContent = str
-        document.body.append(element);
-        (document.getElementById('myTextarea') as any).select();
-        document.execCommand("Copy")
-        document.body.removeChild(element);
-        message.success(`复制成功:${str}`)
-    }
+import AdcreativePopover from "../../components/adcreativePopover";
+function tableConfig(copyCreative: (data: any) => void, callback: (data: any) => void): any {
     return [
         {
             title: 'ID',
             dataIndex: 'id',
             key: 'id',
             align: 'center',
-            width: 45,
+            width: 50,
         },
         {
             title: '媒体账户',
@@ -39,6 +32,58 @@ function tableConfig(): any {
                 return <span style={{ fontSize: "12px" }}>{a || '--'}</span>
             }
         },
+        {
+            title: '品牌形象',
+            dataIndex: 'promotedObjectId',
+            key: 'promotedObjectId',
+            width: 140,
+            align: 'center',
+            ellipsis: true,
+            render: (a: any, b: any) => {
+                return <span style={{ fontSize: "12px" }}>{a || '--'}</span>
+            }
+        },
+        {
+            title: '创意',
+            dataIndex: 'sysAdcreativeId',
+            key: 'sysAdcreativeId',
+            align: 'center',
+            width: 80,
+            ellipsis: true,
+            render: (a: any, b: any) => {
+                if (a) {
+                    return <AdcreativePopover id={a} name={b?.sysAdcreativeInfo?.adcreativeName}/>
+                } else {
+                    return <span>--</span>
+                }
+            }
+        },
+        {
+            title: '任务反馈',
+            dataIndex: 'count',
+            key: 'count',
+            width: 250,
+            render: (a: any, b: any) => {
+                return <Space style={{ fontSize: "12px" }} size={20}>
+                    <span><Badge status="processing" />总条数:{a}条</span>
+                    <span><Badge status="success" />成功:{b?.successCount || 0}条</span>
+                </Space>
+            }
+        },
+        {
+            title: '落地页ID',
+            dataIndex: 'sysPageId',
+            key: 'sysPageId',
+            align: 'center',
+            width: 80,
+            render: (a: any, b: any) => {
+                if (a) {
+                    return <Space><span style={{ fontSize: "12px" }}>{a}</span><a style={{ color: '#1890ff', fontSize: 12 }} onClick={() => { callback(b.sysPageId) }}><EyeOutlined /></a></Space>
+                } else {
+                    return <span>--</span>
+                }
+            }
+        },
         {
             title: '创建状态',
             dataIndex: 'createStatus',
@@ -47,7 +92,7 @@ function tableConfig(): any {
             width: 90,
             render: (a: any, b: any) => {
                 if (a) {
-                    return a === 0 ? <Badge status="warning" text={<span style={{ fontSize: "12px" }}>创建中</span>}/> : a === 100 ? <Badge status="success" text={<span style={{ fontSize: "12px" }}>创建成功</span>} /> : <Badge status="error" text={<span style={{ fontSize: "12px" }}>创建失败</span>} />
+                    return a === 0 ? <Badge status="warning" text={<span style={{ fontSize: "12px" }}>创建中</span>} /> : a === 100 ? <Badge status="success" text={<span style={{ fontSize: "12px" }}>创建成功</span>} /> : <Badge status="error" text={<span style={{ fontSize: "12px" }}>创建失败</span>} />
                 } else {
                     return <span>--</span>
                 }
@@ -58,7 +103,8 @@ function tableConfig(): any {
             dataIndex: 'createTime',
             key: 'createTime',
             align: 'center',
-            width: 130,
+            width: 160,
+            ellipsis: true,
             render: (a: any, b: any) => {
                 return <span style={{ fontSize: "12px" }}>{a || '--'}</span>
             }
@@ -73,6 +119,28 @@ function tableConfig(): any {
                 return <a style={{ fontSize: "12px" }} onClick={() => copy(a)}>{a || '--'}</a>
             }
         },
+        // {
+        //     title: '操作',
+        //     dataIndex: 'cz',
+        //     key: 'cz',
+        //     width: 100,
+        //     align: 'center',
+        //     render: (a: any, b: any) => {
+        //         return <Dropdown trigger={['click']} overlay={<Menu items={[
+        //             {
+        //                 key: '1',
+        //                 label: (<Button size="small" type="link" onClick={() => { copyCreative(b) }}>批量复制</Button>)
+        //             }
+        //         ]} />}>
+        //             <a onClick={e => e.preventDefault()} style={{ fontSize: 12 }}>
+        //                 <Space>
+        //                     操作
+        //                     <DownOutlined />
+        //                 </Space>
+        //             </a>
+        //         </Dropdown>
+        //     }
+        // }
     ]
 }
 

+ 27 - 42
src/pages/launchSystemNew/launchManage/taskList/tableConfig.tsx

@@ -1,12 +1,22 @@
 import React from "react"
-import { Space } from "antd"
+import { Badge, Space } from "antd"
 import { AdStatus, PromotedObjectType, SpeedMode } from "@/services/launchAdq/enum"
 import TargetingPopover from "../../components/targetingPopover"
-import { EyeOutlined } from "@ant-design/icons"
 import AdPopover from "../../components/adPopover"
-import AdcreativePopover from "../../components/adcreativePopover"
-function tableConfig(callback: (data: any, type: 'log' | 'page') => void): any {
+function tableConfig(callback: (data: any, type: 'log' | 'page', allData?: any) => void): any {
     return [
+        {
+            title: '操作',
+            dataIndex: 'taskName',
+            key: 'taskName',
+            width: 60,
+            align: 'center',
+            render: (a: any, b: any) => {
+                return <Space>
+                    <a style={{ color: '#1890ff', fontSize: 12 }} onClick={() => { callback({ taskId: b.id, campaignName: b.campaignName }, 'log', b) }}>日志</a>
+                </Space>
+            }
+        },
         {
             title: 'ID',
             dataIndex: 'id',
@@ -82,14 +92,15 @@ function tableConfig(callback: (data: any, type: 'log' | 'page') => void): any {
             }
         },
         {
-            title: '广告ID',
+            title: '广告',
             dataIndex: 'sysAdgroupId',
             key: 'sysAdgroupId',
             align: 'center',
-            width: 80,
+            width: 90,
+            ellipsis: true,
             render: (a: any, b: any) => {
                 if (a) {
-                    return <Space><span style={{ fontSize: "12px" }}>{a}</span><AdPopover id={a}/></Space>
+                    return <AdPopover name={b?.sysAdgroupInfo?.adgroupName} id={a} />
                 } else {
                     return <span>--</span>
                 }
@@ -103,35 +114,7 @@ function tableConfig(callback: (data: any, type: 'log' | 'page') => void): any {
             width: 80,
             render: (a: any, b: any) => {
                 if (a) {
-                    return <Space><span style={{ fontSize: "12px" }}>{a}</span><TargetingPopover id={b.sysTargetingId}/></Space> 
-                } else {
-                    return <span>--</span>
-                }
-            }
-        },
-        {
-            title: '创意ID',
-            dataIndex: 'sysAdcreativeId',
-            key: 'sysAdcreativeId',
-            align: 'center',
-            width: 80,
-            render: (a: any, b: any) => {
-                if (a) {
-                    return <Space><span style={{ fontSize: "12px" }}>{a}</span><AdcreativePopover id={a}/></Space>
-                } else {
-                    return <span>--</span>
-                }
-            }
-        },
-        {
-            title: '落地页ID',
-            dataIndex: 'sysPageId',
-            key: 'sysPageId',
-            align: 'center',
-            width: 80,
-            render: (a: any, b: any) => {
-                if (a) {
-                    return <Space><span style={{ fontSize: "12px" }}>{a}</span><a style={{ color: '#1890ff', fontSize: 12 }} onClick={() => { callback(b.sysPageId, 'page') }}><EyeOutlined /></a></Space>
+                    return <Space><span style={{ fontSize: "12px" }}>{a}</span><TargetingPopover id={b.sysTargetingId} /></Space>
                 } else {
                     return <span>--</span>
                 }
@@ -148,13 +131,15 @@ function tableConfig(callback: (data: any, type: 'log' | 'page') => void): any {
             }
         },
         {
-            title: <span style={{ marginLeft: 10 }}>操作</span>,
-            dataIndex: 'taskName',
-            key: 'taskName',
-            fixed: 'right',
+            title: <span style={{ marginLeft: 10 }}>任务反馈</span>,
+            dataIndex: 'total',
+            key: 'total',
             render: (a: any, b: any) => {
-                return <Space style={{ marginLeft: 10 }}>
-                    <a style={{ color: '#1890ff', fontSize: 12 }} onClick={() => { callback({ taskId: b.id, campaignName: b.campaignName }, 'log') }}>日志</a>
+                let errCount = a - (b?.successCount || 0)
+                return <Space style={{ fontSize: "12px", marginLeft: 10 }} size={20}>
+                    <span><Badge status="processing" />总条数:{a}条</span>
+                    {b?.successCount ? <span><Badge status="success" />成功:{b?.successCount || 0}条</span> : null}
+                    {errCount ? <span><Badge status="error" />失败:{errCount}条</span> : null}
                 </Space>
             }
         }

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

@@ -1,6 +1,7 @@
 import FlieBox from '@/components/FileBoxAD'
 import HocError from '@/Hoc/HocError'
-import { Tabs } from 'antd'
+import { QuestionCircleFilled } from '@ant-design/icons'
+import { Button, Space, Tabs, Tooltip } from 'antd'
 import React, { useEffect, useRef, useState } from 'react'
 import { useModel } from 'umi'
 import AddLandingPage from '../../components/addLandingPage'
@@ -38,10 +39,18 @@ function Cloud() {
             className={style.card}
             activeKey={mediaType}
         >
+
             {
                 ['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}>
+                        <Tabs onChange={(activeKey: any) => { set({ belongUser: activeKey }) }} activeKey={belongUser} tabBarExtraContent={<Space>
+                            {
+                                mediaType === 'PAGE' ? <Button type="primary" size='small' onClick={() => { setVisible(true) }}>新建素材</Button> : <Button size='small' type="primary" onClick={() => { set({ imgVisrible: true }) }}>新建素材</Button>
+                            }
+                            <Tooltip title="功能都在下方内容区右键" placement="left">
+                                <QuestionCircleFilled />
+                            </Tooltip>
+                        </Space>}>
                             <TabPane tab={'个人本地'} key={1} />
                             <TabPane tab={'公共本地'} key={0} />
                         </Tabs>
@@ -60,11 +69,11 @@ function Cloud() {
             }
         </Tabs>
         {/* 落地页新增 复制 */}
-        {visible && <AddLandingPage visible={visible} hideModal={() => setVisible(false)} ajax={list} id={id}/>}
+        {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}/>}
+        {copyVisible && <BathLauCopy visible={copyVisible} onClose={() => setCopyVisible(false)} id={id} ajaxHome={list} />}
     </div>
 
 }

+ 27 - 14
src/utils/utils.ts

@@ -1,3 +1,4 @@
+import { message } from 'antd';
 import { RcFile } from 'antd/lib/upload';
 import { parse } from 'querystring';
 
@@ -99,20 +100,32 @@ export const getImgSize = (file: RcFile): Promise<{ width: number, height: numbe
 // 返回落地页组件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'
+    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 ''
+}
+
+// 点击复制
+export const copy = (str: string) => {
+  let element = document.createElement("textarea");
+  element.id = 'myTextarea'
+  element.textContent = str
+  document.body.append(element);
+  (document.getElementById('myTextarea') as any).select();
+  document.execCommand("Copy")
+  document.body.removeChild(element);
+  message.success(`复制成功:${str}`)
 }