wjx 5 ヶ月 前
コミット
3020cb9192

+ 5 - 3
src/pages/launchSystemV3/adqv3/ad/index.tsx

@@ -23,7 +23,8 @@ const Ad: React.FC<ADQV3.AdProps> = ({ userId, creativeHandle }) => {
 
     /*****************************************/
     const [useType, setUseType] = useLocalStorageState<1 | 2>('AD_USETYPE', 1);
-    const [queryFrom, set_queryFrom] = useState<ADQV3.GetAdListProps>({ pageNum: 1, pageSize: 20, useType: useType || 1 })
+    const [pageSize, setPageSize] = useLocalStorageState<number>('AD_PAGESIZE', 20);
+    const [queryFrom, set_queryFrom] = useState<ADQV3.GetAdListProps>({ pageNum: 1, pageSize: pageSize || 20, useType: useType || 1 })
     const [isClearSelect, setIsClearSelect] = useState(true)
     const [selectedRows, setSelectedRows] = useState<any[]>([])
     const [tactics, setTactics] = useState<any>()
@@ -40,8 +41,8 @@ const Ad: React.FC<ADQV3.AdProps> = ({ userId, creativeHandle }) => {
     /*****************************************/
 
     useEffect(() => {
-        getList({ pageNum: 1, pageSize: 20, useType: useType || 1 })
-    }, [userId])
+        getList({ pageNum: 1, pageSize: pageSize || 20, useType: useType || 1 })
+    }, [userId, pageSize])
 
     // 获取列表
     const getList = useCallback((params: ADQV3.GetAdListProps) => {
@@ -453,6 +454,7 @@ const Ad: React.FC<ADQV3.AdProps> = ({ userId, creativeHandle }) => {
                 }
                 newQueryForm.pageNum = current
                 newQueryForm.pageSize = pageSize
+                setPageSize(pageSize)
                 set_queryFrom(newQueryForm)
                 getList(newQueryForm)
             }}

+ 4 - 3
src/pages/launchSystemV3/components/TargetingTooltip/index.tsx

@@ -1,6 +1,6 @@
 import React, { useEffect, useState } from "react"
 import './index.less'
