wjx vor 8 Monaten
Ursprung
Commit
5b9d4d3d5b

+ 10 - 8
src/components/InputName/index.tsx

@@ -7,12 +7,13 @@ import './index.less'
 
 interface Props extends TextAreaProps {
     length?: number
+    isCurrent?: Array<'素材名称'>
 }
 /**
  * 广告名称 设置 组件
  * @returns 
  */
-const InputName: React.FC<Props> = ({ value, onChange, length, ...props }) => {
+const InputName: React.FC<Props> = ({ isCurrent, value, onChange, length, ...props }) => {
 
     /******************************/
     const [status, setStatus] = useState<{ status?: string }>({})
@@ -69,26 +70,27 @@ const InputName: React.FC<Props> = ({ value, onChange, length, ...props }) => {
         onChange?.(value + `-${matching}` as any)
     }
 
-    return <Space direction="vertical">
+    return <Space direction="vertical" size={0}>
         <Space align="end">
             <Input.TextArea value={value} {...props} onChange={(e) => {
                 onChange?.(e)
             }} />
             {length && <span style={Object.keys(status).length > 0 ? { color: 'red' } : {}}>{txtLength(value as any)}/{length}</span>}
         </Space>
-        {/* <div className="matching">
+        {isCurrent && isCurrent?.length > 0 && <div className="matching">
             <span>通配符:</span>
             <Space>
-                <Button type="link" onClick={() => insertMatching('<定向名>')} style={{ padding: 0 }} disabled={value?.toString()?.includes('<定向名>')}>+定向名</Button>
-                <Button type="link" onClick={() => insertMatching('<素材名>')} style={{ padding: 0 }} disabled={value?.toString()?.includes('<素材名>')}>+素材名</Button>
-                <Button type="link" onClick={() => insertMatching('<落地页名>')} style={{ padding: 0 }} disabled={value?.toString()?.includes('<落地页名>')}>+落地页名</Button>
+                {/* <Button type="link" onClick={() => insertMatching('<定向名>')} style={{ padding: 0 }} disabled={value?.toString()?.includes('<定向名>')}>+定向名</Button> */}
+                {isCurrent.includes('素材名称') && <Button type="link" onClick={() => insertMatching('<素材名>')} style={{ padding: 0 }} disabled={value?.toString()?.includes('<素材名>')}>+素材名</Button>}
+                {/* <Button type="link" onClick={() => insertMatching('<落地页名>')} style={{ padding: 0 }} disabled={value?.toString()?.includes('<落地页名>')}>+落地页名</Button>
                 <Button type="link" onClick={() => insertMatching('<优化目标>')} style={{ padding: 0 }} disabled={value?.toString()?.includes('<优化目标>')}>+优化目标</Button>
 
                 <Dropdown overlay={menu} trigger={['click']}>
                     <a style={{ marginLeft: 50 }} onClick={e => e.preventDefault()}><DiffOutlined /> 插入更多</a>
-                </Dropdown>
+                </Dropdown> */}
             </Space>
-        </div> */}
+        </div>}
+        
     </Space>
 }
 

+ 2 - 1
src/pages/launchSystemV3/adqv3/ad/index.tsx

