Procházet zdrojové kódy

Merge branch 'develop' of http://git.zanxiangnet.com/wjx/ad-manage

wjx před 8 měsíci
rodič
revize
25a8ccdda3

+ 3 - 0
src/components/FileBoxAD/components/uploadsTable/index.tsx

@@ -187,6 +187,9 @@ const UploadsTable: React.FC<Props> = ({ fileList, visible, onClose, onChange, m
             bordered
             ref={tableRef}
             scroll={{ y: 450 }}
+            pagination={{
+                defaultPageSize: 100
+            }}
             columns={[
                 {
                     title: '序号',

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

@@ -7,6 +7,7 @@ import { FolderOpenOutlined, RedoOutlined, SyncOutlined } from "@ant-design/icon
 import AddMaterial from "./addMaterial";
 import VideoNews from "@/pages/launchSystemNew/components/newsModal/videoNews";
 import { shuffleArray } from "@/utils/utils";
+import SaveUseImg from "../Save/saveUseImg";
 const { Title } = Typography;
 
 const Material: React.FC<{ adData?: any[] }> = ({ adData }) => {
@@ -83,7 +84,7 @@ const Material: React.FC<{ adData?: any[] }> = ({ adData }) => {
                     <a style={{ marginLeft: 16, fontSize: 12 }} onClick={handleDisruption}><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}
+            {(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>
         <div className={style.detail}>
             <div className={style.detail_body}>
@@ -154,7 +155,8 @@ const Material: React.FC<{ adData?: any[] }> = ({ adData }) => {
             <div className={style.detail_footer}>
                 <Button
                     disabled={!(dynamic && Object.keys(dynamic)?.length > 0)}
-                    type="link" icon={<FolderOpenOutlined />}
+                    type="link"
+                    icon={<FolderOpenOutlined />}
                     style={{ padding: 0, fontSize: 12 }}
                     onClick={() => {
                         if (!(adData && adData?.length > 0)) {
@@ -172,6 +174,12 @@ const Material: React.FC<{ adData?: any[] }> = ({ adData }) => {
                 >
                     选择素材
                 </Button>
+                {dynamic && deliveryMode && <SaveUseImg
+                    type={deliveryMode === "DELIVERY_MODE_CUSTOMIZE" ? creativeTemplateId : deliveryMode}
+                    onChange={({ dynamicMaterialDTos, mediaType, dynamicCreativesTextDTOS }) => {
+                        setAddelivery({ ...addelivery, dynamicMaterialDTos, mediaType: mediaType as any, dynamicCreativesTextDTOS })
+                    }}
+                />}
             </div>
 
         </div>

+ 118 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Save/PreviewImg.tsx

@@ -0,0 +1,118 @@
+import { Empty, Popover, Space, Typography } from "antd"
+import React from "react"
+import style from '../index.less'
+import styles from '../Material/index.less'
+import VideoNews from "@/pages/launchSystemNew/components/newsModal/videoNews";
+const { Title, Text } = Typography;
+
+/**
+ * 预览创意素材
+ * @returns 
+ */
+const PreviewImg: React.FC<{ strategyValue: { dynamicMaterialDTos: any, mediaType: number, deliveryMode: string, creativeTemplateId: number, dynamicCreativesTextDTOS: any } }> = ({ strategyValue: { dynamicMaterialDTos, mediaType, deliveryMode, creativeTemplateId, dynamicCreativesTextDTOS } }) => {
+
+
+
+    return <Space>
+        <Popover
+            destroyTooltipOnHide={true}
+            mouseEnterDelay={0.5}
+            content={<div className={style.detail} style={{ width: 500, maxWidth: 500 }}>
+                <div className={style.detail_body}>
+                    {(dynamicMaterialDTos && Object.keys(dynamicMaterialDTos).length > 0) ?
+                        <>
+                            <Title level={5} style={{ fontSize: 12 }}>{mediaType === 0 ? '全账号复用' : mediaType === 1 ? '平均分配到广告' : mediaType === 2 ? '顺序分配到广告' : null}</Title>
+                            <div className={style.detail_body_m}>
+                                {dynamicMaterialDTos.dynamicGroup.map((item: any, index: number) => {
+                                    let dynamicGroup = item
+                                    if (deliveryMode === 'DELIVERY_MODE_CUSTOMIZE') {
+                                        let keys = Object.keys(dynamicGroup)
+                                        return <div key={index} style={{ width: deliveryMode === 'DELIVERY_MODE_CUSTOMIZE' ? [641, 642, 643].includes(creativeTemplateId) ? '100%' : [720, 721, 722, 1529, 618].includes(creativeTemplateId) ? '50%' : '25%' : '100%' }}>
+                                            <Title level={5} style={{ fontSize: 12 }}>创意组{index + 1}</Title>
+                                            {(keys.includes('video_id') || keys.includes('short_video1')) ? <div className={style.video}>
+                                                <VideoNews src={item?.video_id?.url || item?.short_video1?.url} />
+                                                {item?.cover_id && <div className={style.cover_image}>
+                                                    <img src={item?.cover_id?.url} />
+                                                </div>}
+                                            </div> : (keys.includes('image_list') || keys.includes('element_story')) ? <div className={style.imageList}>
+                                                {item?.image_list?.map((item: { url: string | undefined; }, index: undefined) => <div className={style.cover_image} key={index}>
+                                                    <img src={item?.url} />
+                                                </div>)}
+                                            </div> : keys.includes('image_id') ? <div className={style.cover_image}>
+                                                <img src={item?.image_id?.url} />
+                                            </div> : null}
+                                        </div>
+                                    } else {
+                                        return <div key={index} style={{ width: '100%' }}>
+                                            <Title level={5} style={{ fontSize: 12 }}>创意组{index + 1}</Title>
+                                            <div style={{ display: 'flex', gap: 5, flexWrap: 'wrap' }}>
+                                                {item?.list?.map((item: any, index: number) => {
+                                                    if (Array.isArray(item)) {
+                                                        let length = item.length
+                                                        return <div className={styles.boxList_body_item} key={index} style={{ width: 60, height: 60 }}>
+                                                            <div className={styles.tag}>{length}图</div>
+                                                            <div className={styles.content} style={{ width: 60, height: 60 }}>
+                                                                {item.map((l, i) => <img src={l?.url} key={i} style={{ width: length === 6 ? 19.9 : 29.9 }} />)}
+                                                            </div>
+                                                        </div>
+                                                    } else if (item?.url?.includes('mp4')) {
+                                                        return <div className={styles.boxList_body_item} key={index} style={{ width: 60, height: 60 }}>
+                                                            <div className={styles.content} style={{ width: 60, height: 60 }}>
+                                                                <VideoNews src={item?.url} style={{ width: 60, height: 60 }} maskBodyStyle={{ backgroundColor: "rgba(242, 246, 254, 0.1)" }} />
+                                                            </div>
+                                                        </div>
+                                                    } else {
+                                                        return <div className={styles.boxList_body_item} key={index} style={{ width: 60, height: 60 }}>
+                                                            <div className={styles.content} style={{ width: 60, height: 60 }}><img src={item?.url} /></div>
+                                                        </div>
+                                                    }
+                                                })}
+                                            </div>
+                                        </div>
+                                    }
+
+                                })}
+                            </div>
+                        </> :
+                        <div className={style.empty_block}>
+                            <Empty description="暂无素材" imageStyle={{ height: 50 }} />
+                        </div>}
+                </div>
+
+            </div>}
+            title="预览"
+        >
+            <a style={{ fontSize: 12 }}>素材预览</a>
+        </Popover>
+
+        <Popover
+            destroyTooltipOnHide={true}
+            mouseEnterDelay={0.5}
+            content={<div className={style.detail} style={{ minWidth: 200, maxWidth: 250 }}>
+                <div className={style.detail_body}>
+                    <Title level={5} style={{ fontSize: 12 }} ellipsis>{dynamicCreativesTextDTOS?.type === 0 ? '全部相同' : dynamicCreativesTextDTOS?.type === 1 ? '按创意组一一对应' : dynamicCreativesTextDTOS?.type === 2 ? '按文案顺序分配' : dynamicCreativesTextDTOS?.type === 3 ? '先分配创意组,文案后叉乘' : dynamicCreativesTextDTOS?.type === 4 ? '创意组和文案叉乘打乱后分配' : null}</Title>
+                    {dynamicCreativesTextDTOS?.dynamicCreativesTextDetailDTOList?.map((item: { [x: string]: (boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | null | undefined)[]; }, index: number) => {
+                        if (item) {
+                            let keys = Object.keys(item)
+                            return <div key={index}>
+                                {keys.includes('description') ? <>
+                                    <Title level={5} style={{ fontSize: 12 }}>文案</Title>
+                                    {item['description'].map((t, index) => <div className={style.text} key={index}><Text ellipsis={{ tooltip: true }}>{t}</Text></div>)}
+                                </> : keys.includes('title') ? <>
+                                    <Title level={5} style={{ fontSize: 12 }}>标题</Title>
+                                    {item['title'].map((t, index) => <div className={style.text} key={index}><Text ellipsis={{ tooltip: true }}>{t}</Text></div>)}
+                                </> : null}
+                            </div>
+                        }
+                        return null
+                    })}
+                </div>
+            </div>}
+            title="预览"
+        >
+            <a style={{ fontSize: 12 }}>文案预览</a>
+        </Popover>
+    </Space>
+}
+
+export default React.memo(PreviewImg)

+ 90 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Save/index.tsx

@@ -0,0 +1,90 @@
+import { useAjax } from "@/Hook/useAjax";
+import { addV3StrategyApi } from "@/services/adqV3";
+import { Button, Dropdown, Form, Input, Menu, message, Modal } from "antd";
+import React, { useState } from "react";
+
+
+
+interface Props {
+    addelivery: PULLIN.AddeliveryProps
+}
+
+/**
+ * 素材存为预设
+ * @param param0 
+ * @returns 
+ */
+const Save: React.FC<Props> = ({ addelivery: { dynamic: { deliveryMode, creativeTemplateId }, dynamicMaterialDTos, mediaType, dynamicCreativesTextDTOS } }) => {
+
+    /*********************************/
+    const [form] = Form.useForm();
+    const [visible, setVisible] = useState<boolean>(false)
+    const [type, setType] = useState<1 | 2>(1)
+
+    const addV3Strategy = useAjax((params) => addV3StrategyApi(params))
+    /*********************************/
+
+    const seve = () => {
+        setVisible(true)
+    }
+
+    const handleOk = () => {
+        form.validateFields().then(values => {
+            let params: any = { ...values }
+            if (type === 1) {
+                if (deliveryMode === "DELIVERY_MODE_CUSTOMIZE") {
+                    params.type = creativeTemplateId
+                } else {
+                    params.type = deliveryMode
+                }
+                params.strategyValue = JSON.stringify({ mediaType, dynamicMaterialDTos, deliveryMode, creativeTemplateId, dynamicCreativesTextDTOS })
+            }
+            addV3Strategy.run(params).then(res => {
+                if (res) {
+                    message.success('保存成功')
+                    form.resetFields()
+                    setVisible(false)
+                }
+            })
+        })
+    }
+
+    return <>
+        <Dropdown overlay={<Menu>
+            <Menu.Item disabled={!(dynamicMaterialDTos && Object.keys(dynamicMaterialDTos).length > 0)} onClick={() => {
+                setType(1)
+                seve()
+            }}>根据创意形式保存素材</Menu.Item>
+            {/* <Menu.Item disabled={!(dynamicCreativesTextDTOS && Object.keys(dynamicCreativesTextDTOS).length > 0)} onClick={() => {
+                setType(2)
+                seve()
+            }}>保存文案</Menu.Item> */}
+        </Menu>}>
+            <Button type='primary'>素材存为预设</Button>
+        </Dropdown>
+
+        {visible && <Modal
+            title={<strong>{type === 1 ? '根据创意形式保存素材' : '保存文案'}</strong>}
+            visible={visible}
+            onCancel={() => { form.resetFields(); setVisible(false) }}
+            onOk={handleOk}
+            confirmLoading={addV3Strategy.loading}
+            className="modalResetCss"
+        >
+            <Form
+                form={form}
+                layout='vertical'
+                name="saveUpdateAd"
+                colon={false}
+                initialValues={{}}
+            >
+                <Form.Item label={<strong>描述</strong>} name='strategyKey' rules={[{ required: true, message: '请输入描述' }]}>
+                    <Input placeholder="请输入描述" showCount maxLength={30} />
+                </Form.Item>
+            </Form>
+        </Modal>}
+    </>
+}
+
+
+export default React.memo(Save)

+ 103 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Save/saveUseImg.tsx

@@ -0,0 +1,103 @@
+import { useAjax } from "@/Hook/useAjax"
+import { Button, message, Modal, Space, Table } from "antd"
+import React, { useEffect, useState } from "react"
+import { Columns } from "./tableConfig"
+import { delV3StrategyApi, getV3StrategyListApi } from "@/services/adqV3"
+import { getAdqV3AdListApi } from "@/services/launchAdq/adqv3";
+import '../../index.less'
+import { DiffOutlined } from "@ant-design/icons"
+
+interface Props {
+    type: string,
+    onChange?: (value: { dynamicMaterialDTos: any, mediaType: number, deliveryMode: string, creativeTemplateId: number, dynamicCreativesTextDTOS: any }) => void
+}
+/**
+ * 使用素材
+ * @returns 
+ */
+const SaveUseImg: React.FC<Props> = ({ onChange, type }) => {
+
+    /***********************/
+    const [visible, setVisible] = useState<boolean>(false)
+    const [queryFormNew, setQueryFormNew] = useState<PULLIN.GetV3StrategyListProps>({ pageNum: 1, pageSize: 20, type })
+    const [selectedRowKeys, setSelectedRowKeys] = useState<any[]>([])
+
+    const getStrategy = useAjax((params) => getV3StrategyListApi(params))
+    const delStrategy = useAjax((params) => delV3StrategyApi(params))
+    const getAdqV3AdList = useAjax((params) => getAdqV3AdListApi(params), { formatResult: true })
+    /***********************/
+
+    useEffect(() => {
+        if (visible) {
+            getList()
+        }
+    }, [visible, queryFormNew])
+
+    const getList = () => {
+        getStrategy.run(queryFormNew)
+    }
+
+    const use = () => {
+        setVisible(true)
+    }
+
+    const handleOk = () => {
+        if (selectedRowKeys.length > 0) {
+            onChange?.(JSON.parse(selectedRowKeys[0].strategyValue))
+            setVisible(false)
+        } else {
+            message.error('请选择素材组')
+        }
+    }
+
+    const del = (id: number) => {
+        delStrategy.run(id).then(res => {
+            message.success('删除成功')
+            getStrategy.refresh()
+        })
+    }
+
+    return <>
+        <Button type='link' style={{ padding: 0, fontSize: 12 }} onClick={use} icon={<DiffOutlined />}>使用素材组</Button>
+        {visible && <Modal
+            title={<strong>使用策略组</strong>}
+            visible={visible}
+            onCancel={() => { setVisible(false) }}
+            onOk={handleOk}
+            width={700}
+            confirmLoading={getAdqV3AdList.loading}
+            className="modalResetCss"
+        >
+            <Space style={{ width: '100%' }} direction='vertical'>
+                <Table
+                    dataSource={getStrategy?.data?.records}
+                    loading={getStrategy.loading}
+                    columns={Columns(del)}
+                    size="small"
+                    bordered
+                    rowKey={'id'}
+                    scroll={{ x: 650 }}
+                    rowSelection={{
+                        type: 'radio',
+                        onChange: (selectedRowKeys: React.Key[], selectedRows: any[]) => {
+                            console.log(selectedRowKeys, selectedRows);
+                            setSelectedRowKeys(selectedRows)
+                        }
+                    }}
+                    pagination={{
+                        total: getStrategy?.data?.total,
+                        current: queryFormNew?.pageNum,
+                        pageSize: queryFormNew?.pageSize
+                    }}
+                    onChange={(pagination) => {
+                        const { current, pageSize } = pagination
+                        setQueryFormNew({ ...queryFormNew, pageNum: current as number, pageSize: pageSize as number || 20 })
+                    }}
+                />
+            </Space>
+        </Modal>}
+    </>
+}
+
+
+export default React.memo(SaveUseImg)

+ 51 - 0
src/pages/launchSystemV3/tencentAdPutIn/create/Save/tableConfig.tsx

@@ -0,0 +1,51 @@
+import { Popconfirm, Space, TableProps } from "antd";
+import React from "react";
+import PreviewImg from "./PreviewImg";
+
+export const Columns = (del: (id: number) => void): TableProps<any>['columns'] => {
+
+    const columns: TableProps<any>['columns'] = [
+        {
+            title: '创建人',
+            dataIndex: 'putUserName',
+            key: 'putUserName',
+            width: 60,
+            align: 'center'
+        },
+        {
+            title: '描述',
+            dataIndex: 'strategyKey',
+            key: 'strategyKey',
+            width: 140,
+            ellipsis: true,
+        },
+        {
+            title: '创建时间',
+            dataIndex: 'createTime',
+            key: 'createTime',
+            ellipsis: true,
+            width: 135
+        },
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            fixed: 'right',
+            width: 120,
+            align: 'center',
+            render: (_, b) => {
+                return <Space>
+                    <PreviewImg strategyValue={JSON.parse(b.strategyValue)} />
+                    <Popconfirm
+                        title="是否删除?"
+                        onConfirm={() => del(b?.id)}
+                    >
+                        <a style={{ color: 'red' }}>删除</a>
+                    </Popconfirm>
+                </Space>
+            }
+        }
+    ];
+
+    return columns
+}

+ 4 - 4
src/pages/launchSystemV3/tencentAdPutIn/create/TacticsS/tableConfig.tsx

@@ -71,11 +71,11 @@ export const Columns = (del: (id: number) => void): TableProps<any>['columns'] =
                 let strategyValue = b.strategyValue
                 let { adData } = JSON.parse(strategyValue)
                 return <span dangerouslySetInnerHTML={{
-                    __html: `营销目的:<span style="color: #52c41a">${MARKETING_GOAL_ENUM[adData?.[0]?.marketingGoal]}</span>,
-                    推广产品类型:<span style="color: #52c41a">${MARKETING_TARGET_TYPE_ENUM[adData?.[0]?.marketingTargetType]}</span>,
-                    营销载体类型:<span style="color: #52c41a">${MARKETING_CARRIER_TYPE_ENUM[adData?.[0]?.marketingCarrierType]}</span>,
+                    __html: `营销目的:<span style="color: #52c41a">${MARKETING_GOAL_ENUM[adData?.[0]?.marketingGoal as keyof typeof MARKETING_GOAL_ENUM]}</span>,
+                    推广产品类型:<span style="color: #52c41a">${MARKETING_TARGET_TYPE_ENUM[adData?.[0]?.marketingTargetType as keyof typeof MARKETING_TARGET_TYPE_ENUM]}</span>,
+                    营销载体类型:<span style="color: #52c41a">${MARKETING_CARRIER_TYPE_ENUM[adData?.[0]?.marketingCarrierType as keyof typeof MARKETING_CARRIER_TYPE_ENUM]}</span>,
                     版位选择:<span style="color: #52c41a">${adData?.[0]?.automaticSiteEnabled ? '自动版位' : '选择特定版位'}</span>,
-                    ${!adData?.[0]?.automaticSiteEnabled && `广告版位:<span style="color: #52c41a">${adData?.[0]?.siteSet.map((item: string | number) => SITE_SET_ENUM[item]).toString()}</span>`}
+                    ${!adData?.[0]?.automaticSiteEnabled && `广告版位:<span style="color: #52c41a">${adData?.[0]?.siteSet.map((item: string | number) => SITE_SET_ENUM[item as keyof typeof SITE_SET_ENUM]).toString()}</span>`}
                 `}} />
             }
         },

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

@@ -25,6 +25,7 @@ import Title from "antd/lib/typography/Title"
 import { getCreativeDetailsApi } from "@/services/adqV3/global"
 import ConversionSelect from "../../components/ConversionSelect"
 import VideoChannel from "../../components/VideoChannel"
+import Save from "./Save"
 
 export const DispatchAddelivery = React.createContext<PULLIN.DispatchAddelivery | null>(null);
 
@@ -913,6 +914,7 @@ const Create: React.FC = () => {
                     </div>
                 </div>
                 <Space className={style.bts} wrap>
+                    <Save addelivery={addelivery} />
                     <Button type='primary' onClick={severBd}>存为预设</Button>
                     <Popconfirm
                         title="确定清空?"