-import { DEVICE_PRICE_ENUM, EDUCATION_ENUM, EXCLUDED_DIMENSION_ENUM, GAME_CONSUMPTION_LEVEL_ENUM, LOCATION_TYPES_ENUM, MARITAL_STATUS_ENUM, NETWORK_ENUM, OPTIMIZATIONGOAL_ENUM, USER_OS_ENUM, WECHAT_AD_NEHAVIOR_ENUM, WECHAT_AD_NEHAVIOR_GAME_ENUM } from "../../tencentAdPutIn/const"
+import { DEVICE_PRICE_ENUM, EDUCATION_ENUM, EXCLUDED_DAY_ENUM, EXCLUDED_DIMENSION_ENUM, GAME_CONSUMPTION_LEVEL_ENUM, LOCATION_TYPES_ENUM, MARITAL_STATUS_ENUM, NETWORK_ENUM, OPTIMIZATIONGOAL_ENUM, USER_OS_ENUM, WECHAT_AD_NEHAVIOR_ENUM, WECHAT_AD_NEHAVIOR_GAME_ENUM } from "../../tencentAdPutIn/const"
 
 let targetingData = [
     { key: 'geoLocation', name: '地域' },
@@ -47,7 +47,8 @@ interface ContentProps {
     gameConsumptionLevel?: string[] // 游戏消费能力
     excludedConvertedAudience?: {
         conversionBehaviorList: string[],
-        excludedDimension: string
+        excludedDimension: string,
+        excludedDay?: string
     }
 }
 /**
@@ -199,7 +200,7 @@ const TargetingTooltip: React.FC<Props> = (props) => {
             </div>}
         </>}
         {content?.excludedConvertedAudience && <div>
-            <strong>排除已转化用户:</strong><span>{EXCLUDED_DIMENSION_ENUM[content?.excludedConvertedAudience?.excludedDimension as keyof typeof EXCLUDED_DIMENSION_ENUM]}{`(自定义转化行为:${content?.excludedConvertedAudience?.conversionBehaviorList?.map(item => OPTIMIZATIONGOAL_ENUM[item as keyof typeof OPTIMIZATIONGOAL_ENUM])?.toString()})`}</span>
+            <strong>排除已转化用户:</strong><span>{EXCLUDED_DIMENSION_ENUM[content?.excludedConvertedAudience?.excludedDimension as keyof typeof EXCLUDED_DIMENSION_ENUM]}{`(自定义转化行为:${content?.excludedConvertedAudience?.conversionBehaviorList?.map(item => OPTIMIZATIONGOAL_ENUM[item as keyof typeof OPTIMIZATIONGOAL_ENUM])?.toString()})`} {content?.excludedConvertedAudience?.excludedDay ? `(转化时间区间:${EXCLUDED_DAY_ENUM[content?.excludedConvertedAudience?.excludedDay as keyof typeof EXCLUDED_DAY_ENUM]})` : ''}</span>
         </div>}
         {content?.unlimited && <div>
             <strong>不限:</strong><span>{!content?.geoLocation && '地域,'}{content?.unlimited}</span>

+ 23 - 3
src/pages/launchSystemV3/tencenTasset/copyWriting/index.tsx

@@ -1,11 +1,12 @@
 import React, { useEffect, useState } from "react"
 import '../../tencentAdPutIn/index.less'
-import { Button, Card, Input, message, Table } from "antd"
+import { Button, Card, Input, message, Select, Table } from "antd"
 import { PlusOutlined, SearchOutlined } from "@ant-design/icons"
 import { useAjax } from "@/Hook/useAjax"
 import { delCopyWritingApi, getCopyWritingListApi } from "@/services/adqV3/global"
 import ModifyCopyWriting from "./modifyCopyWriting"
 import columns from "./tableConfig"
+import { getErpUserAll } from "@/services/launchAdq/adq"
 
 /**
  * 文案库
@@ -14,16 +15,21 @@ import columns from "./tableConfig"
 const CopyWriting: React.FC = () => {
 
     /*************************************/
-    const [queryForm, setQueryForm] = useState<{ category?: string, content?: string, pageNum: number, pageSize: number }>({ pageNum: 1, pageSize: 20 })
-    const [queryFormNew, setQueryFormNew] = useState<{ category?: string, content?: string, pageNum: number, pageSize: number }>({ pageNum: 1, pageSize: 20 })
+    const [queryForm, setQueryForm] = useState<{ category?: string, content?: string, createBy?: number, pageNum: number, pageSize: number }>({ pageNum: 1, pageSize: 20 })
+    const [queryFormNew, setQueryFormNew] = useState<{ category?: string, content?: string, createBy?: number, pageNum: number, pageSize: number }>({ pageNum: 1, pageSize: 20 })
     const [initialValues, setInitialValues] = useState<any>()
     const [visible, setVisible] = useState<boolean>(false)
 
 
     const getCopyWritingList = useAjax((params) => getCopyWritingListApi(params))
     const delCopyWriting = useAjax((params) => delCopyWritingApi(params))
+    const allOfMember = useAjax(() => getErpUserAll())
     /*************************************/
 
+    useEffect(() => {
+        allOfMember.run()
+    }, [])
+
     useEffect(() => {
         getCopyWritingList.run(queryFormNew)
     }, [queryFormNew])
@@ -45,6 +51,20 @@ const CopyWriting: React.FC = () => {
     return <Card
         className="cardResetCss"
         title={<div className="flexStart" style={{ gap: 8 }}>
+            <Select
+                showSearch
+                placeholder="请选择创建人"
+                onChange={(e) => setQueryForm({ ...queryForm, createBy: e, pageNum: 1 })}
+                filterOption={(input, option) =>
+                    (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                }
+                allowClear
+                style={{ width: 140 }}
+                options={allOfMember?.data?.map((item: { userId: any; nickname: string }) => ({
+                    value: item.userId,
+                    label: item.nickname,
+                }))}
+            />
             <Input style={{ width: 200 }} placeholder="文案分类" value={queryForm?.category} allowClear onChange={(e) => setQueryForm({ ...queryForm, category: e.target.value, pageNum: 1 })} />
             <Input style={{ width: 200 }} placeholder="关键字" value={queryForm?.content} allowClear onChange={(e) => setQueryForm({ ...queryForm, content: e.target.value, pageNum: 1 })} />
 

+ 23 - 3
src/pages/launchSystemV3/tencenTasset/copyWriting/selectCopyWriting.tsx

@@ -1,11 +1,12 @@
 import { useAjax } from "@/Hook/useAjax"
 import { delCopyWritingApi, getCopyWritingListApi } from "@/services/adqV3/global"
 import { PlusOutlined, SearchOutlined } from "@ant-design/icons"
-import { Button, Input, message, Modal, Space, Table } from "antd"
+import { Button, Input, message, Modal, Select, Space, Table } from "antd"
 import React, { useEffect, useState } from "react"
 import ModifyCopyWriting from "./modifyCopyWriting"
 import columns from "./tableConfig"
 import '../../tencentAdPutIn/index.less'
+import { getErpUserAll } from "@/services/launchAdq/adq"
 
 interface Props {
     onChange?: (value: string[]) => void
@@ -16,16 +17,21 @@ const SelectCopyWriting: React.FC<Props> = ({ onChange, onClick }) => {
 
     /**********************************/
     const [visible, setVisible] = useState<boolean>(false)
-    const [queryForm, setQueryForm] = useState<{ category?: string, content?: string, pageNum: number, pageSize: number }>({ pageNum: 1, pageSize: 20 })
-    const [queryFormNew, setQueryFormNew] = useState<{ category?: string, content?: string, pageNum: number, pageSize: number }>({ pageNum: 1, pageSize: 20 })
+    const [queryForm, setQueryForm] = useState<{ category?: string, content?: string, createBy?: number, pageNum: number, pageSize: number }>({ pageNum: 1, pageSize: 20 })
+    const [queryFormNew, setQueryFormNew] = useState<{ category?: string, content?: string, createBy?: number, pageNum: number, pageSize: number }>({ pageNum: 1, pageSize: 20 })
     const [initialValues, setInitialValues] = useState<any>()
     const [mvisible, setMVisible] = useState<boolean>(false)
     const [selectedRows, setSelectedRows] = useState<any[]>([])
 
     const getCopyWritingList = useAjax((params) => getCopyWritingListApi(params))
     const delCopyWriting = useAjax((params) => delCopyWritingApi(params))
+    const allOfMember = useAjax(() => getErpUserAll())
     /**********************************/
 
+    useEffect(() => {
+        allOfMember.run()
+    }, [])
+
     useEffect(() => {
         if (visible)
             getCopyWritingList.run(queryFormNew)
@@ -73,6 +79,20 @@ const SelectCopyWriting: React.FC<Props> = ({ onChange, onClick }) => {
         >
             <Space style={{ width: '100%' }} direction="vertical">
                 <Space>
+                    <Select
+                        showSearch
+                        placeholder="请选择创建人"
+                        onChange={(e) => setQueryForm({ ...queryForm, createBy: e, pageNum: 1 })}
+                        filterOption={(input, option) =>
+                            (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                        }
+                        allowClear
+                        style={{ width: 140 }}
+                        options={allOfMember?.data?.map((item: { userId: any; nickname: string }) => ({
+                            value: item.userId,
+                            label: item.nickname,
+                        }))}
+                    />
                     <Input style={{ width: 200 }} placeholder="文案分类" value={queryForm?.category} allowClear onChange={(e) => setQueryForm({ ...queryForm, category: e.target.value, pageNum: 1 })} />
                     <Input style={{ width: 200 }} placeholder="关键字" value={queryForm?.content} allowClear onChange={(e) => setQueryForm({ ...queryForm, content: e.target.value, pageNum: 1 })} />
 

+ 8 - 1
src/pages/launchSystemV3/tencentAdPutIn/const.ts

@@ -440,7 +440,7 @@ export enum NETWORK_ENUM {
 
 /**排除已转化用户定向范围*/
 export enum EXCLUDED_DIMENSION_ENUM {
-	EXCLUDED_DIMENSION_CAMPAIGN = '同计划广告',
+	// EXCLUDED_DIMENSION_CAMPAIGN = '同计划广告',
 	EXCLUDED_DIMENSION_UID = '同账号广告',
 	EXCLUDED_DIMENSION_BUSINESS_MANAGER = '同商务管家广告',
 	EXCLUDED_DIMENSION_COMPANY_ACCOUNT = '同主体广告',
@@ -821,4 +821,11 @@ export enum SOURCE_TYPE_VIDEO_ENUM {
 	SOURCE_TYPE_DERIVATION = '视频派生工具',
 	SOURCE_TYPE_HUXUAN = '互选',
 	SOURCE_TYPE_HUXUAN_DERIVE = '互选二创'
+}
+
+/** 排除天数 */
+export enum EXCLUDED_DAY_ENUM {
+	EXCLUDED_DAY_ONE_DAY = '1天',
+	EXCLUDED_DAY_SEVEN_DAY = '7天',
+	EXCLUDED_DAY_ONE_MONTH = '1月',
 }

+ 30 - 28
src/pages/launchSystemV3/tencentAdPutIn/create/Material/addMaterial.tsx

@@ -205,33 +205,35 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                 }}>批量添加图片素材</Button>}
             </> : <>
                 <InputNumber size="small" min={1} style={{ width: 50 }} value={mCount} max={15} onChange={(e) => setMCount(e || 0)} />
-                <Button type="link" onClick={() => {
-                    setSelectCloudData({
-                        num: 100,
-                        defaultParams: {
-                            materialType: 'image',
-                            sizeQueries: [
-                                { relation: '=', width: 800, height: 800 },
-                                { relation: '=', width: 1280, height: 720 },
-                                { relation: '=', width: 720, height: 1280 },
-                                { relation: '=', width: 960, height: 334 },
-                                { relation: '=', width: 480, height: 320 },
-                            ],
-                            fileSize: 400 * 1024
-                        }
-                    })
-                    setMaterialConfig({
-                        ...materialConfig,
-                        type: 'image',
-                        max: 100,
-                        index: 20000,
-                        adcreativeTemplateId: creativeTemplateId,
-                        isGroup: false
-                    })
-                    setTimeout(() => {
-                        setSelectVideoVisible(true)
-                    }, 100)
-                }}>批量添加图片素材</Button>
+                <Button
+                    type="link"
+                    onClick={() => {
+                        setSelectCloudData({
+                            num: 100,
+                            defaultParams: {
+                                materialType: 'image',
+                                sizeQueries: [
+                                    { relation: '=', width: 800, height: 800 },
+                                    { relation: '=', width: 1280, height: 720 },
+                                    { relation: '=', width: 720, height: 1280 },
+                                    { relation: '=', width: 960, height: 334 },
+                                    { relation: '=', width: 480, height: 320 },
+                                ],
+                                fileSize: 400 * 1024
+                            }
+                        })
+                        setMaterialConfig({
+                            ...materialConfig,
+                            type: 'image',
+                            max: 100,
+                            index: 20000,
+                            adcreativeTemplateId: creativeTemplateId,
+                            isGroup: false
+                        })
+                        setTimeout(() => {
+                            setSelectVideoVisible(true)
+                        }, 100)
+                    }}>批量添加图片素材</Button>
                 <Button type="link" onClick={() => {
                     setSelectCloudData({
                         num: 100,
@@ -384,7 +386,7 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                                     <Dropdown
                                         menu={{
                                             items: [
-                                                { label: '1:1 九图', key: '1', disabled: dynamicGroup?.[num]?.['list']?.length >= 15, onClick: () => { selectGroupImg(num, 9) } },
+                                                { label: '1:1 九图', key: '4', disabled: dynamicGroup?.[num]?.['list']?.length >= 15, onClick: () => { selectGroupImg(num, 9) } },
                                                 { label: '1:1 六图', key: '1', disabled: dynamicGroup?.[num]?.['list']?.length >= 15, onClick: () => { selectGroupImg(num, 6) } },
                                                 { label: '1:1 三图', key: '2', disabled: dynamicGroup?.[num]?.['list']?.length >= 15, onClick: () => { selectGroupImg(num, 3) } },
                                                 { label: '1:1 四图', key: '3', disabled: dynamicGroup?.[num]?.['list']?.length >= 15, onClick: () => { selectGroupImg(num, 4) } },

+ 27 - 2
src/pages/launchSystemV3/tencentAdPutIn/create/Material/index.tsx

@@ -6,7 +6,7 @@ import { Button, Empty, message, Tooltip, Typography } from "antd";
 import { FolderOpenOutlined, RedoOutlined, SyncOutlined } from "@ant-design/icons";
 import AddMaterial from "./addMaterial";
 import VideoNews from "@/pages/launchSystemNew/components/newsModal/videoNews";
-import { shuffleArray } from "@/utils/utils";
+import { getRandomElements, shuffleArray } from "@/utils/utils";
 import SaveUseImg from "../Save/saveUseImg";
 const { Title } = Typography;
 
@@ -75,6 +75,28 @@ const Material: React.FC<{ adData?: any[] }> = ({ adData }) => {
         }
     }
 
+    // 组件化创意里面所有素材打乱
+    const handleDisruptionComponent = () => {
+        let count = dynamicMaterialDTos?.dynamicGroup?.length
+        if (count) {
+            const dynamicGroupList = shuffleArray(dynamicMaterialDTos.dynamicGroup.reduce((cur: any[], pre: { list: any[] }) => {
+                cur.push(...pre.list)
+                return cur
+            }, []))
+            const newDynamicGroup = JSON.parse(JSON.stringify(dynamicMaterialDTos.dynamicGroup)).map((item: { list: any[] }) => {
+                const length = item.list.length
+                return { list: dynamicGroupList.splice(0, length)}
+            })
+            setAddelivery({
+                ...addelivery,
+                dynamicMaterialDTos: {
+                    ...dynamicMaterialDTos,
+                    dynamicGroup: newDynamicGroup
+                }
+            })
+        }
+    }
+
     return <div className={`${style.settingsBody_content_row} ${style.row4}`}>
         <div className={style.title}>
             <span>
@@ -83,6 +105,9 @@ const Material: React.FC<{ adData?: any[] }> = ({ adData }) => {
                 {dynamicMaterialDTos?.dynamicGroup?.length > 1 && <Tooltip title="随机打乱素材">
                     <a style={{ marginLeft: 16, fontSize: 12 }} onClick={handleDisruption}><SyncOutlined /></a>
                 </Tooltip>}
+                {dynamicMaterialDTos?.dynamicGroup?.length > 1 && deliveryMode === 'DELIVERY_MODE_COMPONENT' && <Tooltip title="组件化创意所有素材随机打乱">
+                    <a style={{ marginLeft: 16, fontSize: 12, color: '#f50' }} onClick={handleDisruptionComponent}><SyncOutlined /></a>
+                </Tooltip>}
             </span>
             {(dynamicMaterialDTos && Object.keys(dynamicMaterialDTos).length > 0) ? <Button type="link" size="small" style={{ fontSize: 11, padding: 0 }} onClick={() => setAddelivery({ ...addelivery, dynamicMaterialDTos: {}, dynamicCreativesTextDTOS: {} })}><RedoOutlined />清空</Button> : null}
         </div>
@@ -97,7 +122,7 @@ const Material: React.FC<{ adData?: any[] }> = ({ adData }) => {
                                     return <div key={index} style={{ width: deliveryMode === 'DELIVERY_MODE_CUSTOMIZE' ? [641, 642, 643, 2277].includes(creativeTemplateId) ? '100%' : [720, 721, 722, 1529, 618].includes(creativeTemplateId) ? '50%' : '25%' : '100%' }}>
                                         <Title level={5} style={{ fontSize: 12 }}>创意组{index + 1}</Title>
                                         {mType ? ['short_video', 'video'].includes(mType) ? <div className={style.video}>
-                                            <VideoNews src={item?.video_id?.url || item?.short_video1?.url} keyFrameImageUrl={item?.video_id?.keyFrameImageUrl || item?.short_video1?.keyFrameImageUrl}/>
+                                            <VideoNews src={item?.video_id?.url || item?.short_video1?.url} keyFrameImageUrl={item?.video_id?.keyFrameImageUrl || item?.short_video1?.keyFrameImageUrl} />
                                             {item?.cover_id && <div className={style.cover_image}>
                                                 <img src={item?.cover_id?.url} />
                                             </div>}

+ 6 - 1
src/pages/launchSystemV3/tencentAdPutIn/create/Target/addTarget.tsx

@@ -1,7 +1,7 @@
 import { Button, Card, Checkbox, Form, Input, Modal, Radio, Select, Space, Spin, Tooltip, TreeSelect, Typography, message } from "antd"
 import React, { useEffect, useState } from "react"
 import style from '../index.less'
-import { DEVICE_PRICE_ENUM, EDUCATION_ENUM, EXCLUDED_DIMENSION_ENUM, GAME_CONSUMPTION_LEVEL_ENUM, GENDER_ENUM, LOCATION_TYPES_ENUM, MARITAL_STATUS_ENUM, NETWORK_ENUM, OPTIMIZATIONGOAL_ENUM, USER_OS_ENUM, WECHAT_AD_NEHAVIOR_ENUM, WECHAT_AD_NEHAVIOR_GAME_ENUM } from "../../const"
+import { DEVICE_PRICE_ENUM, EDUCATION_ENUM, EXCLUDED_DAY_ENUM, EXCLUDED_DIMENSION_ENUM, GAME_CONSUMPTION_LEVEL_ENUM, GENDER_ENUM, LOCATION_TYPES_ENUM, MARITAL_STATUS_ENUM, NETWORK_ENUM, OPTIMIZATIONGOAL_ENUM, USER_OS_ENUM, WECHAT_AD_NEHAVIOR_ENUM, WECHAT_AD_NEHAVIOR_GAME_ENUM } from "../../const"
 import { QuestionCircleFilled } from "@ant-design/icons"
 import { getTargetingGagsApi } from "@/services/adqV3/global"
 import { useAjax } from "@/Hook/useAjax"
@@ -687,6 +687,11 @@ const AddTarget: React.FC<Props> = ({ isBackVal, putInType, value, visible, onCh
                                 })}
                             </Select>
                         </Form.Item>
+                        <Form.Item label="转化时间区间" name={['excludedConvertedAudience', 'excludedDay']}>
+                            <Radio.Group>
+                                {Object.keys(EXCLUDED_DAY_ENUM).map(key => <Radio value={key} key={key}>{EXCLUDED_DAY_ENUM[key as keyof typeof EXCLUDED_DAY_ENUM]}</Radio>)}
+                            </Radio.Group>
+                        </Form.Item>
                     </div>}
 
                     <Form.Item name="deviceBrandModelType" label={<strong>设备品牌型号</strong>} style={{ marginBottom: deviceBrandModelType === '0' ? 0 : 4 }}>

+ 2 - 1
src/pages/launchSystemV3/tencentAdPutIn/create/index.tsx

@@ -475,10 +475,11 @@ const Create: React.FC = () => {
                     averageAdDynamicList = distributeArray(newDynamicGroup, adLength)
                 }
             }
+
             let data = cartesianProduct(productList, targeting).map((newD, dindex) => {
                 let [productDto, targetDto, index] = newD
                 let suffix = '_' + item.accountId + '_' + index
-                let averageAdDynamic = mediaType === 3 ? averageAdDynamicList?.[dindex] : averageAdDynamicList?.[accountIndex]
+                let averageAdDynamic = (mediaType === 3 ? averageAdDynamicList?.[dindex] : averageAdDynamicList?.[accountIndex]) || []
                 let dat: any = {
                     id: item.accountId + '_' + index,
                     accountId: item.accountId,                    // 账户

+ 1 - 1
src/services/adqV3/global.ts

@@ -830,7 +830,7 @@ export async function syncMediaImageApi({ type, ...data }: { accountIds: number[
  * @param data 
  * @returns 
  */
-export async function getCopyWritingListApi(data: { category?: string, content?: string, pageNum: number, pageSize: number }) {
+export async function getCopyWritingListApi(data: { category?: string, content?: string, createBy?: number, pageNum: number, pageSize: number }) {
     return request(api + `/adq/copyWriting/pageList`, {
         method: 'POST',
         data