@@ -199,7 +199,7 @@ const Ad: React.FC<ADQV3.AdProps> = ({ userId, creativeHandle }) => {
             <Col>
                 <DatePicker.RangePicker
                     placeholder={['创建开始日期', '创建结束日期']}
-                    onChange={(e, options) => { 
+                    onChange={(e, options) => {
                         set_queryFrom({ ...queryFrom, beginDate: options[0], endDate: options[1] })
                     }}
                 />
@@ -438,6 +438,7 @@ const Ad: React.FC<ADQV3.AdProps> = ({ userId, creativeHandle }) => {
             visible={addDynamicVisible}
             onClose={() => {
                 setAddDynamicVisible(false)
+                setTactics(undefined)
                 getAdqV3AdList.refresh()
             }}
             tactics={tactics}

+ 46 - 5
src/pages/launchSystemV3/adqv3/ad/tableConfig.tsx

@@ -7,7 +7,7 @@ import { ADGROUP_STATUS } from '../const'
 import SwitchStatus from './switchStatus'
 import TimeSeriesLook from '@/pages/launchSystemNew/adq/ad/timeSeriesLook'
 import CreativePreview from '../../adMonitorListV3/CreativePreview'
-import { BID_MODE_ENUM, OPTIMIZATIONGOAL_ENUM } from '../../tencentAdPutIn/const'
+import { BID_MODE_ENUM, MARKETING_CARRIER_TYPE_ENUM, MARKETING_GOAL_ENUM, MARKETING_TARGET_TYPE_ENUM, OPTIMIZATIONGOAL_ENUM, SITE_SET_ENUM } from '../../tencentAdPutIn/const'
 function tableConfig(onChange: () => void, creativeHandle?: (id: number) => void): any {
     return [
         {
@@ -165,10 +165,40 @@ function tableConfig(onChange: () => void, creativeHandle?: (id: number) => void
             dataIndex: 'dailyBudget',
             key: 'dailyBudget',
             align: 'center',
-            width: 70,
-            // render: (a: string, b: any) => {
-            //     return <InputUpdate title={a} isNum={true} dataIndex={'dailyBudget'} record={b} handleSave={handleSaveDaily} />
-            // }
+            width: 70
+        },
+        {
+            title: '营销目的',
+            dataIndex: 'marketingGoal',
+            key: 'marketingGoal',
+            align: 'center',
+            width: 80,
+            ellipsis: true,
+            render: (a: any) => {
+                return MARKETING_GOAL_ENUM[a as keyof typeof MARKETING_GOAL_ENUM]
+            }
+        },
+        {
+            title: '推广产品类型',
+            dataIndex: 'marketingTargetType',
+            key: 'marketingTargetType',
+            align: 'center',
+            width: 80,
+            ellipsis: true,
+            render: (a: any) => {
+                return MARKETING_TARGET_TYPE_ENUM[a as keyof typeof MARKETING_TARGET_TYPE_ENUM]
+            }
+        },
+        {
+            title: '营销载体类型',
+            dataIndex: 'marketingCarrierType',
+            key: 'marketingCarrierType',
+            align: 'center',
+            width: 80,
+            ellipsis: true,
+            render: (a: any) => {
+                return MARKETING_CARRIER_TYPE_ENUM[a as keyof typeof MARKETING_CARRIER_TYPE_ENUM]
+            }
         },
         {
             title: '是否开启自动版位功能',
@@ -180,6 +210,17 @@ function tableConfig(onChange: () => void, creativeHandle?: (id: number) => void
                 return a ? '开' : '关'
             }
         },
+        {
+            title: '版位选择',
+            dataIndex: 'siteSet',
+            key: 'siteSet',
+            align: 'center',
+            width: 80,
+            ellipsis: true,
+            render: (a: any) => {
+                return a ? a.map((item: string | number) => SITE_SET_ENUM[item as keyof typeof SITE_SET_ENUM]).toString() : '--'
+            }
+        },
         {
             title: '定向条件描述',
             dataIndex: 'targetingTranslation',

+ 11 - 7
src/pages/launchSystemV3/adqv3/config.ts

@@ -18,13 +18,17 @@ const txAdConfig = [
             { title: '出价类型', dataIndex: 'smartBidType', label: '广告详情', default: 13, width: 80 },
             { title: '出价策略', dataIndex: 'bidStrategy', label: '广告详情', default: 14, width: 80 },
             { title: '广告组日预算(元)', dataIndex: 'dailyBudget', label: '广告详情', default: 15, width: 70 },
-            { title: '是否开启自动版位功能', dataIndex: 'automaticSiteEnabled', label: '广告详情', default: 16, width: 80 },
-            { title: '定向条件描述', dataIndex: 'targetingTranslation', label: '广告详情', default: 17, width: 80 },
-            { title: '创建时间', dataIndex: 'createdTime', label: '广告详情', default: 18, width: 140 },
-            { title: '是否已删除', dataIndex: 'isDeleted', label: '广告详情', default: 19, width: 60 },
-            { title: '广告状态', dataIndex: 'systemStatus', label: '广告详情', default: 20, width: 80 },
-            { title: '创意预览', dataIndex: 'dynamicCreativeList', label: '广告详情', default: 21, width: 150 },
-            { title: '操作', dataIndex: 'cz', label: '广告详情', default: 22, width: 65 },
+            { title: '营销目的', dataIndex: 'marketingGoal', label: '广告详情', default: 16, width: 80 },
+            { title: '推广产品类型', dataIndex: 'marketingTargetType', label: '广告详情', default: 17, width: 90 },
+            { title: '营销载体类型', dataIndex: 'marketingCarrierType', label: '广告详情', default: 18, width: 90 },
+            { title: '是否开启自动版位功能', dataIndex: 'automaticSiteEnabled', label: '广告详情', default: 19, width: 80 },
+            { title: '版位选择', dataIndex: 'siteSet', label: '广告详情', default: 20, width: 100 },
+            { title: '定向条件描述', dataIndex: 'targetingTranslation', label: '广告详情', default: 21, width: 80 },
+            { title: '创建时间', dataIndex: 'createdTime', label: '广告详情', default: 22, width: 140 },
+            { title: '是否已删除', dataIndex: 'isDeleted', label: '广告详情', default: 23, width: 60 },
+            { title: '广告状态', dataIndex: 'systemStatus', label: '广告详情', default: 24, width: 80 },
+            { title: '创意预览', dataIndex: 'dynamicCreativeList', label: '广告详情', default: 25, width: 150 },
+            { title: '操作', dataIndex: 'cz', label: '广告详情', default: 26, width: 65 },
         ]
     }
 ]

+ 6 - 3
src/pages/launchSystemV3/components/TextAideInput/index.tsx

@@ -12,7 +12,8 @@ interface Props {
     onChange?: (value: any) => void;
     style?: React.CSSProperties;
     placeholder?: string;
-    maxTextLength?: number
+    maxTextLength?: number;
+    isShowAjax?: boolean
 }
 
 /**
@@ -23,7 +24,7 @@ interface Props {
 const TextAideInput: React.FC<Props> = (props) => {
 
     /************************/
-    const { value, onChange, style, placeholder, maxTextLength = 10 } = props
+    const { value, onChange, style, placeholder, maxTextLength = 10, isShowAjax = true } = props
     const [text, setText] = useState<any>(value)
     const [descriptionShow, setDescriptionshow] = useState(false)
 
@@ -62,7 +63,9 @@ const TextAideInput: React.FC<Props> = (props) => {
                 style={style}
                 value={text}
                 onFocus={() => {
-                    setDescriptionshow(true)
+                    if (isShowAjax) {
+                        setDescriptionshow(true)
+                    }
                     textList(value)
                 }}
                 onBlur={() => {

+ 194 - 0
src/pages/launchSystemV3/components/VideoChannel/index.tsx

@@ -0,0 +1,194 @@
+import { useAjax } from "@/Hook/useAjax"
+import { CheckOutlined, QuestionCircleOutlined, SyncOutlined } from "@ant-design/icons"
+import { Button, message, Modal, Space, Table, Tooltip } from "antd"
+import React, { useEffect, useState } from "react"
+import style from '../GoodsModal/index.less'
+import columns from './tableConfig'
+import { getVideoChannelInfoApi, getVideoChannelInfoBatchApi } from "@/services/adqV3/global"
+
+/**
+ * 获取视频号列表
+ * @returns 
+ */
+interface Props {
+    visible?: boolean,
+    onClose?: () => void,
+    onChange?: (data: PULLIN.AccountCreateLogsProps[]) => void,
+    data: PULLIN.AccountCreateLogsProps[]
+}
+const VideoChannel: React.FC<Props> = (props) => {
+
+    /************************/
+    const { visible, onClose, data: data1, onChange } = props
+    const [tableData, setTableData] = useState<any[]>([])//table数据
+    const [selectAdz, setSelectAdz] = useState<number>(1)   // 选择广告主
+    const [data, setData] = useState<PULLIN.AccountCreateLogsProps[]>(data1)
+    const [loading, setLoading] = useState<boolean>(false)
+
+    const getVideoChannelInfo = useAjax((params) => getVideoChannelInfoApi(params))
+    const getVideoChannelInfoBatch = useAjax((params) => getVideoChannelInfoBatchApi(params))
+    /************************/
+
+    useEffect(() => {
+        if (data?.length > 0) {
+            getList(data[selectAdz - 1].accountId)
+        } else {
+            setTableData([])
+        }
+    }, [selectAdz])
+
+    // 获取公众号列表
+    const getList = (accountId: number) => {
+        getVideoChannelInfo.run({ accountId }).then(res => {
+            setTableData(res || [])
+        })
+    }
+
+    const handleOk = () => {
+        onChange && onChange(data)
+    }
+
+    /** 设置选中广告主 */
+    const handleSelectAdz = (value: number) => {
+        if (value === selectAdz) {
+            return
+        }
+        setSelectAdz(value)
+    }
+
+    /** 表格选折 */
+    const onChangeTable = (_: React.Key[], selectedRows: any) => {
+        let newData = JSON.parse(JSON.stringify(data))
+        newData[selectAdz - 1]['videoChannelList'] = selectedRows
+        setData([...newData])
+    }
+
+    // 清空已选
+    const clearGoods = () => {
+        let newData = JSON.parse(JSON.stringify(data))
+        newData[selectAdz - 1]['videoChannelList'] = []
+        setData([...newData])
+    }
+
+    /** 一键设置 */
+    const setOnekey = (isFirst?: boolean) => {
+        let newData: PULLIN.AccountCreateLogsProps[] = JSON.parse(JSON.stringify(data))
+        const hide = message.loading(`正在设置...`, 0, () => {
+            message.success('设置成功');
+        });
+        if (isFirst) {
+            setLoading(true)
+            let ajax = data.map(item => getVideoChannelInfoApi({ accountId: item.accountId }))
+            Promise.all(ajax).then(res => {
+                if (res) {
+                    res.forEach(a => {
+                        let data = a?.data?.[0] || {}
+                        newData = newData.map(item => {
+                            if (item.accountId.toString() === data.accountId.toString()) {
+                                return { ...item, videoChannelList: [data] }
+                            }
+                            return item
+                        })
+                    })
+                    setData(newData)
+                }
+                message.success('设置完成');
+                setLoading(false)
+                hide()
+            }).catch(() => {
+                message.success('设置失败');
+                setLoading(false)
+                hide()
+            })
+        } else {
+            let wechatChannelNames: string[] = data[selectAdz - 1]['videoChannelList']?.map((item: { wechatChannelsAccountName: string }) => item.wechatChannelsAccountName) || []
+            getVideoChannelInfoBatch.run({ accountIdList: newData?.filter(item => item.accountId !== data[selectAdz - 1].accountId)?.map(item => item?.accountId) }).then(res => {
+                if (res?.length > 0) {
+                    res.forEach((i: { accountId: number, wechatChannelsAccountName: string }) => {
+                        if (wechatChannelNames.includes(i.wechatChannelsAccountName)) {
+                            newData = newData.map(item => {
+                                if (item.accountId.toString() === i.accountId.toString()) {
+                                    return { ...item, videoChannelList: [i] }
+                                }
+                                return item
+                            })
+                        }
+                    })
+                    setData(newData)
+                }
+                message.success('设置完成');
+                hide()
+            })
+        }
+    }
+
+
+    return <Modal
+        title={<Space>
+            <strong>选择视频号</strong>
+            <Button type="link" danger onClick={() => setOnekey(true)} loading={loading}>一键设置第一个视频号给账户</Button>
+        </Space>}
+        visible={visible}
+        onCancel={() => { onClose && onClose() }}
+        onOk={handleOk}
+        width={900}
+        className={`${style.SelectPackage} modalResetCss`}
+        bodyStyle={{ padding: '0 10px 0 10px' }}
+    >
+        <div className={style.content}>
+            <div className={style.left}>
+                <h4 className={style.title}>媒体账户</h4>
+                <div className={style.accountIdList}>
+                    {data?.map((item, index) => {
+                        let videoChannelList = data[index].videoChannelList || []
+                        return <div key={index} onClick={() => { handleSelectAdz(index + 1) }} className={`${style.accItem} ${selectAdz === index + 1 && style.select} `}>
+                            {item?.accountId}
+                            {videoChannelList?.length > 0 && <CheckOutlined style={{ color: '#1890ff' }} />}
+                        </div>
+                    })}
+                </div>
+            </div>
+            <div className={style.right}>
+                <Space style={{ marginBottom: 10 }} align="end" size={0}>
+                    <Button icon={<SyncOutlined />} type='link' loading={getVideoChannelInfo?.loading} onClick={() => { getList(data[selectAdz - 1].accountId) }}>刷新</Button>
+                    {data?.length > 1 && <Button disabled={!data[selectAdz - 1]['videoChannelList']?.length} onClick={() => setOnekey()} type="link" loading={getVideoChannelInfo.loading}>
+                        <Space>
+                            <span>一键设置</span>
+                            <Tooltip color="#FFF" overlayInnerStyle={{ color: '#000' }} title="设置其它账号有相同名称的视频号为那个账号的视频号(注意需要用户视频号名称称相同,否则不设置)">
+                                <QuestionCircleOutlined />
+                            </Tooltip>
+                        </Space>
+                    </Button>}
+                    {(data[selectAdz - 1]?.videoChannelList || [])?.length > 0 && <Button type='link' onClick={() => { clearGoods() }}>清空</Button>}
+                </Space>
+                <Table
+                    columns={columns()}
+                    dataSource={tableData}
+                    size="small"
+                    loading={getVideoChannelInfo?.loading}
+                    scroll={{ y: 400 }}
+                    rowKey={'wechatChannelsAccountId'}
+                    rowSelection={{
+                        type: 'radio',
+                        selectedRowKeys: data[selectAdz - 1]?.videoChannelList?.map((item: any) => item?.wechatChannelsAccountId),
+                        onChange: onChangeTable
+                    }}
+                    onRow={(record) => ({
+                        onClick: () => {
+                            let newDatas = JSON.parse(JSON.stringify(data))
+                            let oldData = newDatas[selectAdz - 1]?.videoChannelList || []
+                            const selected = oldData?.some((item: any) => item?.wechatChannelsAccountId === record.wechatChannelsAccountId);
+                            const newSelectedRows = selected
+                                ? oldData?.filter((item: any) => item?.wechatChannelsAccountId !== record.wechatChannelsAccountId)
+                                : [...oldData, record];
+                            newDatas[selectAdz - 1]['videoChannelList'] = newSelectedRows;
+                            setData([...newDatas])
+                        },
+                    })}
+                />
+            </div>
+        </div>
+    </Modal>
+}
+
+export default React.memo(VideoChannel)

+ 25 - 0
src/pages/launchSystemV3/components/VideoChannel/tableConfig.tsx

@@ -0,0 +1,25 @@
+import { TableProps } from "antd"
+import React from "react"
+
+const columns = (): TableProps<any>['columns'] => [
+    {
+        title: '视频号名称',
+        dataIndex: 'wechatChannelsAccountName',
+        key: 'wechatChannelsAccountName',
+        ellipsis: true,
+        render: (value) => {
+            return <span style={{ fontSize: "12px", cursor: 'pointer' }}>{value}</span>
+        }
+    },
+    // {
+    //     title: '视频号ID',
+    //     dataIndex: 'wechatChannelsAccountId',
+    //     key: 'wechatChannelsAccountId',
+    //     ellipsis: true,
+    //     render: (value) => {
+    //         return <span style={{ fontSize: "12px", cursor: 'pointer' }}>{value}</span>
+    //     }
+    // }
+]
+
+export default columns

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
src/pages/launchSystemV3/tencentAdPutIn/const.ts


+ 232 - 2
src/pages/launchSystemV3/tencentAdPutIn/create/Dynamic/creativeConversionAssistant.tsx

@@ -5,6 +5,11 @@ import style from '../index.less'
 import New1Radio from "@/pages/launchSystemV3/components/New1Radio";
 import { useUpdateEffect } from "ahooks";
 import { QuestionCircleFilled } from "@ant-design/icons";
+import { txtLength } from "@/utils/utils";
+import TextAideInput from "@/pages/launchSystemV3/components/TextAideInput";
+import styles from '../Material/index.less'
+import { useModel } from "umi";
+import SelectCloud from "@/pages/launchSystemNew/components/selectCloud";
 
 /**
  * 营销组件
@@ -21,9 +26,32 @@ const CreativeConversionAssistant: React.FC<{ automaticSiteEnabled?: boolean }>
     const linkNameType = Form.useWatch(['textLink', 'value', 'linkNameType'], form)
     const conversionDataType = Form.useWatch(['showData', 'value', 'conversionDataType'], form)
     const conversionTargetType = Form.useWatch(['showData', 'value', 'conversionTargetType'], form)
+    const floatingZone = Form.useWatch('floatingZone', form)
+    const floatingZoneSwitch = Form.useWatch(['floatingZone', 'value', 'floatingZoneSwitch'], form)
+    const floatingZoneType = Form.useWatch(['floatingZone', 'value', 'floatingZoneType'], form)
+    const image = Form.useWatch(['floatingZone', 'value', 'image'], form)
     const creativeTemplateId = Form.useWatch('creativeTemplateId', form)
     const cardType: string[] = Form.useWatch('cardType', form)
     const [cardData, setCardData] = useState<{ label: string, value: string, disabled?: boolean }[]>([])
+    const { init } = useModel('useLaunchAdq.useBdMediaPup')
+    const [materialConfig, setMaterialConfig] = useState<{
+        adcreativeTemplateId?: number,
+        type: string,
+        cloudSize: { relation: string, width: number, height: number }[],
+        list: any[],
+        index: number,
+        max: number,
+        sliderImgContent: any,
+        isGroup?: boolean
+    }>({
+        type: '',//类型
+        cloudSize: [],//素材搜索条件
+        list: [],//素材
+        index: 0, // 素材组下标
+        max: 1,//素材数量
+        sliderImgContent: undefined
+    })//图片素材配置
+    const [selectVideoVisible, setSelectVideoVisible] = useState(false)
     /**************************************/
 
     useUpdateEffect(() => {
@@ -220,6 +248,7 @@ const CreativeConversionAssistant: React.FC<{ automaticSiteEnabled?: boolean }>
         return null
     }, [creativeComponents?.action_button, pageSpec, actionButtonShow, cardType, creativeTemplateId])
 
+    /** 数据外显 */
     const showDataContent = useMemo(() => {
         let showData = creativeComponents?.show_data;
         if (showData) {
@@ -294,6 +323,174 @@ const CreativeConversionAssistant: React.FC<{ automaticSiteEnabled?: boolean }>
         return null
     }, [creativeComponents?.show_data, linkNameType, showDataShow, creativeTemplateId])
 
+    /** 浮层卡片 */
+    const floatingZoneContent = useMemo(() => {
+        let floatingZone = creativeComponents?.floating_zone;
+        if (floatingZone) {
+            let required = floatingZone?.required
+            let children = floatingZone.children
+            const { floating_zone_type, floating_zone_image_id, floating_zone_single_image_id, floating_zone_name, floating_zone_desc, floating_zone_button_text } = children
+            const floatingZoneTypeList: PULLIN.DataType[] = []
+            if (floating_zone_image_id) {
+                floatingZoneTypeList.push({ label: "图文复合类型", value: "FLOATING_ZONE_TYPE_IMAGE_TEXT" })
+            }
+            if (floating_zone_single_image_id) {
+                floatingZoneTypeList.push({ label: "单图类型", value: "FLOATING_ZONE_TYPE_SINGLE_IMAGE" })
+            }
+            let floatingZoneButtonTextEnumeration: { label: string, value: string }[] = [];
+            if (floating_zone_button_text) {
+                floatingZoneButtonTextEnumeration = (floating_zone_button_text.enumProperty.enumeration as { value: string, description: string }[]).map(item => ({ label: item.value, value: item.value }))
+            }
+
+            return <>
+                <Form.Item style={{ marginBottom: 0 }}>
+                    <div className={style.newSpace} style={{ border: 'none' }}>
+                        <div className={style.newSpace_top}>
+                            <div className={style.newSpace_title}>浮层卡片</div>
+                            <Form.Item name={['floatingZone', 'value', 'floatingZoneSwitch']} rules={[{ required, message: '请选择浮层卡片!' }]} valuePropName="checked" style={{ marginBottom: 0 }}>
+                                <Switch disabled={required} />
+                            </Form.Item>
+                        </div>
+                        {floatingZoneSwitch && <Form.Item name={['floatingZone', 'value', 'floatingZoneType']} rules={[{ required: floating_zone_type?.required, message: `请选择` + floating_zone_type?.description }]} style={{ marginTop: 10, marginBottom: 0 }}>
+                            <New1Radio data={floatingZoneTypeList} />
+                        </Form.Item>}
+                    </div>
+
+                    {(floatingZoneSwitch && floatingZoneType === 'FLOATING_ZONE_TYPE_IMAGE_TEXT') ? <Card bordered className="cardResetCss newCss" bodyStyle={{ padding: 0 }}>
+                        <div className={style.newSpace}>
+                            <div className={style.newSpace_top} style={{ alignItems: 'flex-start' }}>
+                                <div className={style.newSpace_title}>{floating_zone_image_id?.description}</div>
+                                <Form.Item
+                                    rules={[{ required: true, message: '请选择素材!' }]}
+                                    name={['floatingZone', 'value', 'image']}
+                                >
+                                    <div className={`${styles.box} ${styles.image}`} style={{ width: 300, height: 160 }} onClick={() => {
+                                        init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: floating_zone_image_id.restriction.imageRestriction.width, height: floating_zone_image_id.restriction.imageRestriction.height }]], maxSize: floating_zone_image_id.restriction.imageRestriction.fileSize * 1024 })
+                                        setMaterialConfig({
+                                            ...materialConfig,
+                                            type: 'floatingZoneImage',
+                                            max: 1,
+                                            index: 1,
+                                            adcreativeTemplateId: creativeTemplateId
+                                        })
+                                        setTimeout(() => {
+                                            setSelectVideoVisible(true)
+                                        }, 100)
+                                    }}>
+                                        <p>
+                                            {image?.floatingZoneImageUrl ? <img src={image?.floatingZoneImageUrl} /> : <>
+                                                <span>{`推荐尺寸(${floating_zone_image_id.restriction.imageRestriction.width} x ${floating_zone_image_id.restriction.imageRestriction.height})`}</span>
+                                                <span>{`${floating_zone_image_id.restriction.imageRestriction.fileFormat?.map((str: any) => str?.replace('IMAGE_TYPE_', ''))};小于 ${floating_zone_image_id.restriction.imageRestriction.fileSize}KB`}</span>
+                                            </>}
+                                        </p>
+                                    </div>
+                                </Form.Item>
+                            </div>
+                        </div>
+                        <div className={style.newSpace}>
+                            <div className={style.newSpace_top}>
+                                <div className={style.newSpace_title}>{floating_zone_name?.description}</div>
+                                <Form.Item
+                                    name={['floatingZone', 'value', 'floatingZoneName']}
+                                    rules={[{
+                                        required: floating_zone_name.required, message: '请输入正确的' + floating_zone_name.description, validator: (rule, value) => {
+                                            if (!value) {
+                                                return Promise.reject()
+                                            } else if (!value.match(RegExp(floating_zone_name.restriction.textRestriction.textPattern))) {
+                                                return Promise.reject()
+                                            } else if (txtLength(value) > floating_zone_name.restriction.textRestriction.maxLength) {
+                                                return Promise.reject()
+                                            }
+                                            return Promise.resolve()
+                                        }
+                                    }]}
+                                    style={{ marginBottom: 0 }}
+                                >
+                                    <TextAideInput isShowAjax={false} placeholder={'请输入' + floating_zone_name.description} style={{ width: 480 }} maxTextLength={floating_zone_name.restriction.textRestriction.maxLength} />
+                                </Form.Item>
+                            </div>
+                        </div>
+                        <div className={style.newSpace}>
+                            <div className={style.newSpace_top}>
+                                <div className={style.newSpace_title}>{floating_zone_desc?.description}</div>
+                                <Form.Item
+                                    name={['floatingZone', 'value', 'floatingZoneDesc']}
+                                    rules={[{
+                                        required: floating_zone_desc.required, message: '请输入正确的' + floating_zone_desc.description, validator: (rule, value) => {
+                                            if (!value) {
+                                                return Promise.reject()
+                                            } else if (!value.match(RegExp(floating_zone_desc.restriction.textRestriction.textPattern))) {
+                                                return Promise.reject()
+                                            } else if (txtLength(value) > floating_zone_desc.restriction.textRestriction.maxLength) {
+                                                return Promise.reject()
+                                            }
+                                            return Promise.resolve()
+                                        }
+                                    }]}
+                                    style={{ marginBottom: 0 }}
+                                >
+                                    <TextAideInput isShowAjax={false} placeholder={'请输入' + floating_zone_desc.description} style={{ width: 480 }} maxTextLength={floating_zone_desc.restriction.textRestriction.maxLength} />
+                                </Form.Item>
+                            </div>
+                        </div>
+                        <div className={style.newSpace}>
+                            <div className={style.newSpace_top}>
+                                <div className={style.newSpace_title}>按钮文案</div>
+                                <Form.Item
+                                    name={['floatingZone', 'value', 'floatingZoneButtonText']}
+                                    rules={[{ required: true, message: '请选择按钮文案' }]}
+                                    style={{ marginBottom: 0 }}
+                                >
+                                    <Select
+                                        style={{ width: 480 }}
+                                        showSearch
+                                        placeholder="请选择按钮文案"
+                                        optionFilterProp="children"
+                                        filterOption={(input, option: any) =>
+                                            (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
+                                        }
+                                        options={floatingZoneButtonTextEnumeration}
+                                    />
+                                </Form.Item>
+                            </div>
+                        </div>
+                    </Card> : (floatingZoneSwitch && floatingZoneType === 'FLOATING_ZONE_TYPE_SINGLE_IMAGE') ? <Card bordered className="cardResetCss newCss" bodyStyle={{ padding: 0 }}>
+                        <div className={style.newSpace}>
+                            <div className={style.newSpace_top} style={{ alignItems: 'flex-start' }}>
+                                <div className={style.newSpace_title}>{floating_zone_single_image_id?.description}</div>
+                                <Form.Item
+                                    rules={[{ required: true, message: '请选择素材!' }]}
+                                    name={['floatingZone', 'value', 'image']}
+                                >
+                                    <div className={`${styles.box} ${styles.image}`} style={{ width: 300, height: 160 }} onClick={() => {
+                                        init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: floating_zone_single_image_id.restriction.imageRestriction.width, height: floating_zone_single_image_id.restriction.imageRestriction.height }]], maxSize: floating_zone_single_image_id.restriction.imageRestriction.fileSize * 1024 })
+                                        setMaterialConfig({
+                                            ...materialConfig,
+                                            type: 'floatingZoneSingleImage',
+                                            max: 1,
+                                            index: 1,
+                                            adcreativeTemplateId: creativeTemplateId
+                                        })
+                                        setTimeout(() => {
+                                            setSelectVideoVisible(true)
+                                        }, 100)
+                                    }}>
+                                        <p>
+                                            {image?.floatingZoneSingleImageUrl ? <img src={image?.floatingZoneSingleImageUrl} /> : <>
+                                                <span>{`推荐尺寸(${floating_zone_single_image_id.restriction.imageRestriction.width} x ${floating_zone_single_image_id.restriction.imageRestriction.height})`}</span>
+                                                <span>{`${floating_zone_single_image_id.restriction.imageRestriction.fileFormat?.map((str: any) => str?.replace('IMAGE_TYPE_', ''))};小于 ${floating_zone_single_image_id.restriction.imageRestriction.fileSize}KB`}</span>
+                                            </>}
+                                        </p>
+                                    </div>
+                                </Form.Item>
+                            </div>
+                        </div>
+                    </Card> : null}
+                </Form.Item>
+            </>
+        }
+        return null
+    }, [creativeComponents?.floating_zone, floatingZoneSwitch, floatingZoneType, image])
 
     useEffect(() => {
         let data = [
@@ -320,7 +517,7 @@ const CreativeConversionAssistant: React.FC<{ automaticSiteEnabled?: boolean }>
         }
     }, [cardType])
 
-    if (Object.keys(creativeComponents).some(key => ['text_link', 'action_button', 'show_data'].includes(key))) {
+    if (Object.keys(creativeComponents).some(key => ['text_link', 'action_button', 'show_data', 'floating_zone'].includes(key))) {
         return <Card
             title={<strong style={{ fontSize: 18 }}>营销组件</strong>}
             className="cardResetCss"
@@ -364,11 +561,44 @@ const CreativeConversionAssistant: React.FC<{ automaticSiteEnabled?: boolean }>
                         </Form.Item>
                     </div>
                 </div>
-
             </Form.Item>}
             {textLinkContent}
             {actionButtonContent}
             {showDataContent}
+            {floatingZoneContent}
+
+            {/* 选择视频素材 */}
+            {selectVideoVisible && <SelectCloud
+                visible={selectVideoVisible}
+                onClose={() => setSelectVideoVisible(false)}
+                sliderImgContent={materialConfig.type === 'floatingZoneSingleImage' ? image?.floatingZoneSingleImageUrl ? [{ url: image.floatingZoneSingleImageUrl }] : undefined : image?.floatingZoneImageUrl ? [{ url: image.floatingZoneImageUrl }] : undefined}
+                onChange={(content: any) => {
+                    if (content.length > 0) {
+                        if (materialConfig.type === 'floatingZoneSingleImage') {
+                            form.setFieldsValue({
+                                floatingZone: {
+                                    ...floatingZone,
+                                    value: {
+                                        ...floatingZone?.value,
+                                        image: { floatingZoneSingleImageId: content[0]?.id, floatingZoneSingleImageUrl: content[0]?.url, floatingZoneSingleImageMaterialType: 0 }
+                                    }
+                                }
+                            })
+                        } else {
+                            form.setFieldsValue({
+                                floatingZone: {
+                                    ...floatingZone,
+                                    value: {
+                                        ...floatingZone?.value,
+                                        image: { floatingZoneImageId: content[0]?.id, floatingZoneImageUrl: content[0]?.url, floatingZoneImageMaterialType: 0 }
+                                    }
+                                }
+                            })
+                        }
+                    }
+                    setSelectVideoVisible(false)
+                }}
+            />}
         </Card>
     } else {
         return null

+ 1 - 1
src/pages/launchSystemV3/tencentAdPutIn/create/Dynamic/creativeTemplateContent.tsx

@@ -32,7 +32,7 @@ const CreativeTemplateContent: React.FC<{ automaticSiteEnabled: boolean }> = ({
                 case 'brand':
                     brand = creativeComponents[key]
                     let page_type = brand.children.page_type
-                    let typeList = ["PAGE_TYPE_WECHAT_OFFICIAL_ACCOUNT_DETAIL", "PAGE_TYPE_H5_PROFILE", "PAGE_TYPE_NOT_USED"]
+                    let typeList = ["PAGE_TYPE_WECHAT_OFFICIAL_ACCOUNT_DETAIL", "PAGE_TYPE_H5_PROFILE", "PAGE_TYPE_NOT_USED", "PAGE_TYPE_WECHAT_CHANNELS_PROFILE"]
                     enumeration = (page_type.enumProperty.enumeration as { value: string, description: string }[]).filter((item: { value: string; }) => typeList.includes(item.value)).map(item => ({ label: item.value === 'PAGE_TYPE_NOT_USED' ? '品牌形象' : item.description, value: item.value }))
                     break
                 case 'jump_info':

+ 6 - 2
src/pages/launchSystemV3/tencentAdPutIn/create/Dynamic/creativeTemplateSetup.tsx

@@ -17,7 +17,7 @@ const CreativeTemplateSetup: React.FC = () => {
         className="cardResetCss"
     >
         <Form.Item label={<strong>创意状态</strong>} name="configuredStatus" rules={[{ required: true, message: '请选择创意状态' }]}>
-            <New1Radio data={Object.keys(AD_STATUS_ENUM).map(key => ({ label: AD_STATUS_ENUM[key], value: key }))} />
+            <New1Radio data={Object.keys(AD_STATUS_ENUM).map(key => ({ label: AD_STATUS_ENUM[key as keyof typeof AD_STATUS_ENUM], value: key }))} />
         </Form.Item>
         <Form.Item
             label={<strong>创意名称</strong>}
@@ -30,6 +30,10 @@ const CreativeTemplateSetup: React.FC = () => {
                         if (value && reg.test(value)) {
                             return Promise.reject()
                         }
+                        let regL = /[<>]/ig
+                        if (regL.test(value) && !value.includes('<素材名称>')) {
+                            return Promise.reject()
+                        }
                         return Promise.resolve()
                     }
                 },
@@ -43,7 +47,7 @@ const CreativeTemplateSetup: React.FC = () => {
                 }
             ]}
         >
-            <InputName placeholder='请输入创意名称' style={{ width: 480 }} length={50} />
+            <InputName placeholder='请输入创意名称' style={{ width: 480 }} length={50} isCurrent={['素材名称']}/>
         </Form.Item>
     </Card>
 }

+ 11 - 3
src/pages/launchSystemV3/tencentAdPutIn/create/Dynamic/index.tsx

@@ -4,7 +4,7 @@ import { Button, Space } from "antd"
 import { EditOutlined } from "@ant-design/icons"
 import { DispatchAddelivery } from ".."
 import NewDynamic from "./newDynamic"
-import { AD_STATUS_ENUM, CONVERSION_DATA_ENUM, CONVERSION_TARGET_ENUM, DELIVERY_MODE_ENUM, PAGE_TYPE_ENUM, TEXT_LINK_TYPE_ENUM, pageSpecFieldConVertUn } from "../../const"
+import { AD_STATUS_ENUM, CONVERSION_DATA_ENUM, CONVERSION_TARGET_ENUM, DELIVERY_MODE_ENUM, FLOATING_ZONE_TYPE_ENUM, PAGE_TYPE_ENUM, TEXT_LINK_TYPE_ENUM, pageSpecFieldConVertUn } from "../../const"
 import { getProfilesApi } from "@/services/adqV3/global"
 import { useAjax } from "@/Hook/useAjax"
 import { arraysHaveSameValues } from "@/utils/utils"
@@ -19,14 +19,13 @@ const Dynamic: React.FC<{ creativeTemplateAppellation?: string, creativeTemplate
     const { addelivery, setAddelivery, clearData, setAccountCreateLogs, accountCreateLogs } = useContext(DispatchAddelivery)!;
     const { adgroups, dynamic: dynamicData } = addelivery;
     const { deliveryMode, creativeTemplateId, dynamicCreativeName, creativeComponents, configuredStatus } = dynamicData
-    const { textLink, actionButton, showData, brand, mainJumpInfo } = creativeComponents || {}
+    const { textLink, actionButton, showData, brand, mainJumpInfo, floatingZoneComponent } = creativeComponents || {}
     const [newVisible, setNewVisible] = useState<boolean>(false);
     const [profileData, setprofileData] = useState<any>()
 
     const getProfiles = useAjax((params) => getProfilesApi(params))
     /***************************************/
 
-
     useEffect(() => {
         if (['PAGE_TYPE_H5_PROFILE'].includes(brand?.[0]?.value?.jumpInfo?.pageType)) {
             getProfiles.run({}).then(res => {
@@ -82,6 +81,15 @@ const Dynamic: React.FC<{ creativeTemplateAppellation?: string, creativeTemplate
                         <p>数据类型:{CONVERSION_DATA_ENUM[showData?.[0]?.value?.conversionDataType as keyof typeof CONVERSION_DATA_ENUM]}</p>
                         <p>转化行为:{CONVERSION_TARGET_ENUM[showData?.[0]?.value?.conversionTargetType as keyof typeof CONVERSION_TARGET_ENUM]}</p>
                     </>}
+                    {floatingZoneComponent && Object.keys(floatingZoneComponent).length > 0 && <>
+                        <p style={{ fontWeight: 'bold', color: '#000' }}>浮层卡片:{floatingZoneComponent?.floatingZoneSwitch ? '开启' : '关闭'}</p>
+                        {floatingZoneComponent?.floatingZoneType && <p>复层卡片类型:{FLOATING_ZONE_TYPE_ENUM[floatingZoneComponent.floatingZoneType as keyof typeof FLOATING_ZONE_TYPE_ENUM]}</p>}
+                        {floatingZoneComponent?.floatingZoneImageUrl && <p style={{ display: 'flex', alignItems: 'flex-start' }}>创意图片:<img src={floatingZoneComponent?.floatingZoneImageUrl} width={40} /></p>}
+                        {floatingZoneComponent?.floatingZoneSingleImageUrl && <p style={{ display: 'flex', alignItems: 'flex-start' }}>创意图片:<img src={floatingZoneComponent?.floatingZoneSingleImageUrl} width={40} /></p>}
+                        {floatingZoneComponent?.floatingZoneName && <p>文案:{floatingZoneComponent.floatingZoneName}</p>}
+                        {floatingZoneComponent?.floatingZoneDesc && <p>文案:{floatingZoneComponent.floatingZoneDesc}</p>}
+                        {floatingZoneComponent?.floatingZoneButtonText && <p>按钮文案:{floatingZoneComponent.floatingZoneButtonText}</p>}
+                    </>}
                 </>}
             </div>
             <div className={style.detail_footer}>

+ 65 - 3
src/pages/launchSystemV3/tencentAdPutIn/create/Dynamic/newDynamic.tsx

@@ -219,7 +219,7 @@ const NewDynamic: React.FC<Props> = ({ value: newValue, visible, onClose, onChan
                     case 'brand':
                         let brand = result[key]
                         let page_type = brand.children.page_type
-                        let typeList = ["PAGE_TYPE_WECHAT_OFFICIAL_ACCOUNT_DETAIL", "PAGE_TYPE_H5_PROFILE", "PAGE_TYPE_NOT_USED"]
+                        let typeList = ["PAGE_TYPE_WECHAT_OFFICIAL_ACCOUNT_DETAIL", "PAGE_TYPE_H5_PROFILE", "PAGE_TYPE_NOT_USED", "PAGE_TYPE_WECHAT_CHANNELS_PROFILE"]
                         let enumeration = (page_type.enumProperty.enumeration as { value: string, description: string }[]).filter((item: { value: string; }) => typeList.includes(item.value))
                         values.jumpInfo = {
                             pageType: enumeration[0]?.value
@@ -253,6 +253,23 @@ const NewDynamic: React.FC<Props> = ({ value: newValue, visible, onClose, onChan
                         }
                         values.actionButtonShow = true
                         break
+                    case 'floating_zone':
+                        let floatingZone = result[key]
+                        let floatingZoneData: any = {
+                            value: {
+                                floatingZoneSwitch: false,
+                                floatingZoneType: 'FLOATING_ZONE_TYPE_IMAGE_TEXT',
+                                floatingZoneButtonText: '了解更多'
+                            }
+                        }
+                        if (floatingZone?.children?.floating_zone_image_id && floatingZone?.children?.floating_zone_button_text) {
+                            floatingZoneData.value.floatingZoneButtonText = floatingZone.children.floating_zone_button_text?.enumProperty?.enumeration[0]?.value
+                        }
+                        if (floatingZone?.required) {
+                            floatingZoneData.value.floatingZoneSwitch = true
+                        }
+                        values.floatingZone = floatingZoneData
+                        break
                 }
             })
             if ([1707, 1708].includes(creativeTemplateId)) {
@@ -264,9 +281,7 @@ const NewDynamic: React.FC<Props> = ({ value: newValue, visible, onClose, onChan
         }
     }
 
-
     const handleOk = (values: any) => {
-        console.log(values)
         const {
             creativeTemplateStyle,
             brand,
@@ -279,6 +294,7 @@ const NewDynamic: React.FC<Props> = ({ value: newValue, visible, onClose, onChan
             actionButton,
             showDataShow,
             showData,
+            floatingZone,
             ...surplusValues
         } = values
 
@@ -373,6 +389,23 @@ const NewDynamic: React.FC<Props> = ({ value: newValue, visible, onClose, onChan
         if (showDataShow) {
             creativeComponents.showData = [showData]
         }
+        if (floatingZone?.value) {
+            let value = floatingZone.value
+            let { image, ...val } = value
+            if (val.floatingZoneType === "FLOATING_ZONE_TYPE_SINGLE_IMAGE") {
+                delete image?.floatingZoneImageId
+                delete image?.floatingZoneImageUrl
+                delete image?.floatingZoneImageMaterialType
+            } else {
+                delete image?.floatingZoneSingleImageId
+                delete image?.floatingZoneSingleImageUrl
+                delete image?.floatingZoneSingleImageMaterialType
+            }
+            creativeComponents.floatingZoneComponent = {
+                ...val,
+                ...image
+            }
+        }
         dynamicValues.creativeComponents = creativeComponents
         setMaterialData(newMaterialData)
         setTextData(newTextData)
@@ -388,6 +421,7 @@ const NewDynamic: React.FC<Props> = ({ value: newValue, visible, onClose, onChan
                     actionButton,
                     showData,
                     mainJumpInfo,
+                    floatingZoneComponent
                 },
                 ...surplusValues
             } = JSON.parse(JSON.stringify(value))
@@ -456,6 +490,34 @@ const NewDynamic: React.FC<Props> = ({ value: newValue, visible, onClose, onChan
                 }
             }
 
+            if (floatingZoneComponent && Object.keys(floatingZoneComponent).length) {
+                const {
+                    floatingZoneImageId,
+                    floatingZoneImageUrl,
+                    floatingZoneImageMaterialType,
+                    floatingZoneSingleImageId,
+                    floatingZoneSingleImageUrl,
+                    floatingZoneSingleImageMaterialType,
+                    ...val
+                } = floatingZoneComponent
+                dynamicValues = {
+                    ...dynamicValues,
+                    floatingZone: {
+                        value: {
+                            image: {
+                                floatingZoneImageId,
+                                floatingZoneImageUrl,
+                                floatingZoneImageMaterialType,
+                                floatingZoneSingleImageId,
+                                floatingZoneSingleImageUrl,
+                                floatingZoneSingleImageMaterialType,
+                            },
+                            ...val
+                        }
+                    }
+                }
+            }
+
             if (cardType.length === 0 && isCardDynamic) {
                 cardType = ['not']
             }

+ 29 - 7
src/pages/launchSystemV3/tencentAdPutIn/create/addDynamic.tsx

@@ -14,6 +14,7 @@ import { columnsAddDynamic } from "./tableConfig";
 import { useAjax } from "@/Hook/useAjax";
 import { createDynamicTaskApi } from "@/services/adqV3";
 import TacticsS from "./TacticsS";
+import VideoChannel from "../../components/VideoChannel";
 const { Text, Title } = Typography;
 
 /**
@@ -24,8 +25,9 @@ const AddDynamic: React.FC<PULLIN.NewAddDynamic> = ({ visible, onChange, onClose
 
     /****************************************/
     const [addelivery, setAddelivery] = useState<PULLIN.AddeliveryProps>({ adgroups: {}, targeting: [], dynamic: {}, dynamicMaterialDTos: {}, dynamicCreativesTextDTOS: {}, mediaType: 0 })
-    const { adgroups } = addelivery
+    const { adgroups, dynamic } = addelivery
     const [wechatVisible, setWechatVisible] = useState<boolean>(false) // 选择微信公众号弹窗控制
+    const [channelsProfileVisible, setChannelsProfileVisible] = useState<boolean>(false) // 选择微信公众号弹窗控制
     const [materialData, setMaterialData] = useState<any>({}) // 素材数据
     const [textData, setTextData] = useState<any>({})
     const [accountCreateLogs, setAccountCreateLogs] = useState<PULLIN.AccountCreateLogsProps[]>([])  // 账户
@@ -90,10 +92,6 @@ const AddDynamic: React.FC<PULLIN.NewAddDynamic> = ({ visible, onChange, onClose
             message.error('请先配置广告信息')
             return
         }
-        if ((['MARKETING_TARGET_TYPE_WECHAT_OFFICIAL_ACCOUNT'].includes(adgroups?.marketingAssetOuterSpec?.marketingTargetType) || adgroups?.marketingCarrierType === 'MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT') && !accountCreateLogs?.some(item => item?.wechatChannelList?.length)) {
-            message.error('请先选择公众号')
-            return
-        }
         if (!(dynamic && Object.keys(dynamic).length)) {
             message.error('请先配置创意')
             return
@@ -110,6 +108,14 @@ const AddDynamic: React.FC<PULLIN.NewAddDynamic> = ({ visible, onChange, onClose
             message.error('请先选择落地页')
             return
         }
+        if ((['MARKETING_TARGET_TYPE_WECHAT_OFFICIAL_ACCOUNT'].includes(adgroups?.marketingAssetOuterSpec?.marketingTargetType) || adgroups?.marketingCarrierType === 'MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT') && !accountCreateLogs?.some(item => item?.wechatChannelList?.length)) {
+            message.error('请先选择公众号')
+            return
+        }
+        if (dynamic?.creativeComponents?.brand?.[0]?.value?.jumpInfo?.pageType === 'PAGE_TYPE_WECHAT_CHANNELS_PROFILE' && !accountCreateLogs?.some(item => item?.videoChannelList?.length)) {
+            message.error('请先选择视频号')
+            return
+        }
 
         let newTableData: any = {}, newDynamicCount = 0
 
@@ -447,7 +453,7 @@ const AddDynamic: React.FC<PULLIN.NewAddDynamic> = ({ visible, onChange, onClose
 
         let accountIdParamDTOMap: any = {}
         accountCreateLogs.forEach(item => {
-            let { pageList, productList, userActionSetsList, accountId, wechatChannelList } = item
+            let { pageList, productList, userActionSetsList, accountId, wechatChannelList, videoChannelList } = item
 
             let userActionSetsListDto = userActionSetsList?.map((item: any) => ({ id: item?.userActionSetId, type: item?.type })) // dataSourceId
 
@@ -463,6 +469,10 @@ const AddDynamic: React.FC<PULLIN.NewAddDynamic> = ({ visible, onChange, onClose
             if (wechatChannelList) {
                 map.wechatChannelId = wechatChannelList?.[0]?.wechatOfficialAccountId
             }
+            
+            if (videoChannelList) {
+                map.videoChannelId = videoChannelList?.[0]?.wechatChannelsAccountId
+            }
 
             accountIdParamDTOMap[accountId] = map
         })
@@ -487,7 +497,7 @@ const AddDynamic: React.FC<PULLIN.NewAddDynamic> = ({ visible, onChange, onClose
                     onOk: () => {
                         onChange?.()
                     },
-                    onCancel: () => {}
+                    onCancel: () => { }
                 })
             }
         })
@@ -509,6 +519,7 @@ const AddDynamic: React.FC<PULLIN.NewAddDynamic> = ({ visible, onChange, onClose
                 >
                     <Space wrap>
                         {(adgroups?.marketingAssetOuterSpec?.marketingTargetType === 'MARKETING_TARGET_TYPE_WECHAT_OFFICIAL_ACCOUNT' || adgroups?.marketingCarrierType === 'MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT' || addelivery?.dynamic?.creativeComponents?.brand?.[0]?.value?.jumpInfo?.pageType === 'PAGE_TYPE_WECHAT_OFFICIAL_ACCOUNT_DETAIL') && <Button type="primary" danger={!accountCreateLogs?.some(item => item?.wechatChannelList?.length)} onClick={() => { setWechatVisible(true) }}>{accountCreateLogs?.some(item => item?.wechatChannelList?.length) ? <>重新选择公众号 <CheckOutlined style={{ color: '#FFFFFF' }} /></> : '请选择公众号'}</Button>}
+                        {dynamic?.creativeComponents?.brand?.[0]?.value?.jumpInfo?.pageType === 'PAGE_TYPE_WECHAT_CHANNELS_PROFILE' && <Button type="primary" danger={!accountCreateLogs?.some(item => item?.videoChannelList?.length)} onClick={() => { setChannelsProfileVisible(true) }}>{accountCreateLogs?.some(item => item?.videoChannelList?.length) ? <>重新选择视频号 <CheckOutlined style={{ color: '#FFFFFF' }} /></> : '请选择视频号'}</Button>}
                     </Space>
                     <div className={style.settingsBody}>
                         <div className={style.settingsBody_content}>
@@ -590,6 +601,17 @@ const AddDynamic: React.FC<PULLIN.NewAddDynamic> = ({ visible, onChange, onClose
                             clearData()
                         }}
                     />}
+                    {/* 选择视频号 */}
+                    {channelsProfileVisible && <VideoChannel
+                        visible={channelsProfileVisible}
+                        data={accountCreateLogs}
+                        onClose={() => setChannelsProfileVisible(false)}
+                        onChange={(e) => {
+                            setAccountCreateLogs(e);
+                            setChannelsProfileVisible(false);
+                            clearData()
+                        }}
+                    />}
                 </Card>
             </Spin>
 

+ 40 - 15
src/pages/launchSystemV3/tencentAdPutIn/create/index.tsx

@@ -24,6 +24,7 @@ import WechatAccount from "../../components/WechatAccount"
 import Title from "antd/lib/typography/Title"
 import { getCreativeDetailsApi } from "@/services/adqV3/global"
 import ConversionSelect from "../../components/ConversionSelect"
+import VideoChannel from "../../components/VideoChannel"
 
 export const DispatchAddelivery = React.createContext<PULLIN.DispatchAddelivery | null>(null);
 
@@ -43,6 +44,7 @@ const Create: React.FC = () => {
     const [accountCreateLogs, setAccountCreateLogs] = useState<PULLIN.AccountCreateLogsProps[]>([])  // 账户
     const [goodsVisible, setGoodsVisible] = useState<boolean>(false) // 选择小说弹窗控制
     const [wechatVisible, setWechatVisible] = useState<boolean>(false) // 选择微信公众号弹窗控制
+    const [channelsProfileVisible, setChannelsProfileVisible] = useState<boolean>(false) // 选择微信公众号弹窗控制
     const [sourceVisible, setSourceVisible] = useState<boolean>(false) // 选择数据源弹窗控制
     const [conversionVisible, setConversionVisible] = useState<boolean>(false) // 选择转化归因控制
     const [materialData, setMaterialData] = useState<any>({}) // 素材数据
@@ -227,7 +229,7 @@ const Create: React.FC = () => {
 
                     let isConversion = false
                     setAccountCreateLogs(Object.keys(accountIdParamVOMap || {}).map(accountId => {
-                        const { productDTOS, wechatOfficialAccountsVO, pageList, landingPageVOS, userActionSetsList, conversionInfo } = accountIdParamVOMap[accountId]
+                        const { productDTOS, wechatOfficialAccountsVO, pageList, landingPageVOS, userActionSetsList, conversionInfo, wechatChannelVO } = accountIdParamVOMap[accountId]
                         let data: PULLIN.AccountCreateLogsProps = {
                             accountId: Number(accountId),
                             productList: productDTOS
@@ -245,6 +247,9 @@ const Create: React.FC = () => {
                             isConversion = true
                             data.newConversionList = [conversionInfo]
                         }
+                        if (wechatChannelVO) {
+                            data.videoChannelList = [wechatChannelVO]
+                        }
                         return data
                     }))
 
@@ -292,18 +297,6 @@ const Create: React.FC = () => {
             message.error('请先配置广告信息')
             return
         }
-        if (['MARKETING_TARGET_TYPE_FICTION', 'MARKETING_TARGET_TYPE_SHORT_DRAMA'].includes(marketingAssetOuterSpec?.marketingTargetType) && !accountCreateLogs?.some(item => item?.productList?.length)) {
-            message.error(marketingAssetOuterSpec?.marketingTargetType === 'MARKETING_TARGET_TYPE_FICTION' ? '请先选择小说' : '请先选择短剧')
-            return
-        }
-        if ((['MARKETING_TARGET_TYPE_WECHAT_OFFICIAL_ACCOUNT'].includes(marketingAssetOuterSpec?.marketingTargetType) || marketingCarrierType === 'MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT' || dynamic?.creativeComponents?.brand?.[0]?.value?.jumpInfo?.pageType === 'PAGE_TYPE_WECHAT_OFFICIAL_ACCOUNT_DETAIL') && !accountCreateLogs?.some(item => item?.wechatChannelList?.length)) {
-            message.error('请先选择公众号')
-            return
-        }
-        if (isConversion && !accountCreateLogs?.some(item => item?.newConversionList?.length)) {
-            message.error('请先选择转化归因')
-            return
-        }
         if (!(targeting?.length)) {
             message.error('请先添加定向')
             return
@@ -320,6 +313,23 @@ const Create: React.FC = () => {
             message.error('请先配置创意文案')
             return
         }
+        if (['MARKETING_TARGET_TYPE_FICTION', 'MARKETING_TARGET_TYPE_SHORT_DRAMA'].includes(marketingAssetOuterSpec?.marketingTargetType) && !accountCreateLogs?.some(item => item?.productList?.length)) {
+            message.error(marketingAssetOuterSpec?.marketingTargetType === 'MARKETING_TARGET_TYPE_FICTION' ? '请先选择小说' : '请先选择短剧')
+            return
+        }
+        if ((['MARKETING_TARGET_TYPE_WECHAT_OFFICIAL_ACCOUNT'].includes(marketingAssetOuterSpec?.marketingTargetType) || marketingCarrierType === 'MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT' || dynamic?.creativeComponents?.brand?.[0]?.value?.jumpInfo?.pageType === 'PAGE_TYPE_WECHAT_OFFICIAL_ACCOUNT_DETAIL') && !accountCreateLogs?.some(item => item?.wechatChannelList?.length)) {
+            message.error('请先选择公众号')
+            return
+        }
+        if (creativeComponents?.brand?.[0]?.value?.jumpInfo?.pageType === 'PAGE_TYPE_WECHAT_CHANNELS_PROFILE' && !accountCreateLogs?.some(item => item?.videoChannelList?.length)) {
+            message.error('请先选择视频号')
+            return
+        }
+        if (isConversion && !accountCreateLogs?.some(item => item?.newConversionList?.length)) {
+            message.error('请先选择转化归因')
+            return
+        }
+        
         if (!accountCreateLogs?.some(item => item?.pageList?.length)) {
             message.error('请先选择落地页')
             return
@@ -693,7 +703,7 @@ const Create: React.FC = () => {
         }
         let accountIdParamDTOMap: any = {}
         accountCreateLogs.forEach(item => {
-            let { pageList, productList, userActionSetsList, accountId, wechatChannelList, newConversionList } = item
+            let { pageList, productList, userActionSetsList, accountId, wechatChannelList, newConversionList, videoChannelList } = item
 
             let userActionSetsListDto = userActionSetsList?.map((item: any) => ({ id: item?.userActionSetId, type: item?.type })) // dataSourceId
 
@@ -710,7 +720,10 @@ const Create: React.FC = () => {
                 map.wechatChannelId = wechatChannelList?.[0]?.wechatOfficialAccountId
             }
             if (newConversionList) {
-                map.conversionId = newConversionList[0].conversionId
+                map.conversionId = newConversionList?.[0]?.conversionId
+            }
+            if (videoChannelList) {
+                map.videoChannelId = videoChannelList?.[0]?.wechatChannelsAccountId
             }
 
             accountIdParamDTOMap[accountId] = map
@@ -852,6 +865,7 @@ const Create: React.FC = () => {
                     {accountCreateLogs?.length > 0 && <>
                         {['MARKETING_TARGET_TYPE_FICTION', 'MARKETING_TARGET_TYPE_SHORT_DRAMA'].includes(marketingAssetOuterSpec?.marketingTargetType) && <Button type="primary" danger={!accountCreateLogs?.some(item => item?.productList?.length)} onClick={() => { setGoodsVisible(true) }}>{accountCreateLogs?.some(item => item?.productList?.length) ? <>重新选择{marketingAssetOuterSpec?.marketingTargetType === 'MARKETING_TARGET_TYPE_FICTION' ? '小说' : '短剧'} <CheckOutlined style={{ color: '#FFFFFF' }} /></> : `请选择${marketingAssetOuterSpec?.marketingTargetType === 'MARKETING_TARGET_TYPE_FICTION' ? '小说' : '短剧'}`}</Button>}
                         {(marketingAssetOuterSpec?.marketingTargetType === 'MARKETING_TARGET_TYPE_WECHAT_OFFICIAL_ACCOUNT' || marketingCarrierType === 'MARKETING_CARRIER_TYPE_WECHAT_OFFICIAL_ACCOUNT' || creativeComponents?.brand?.[0]?.value?.jumpInfo?.pageType === 'PAGE_TYPE_WECHAT_OFFICIAL_ACCOUNT_DETAIL') && <Button type="primary" danger={!accountCreateLogs?.some(item => item?.wechatChannelList?.length)} onClick={() => { setWechatVisible(true) }}>{accountCreateLogs?.some(item => item?.wechatChannelList?.length) ? <>重新选择公众号 <CheckOutlined style={{ color: '#FFFFFF' }} /></> : '请选择公众号'}</Button>}
+                        {creativeComponents?.brand?.[0]?.value?.jumpInfo?.pageType === 'PAGE_TYPE_WECHAT_CHANNELS_PROFILE' && <Button type="primary" danger={!accountCreateLogs?.some(item => item?.videoChannelList?.length)} onClick={() => { setChannelsProfileVisible(true) }}>{accountCreateLogs?.some(item => item?.videoChannelList?.length) ? <>重新选择视频号 <CheckOutlined style={{ color: '#FFFFFF' }} /></> : '请选择视频号'}</Button>}
                         {!isConversion ?
                             <Button onClick={() => { setSourceVisible(true) }}>精准匹配归因(选填){accountCreateLogs?.some(item => item?.userActionSetsList?.length) && <CheckOutlined style={{ color: '#1890ff' }} />}</Button>
                             :
@@ -941,6 +955,17 @@ const Create: React.FC = () => {
                         clearData()
                     }}
                 />}
+                {/* 选择视频号 */}
+                {channelsProfileVisible && <VideoChannel
+                    visible={channelsProfileVisible}
+                    data={accountCreateLogs}
+                    onClose={() => setChannelsProfileVisible(false)}
+                    onChange={(e) => {
+                        setAccountCreateLogs(e);
+                        setChannelsProfileVisible(false);
+                        clearData()
+                    }}
+                />}
                 {/* 转化归因 */}
                 {conversionVisible && <ConversionSelect
                     adgroups={addelivery.adgroups}

+ 2 - 1
src/pages/launchSystemV3/tencentAdPutIn/typings.d.ts

@@ -72,7 +72,8 @@ declare namespace PULLIN {
         excludedCustomAudienceList?: any,
         pageList?: any,
         coldStartAudienceList?: any[],
-        newConversionList?: any[]
+        newConversionList?: any[],
+        videoChannelList?: any[]
     }
     interface DynamicReactContent {
         form: FormInstance<any>

+ 3 - 1
src/pages/user/login/index.tsx

@@ -241,6 +241,8 @@ const Login: React.FC<{}> = () => {
         } else {
           timeOut(0)
         }
+      }).catch(() => {
+        timeOut(0)
       })
     } else {
       message.error('请输入正确的手机号!!!')
@@ -258,7 +260,7 @@ const Login: React.FC<{}> = () => {
         } else {
           timeOut(0)
         }
-      })
+      }).catch(() => timeOut(0))
     } else {
       message.error('请输入正确的手机号!!!')
     }

+ 24 - 0
src/services/adqV3/global.ts

@@ -101,6 +101,30 @@ export async function getWechatOfficialAccountsApi(data: { accountIdList: number
     });
 }
 
+/**
+ * 获取视频号
+ * @param data 
+ * @returns 
+ */
+export async function getVideoChannelInfoApi(data: { accountIdList?: number[], accountId?: number }) {
+    return request(api + `/adq/v3/marketingAssets/getVideoChannelInfo`, {
+        method: 'POST',
+        data,
+    });
+}
+
+/**
+ * 批量获取视频号
+ * @param data 
+ * @returns 
+ */
+export async function getVideoChannelInfoBatchApi(data: { accountIdList?: number[], accountId?: number }) {
+    return request(api + `/adq/v3/marketingAssets/getVideoChannelInfoBatch`, {
+        method: 'POST',
+        data,
+    });
+}
+
 /**
  * 获取转化归因
  * @param data 

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.