wjx 1 mesiac pred
rodič
commit
7d74991cea
26 zmenil súbory, kde vykonal 2223 pridanie a 546 odobranie
  1. 26 9
      src/components/SelectAdAccount/index.tsx
  2. 43 12
      src/pages/launchSystemV3/components/AdsComponent/Image1X1.tsx
  3. 54 0
      src/pages/launchSystemV3/components/AdsComponent/ImageXXX.tsx
  4. 54 0
      src/pages/launchSystemV3/components/AdsComponent/ImageXXXC.tsx
  5. 78 0
      src/pages/launchSystemV3/components/AdsComponent/index.less
  6. 77 0
      src/pages/launchSystemV3/material/cloudNew/index.less
  7. 391 0
      src/pages/launchSystemV3/material/cloudNew/mediaList.tsx
  8. 74 0
      src/pages/launchSystemV3/material/cloudNew/selectCloudComponent.tsx
  9. 49 379
      src/pages/launchSystemV3/material/cloudNew/selectCloudNew.tsx
  10. 379 0
      src/pages/launchSystemV3/material/cloudNew/selectComponentsUnit.tsx
  11. 125 0
      src/pages/launchSystemV3/material/cloudNew/selectComponentsUnitSearch.tsx
  12. 22 20
      src/pages/launchSystemV3/material/cloudNew/selectGroupCloudNew.tsx
  13. 21 10
      src/pages/launchSystemV3/material/cloudNew/uploadsTable.tsx
  14. 11 0
      src/pages/launchSystemV3/material/typings.d.ts
  15. 277 29
      src/pages/launchSystemV3/tencenTasset/manageComponent/addComponents.tsx
  16. 216 28
      src/pages/launchSystemV3/tencenTasset/manageComponent/const.ts
  17. 124 0
      src/pages/launchSystemV3/tencenTasset/manageComponent/index.less
  18. 68 12
      src/pages/launchSystemV3/tencenTasset/manageComponent/index.tsx
  19. 19 11
      src/pages/launchSystemV3/tencenTasset/manageComponent/tableConfig.tsx
  20. 46 16
      src/pages/launchSystemV3/tencentAdPutIn/create/Material/addMaterial.tsx
  21. 2 1
      src/pages/launchSystemV3/tencentAdPutIn/create/Material/index.less
  22. 9 1
      src/pages/launchSystemV3/tencentAdPutIn/create/Material/index.tsx
  23. 1 1
      src/pages/launchSystemV3/tencentAdPutIn/create/MaterialText/newText.tsx
  24. 8 6
      src/pages/launchSystemV3/tencentAdPutIn/create/index.tsx
  25. 17 4
      src/pages/launchSystemV3/tencentAdPutIn/create/tableConfig.tsx
  26. 32 7
      src/services/adqV3/global.ts

+ 26 - 9
src/components/SelectAdAccount/index.tsx

@@ -11,7 +11,7 @@ const { Text } = Typography;
 
 interface Props {
     value?: number[]
-    onChange?: (value?: number[] | number) => void
+    onChange?: (value?: number[] | number, data?: any[] | any) => void
     type?: 'checkbox' | 'radio'
     /**  是否第一次获取的时候返回值 */
     isReturnFirstValue?: boolean
@@ -64,7 +64,7 @@ const SelectAdAccount: React.FC<Props> = ({ value, onChange, isReturnFirstValue,
                 if (isReturnFirstValue && !isFirstReturn) {
                     setIsFirstReturn(() => true)
                     if (res?.records?.length) {
-                        onChange?.(res.records[0].accountId)
+                        onChange?.(res.records[0].accountId, res.records[0])
                     }
                 }
             })
@@ -159,7 +159,7 @@ const SelectAdAccount: React.FC<Props> = ({ value, onChange, isReturnFirstValue,
                         if (visible) {
                             setSelectedRows([])
                         } else {
-                            onChange?.(type === 'checkbox' ? [] : undefined)
+                            onChange?.(type === 'checkbox' ? [] : undefined, type === 'checkbox' ? [] : undefined)
                         }
                     }}><CloseCircleFilled /></a>}
                 </div>
@@ -205,9 +205,6 @@ const SelectAdAccount: React.FC<Props> = ({ value, onChange, isReturnFirstValue,
                             </Col>
                         </Row>
                     </Form>
-
-                    {/* 
-                 */}
                 </div>
                 <div className={style.selectAccount_list_table}>
                     <Table
@@ -248,7 +245,7 @@ const SelectAdAccount: React.FC<Props> = ({ value, onChange, isReturnFirstValue,
                                 width: 180,
                                 ellipsis: true,
                                 render(value) {
-                                    return <span style={{ fontSize: 12 }}>{value}</span>
+                                    return <span style={{ fontSize: 12 }}>{value || '--'}</span>
                                 }
                             },
                             {
@@ -258,7 +255,27 @@ const SelectAdAccount: React.FC<Props> = ({ value, onChange, isReturnFirstValue,
                                 width: 150,
                                 ellipsis: true,
                                 render(value) {
-                                    return <span style={{ fontSize: 12 }}>{value}</span>
+                                    return <span style={{ fontSize: 12 }}>{value || '--'}</span>
+                                }
+                            },
+                            {
+                                title: '业务单元账号',
+                                dataIndex: 'adUnitAccountId',
+                                key: 'adUnitAccountId',
+                                width: 80,
+                                align: 'center',
+                                render(value) {
+                                    return <span style={{ fontSize: 12 }}>{value || '--'}</span>
+                                }
+                            },
+                            {
+                                title: '是否业务单元账号',
+                                dataIndex: 'adUnitAccount',
+                                key: 'adUnitAccount',
+                                width: 80,
+                                align: 'center',
+                                render(value) {
+                                    return <span style={{ fontSize: 12 }}>{value ? '是' : '否'}</span>
                                 }
                             },
                             {
@@ -327,7 +344,7 @@ const SelectAdAccount: React.FC<Props> = ({ value, onChange, isReturnFirstValue,
                                 return
                             }
                             document.body.style.overflow = 'auto';
-                            onChange?.(type === 'checkbox' ? selectedRows.map(item => item.accountId) : selectedRows?.[0]?.accountId)
+                            onChange?.(type === 'checkbox' ? selectedRows.map(item => item.accountId) : selectedRows?.[0]?.accountId, type === 'checkbox' ? selectedRows : selectedRows?.[0])
                             setSelectedRows([])
                             setVisible(false)
                         }}

+ 43 - 12
src/pages/launchSystemV3/components/AdsComponent/Image1X1.tsx

@@ -1,24 +1,55 @@
-import React from 'react';
+import React, { useState } from 'react';
 import './index.less';
 import Lazyimg from "react-lazyimg-component"
+import { Modal, Popover } from 'antd';
+import play from "../../../../../public/image/play.png"
+import { CloseOutlined } from '@ant-design/icons';
 
 interface Props {
     imageUrl: string
+    videoUrl?: string
     style?: React.CSSProperties
 }
-const Image1X1: React.FC<Props> = ({ imageUrl, style }) => {
+const Image1X1: React.FC<Props> = ({ imageUrl, videoUrl, style }) => {
 
+    const [toPlay, setToPlay] = useState<boolean>(false)
 
-    return (
-        <div style={style} className='mediaPic'>
-            <Lazyimg
-                animateType="transition"
-                src={imageUrl}
-                className={`lazy`}
-                animateClassName={['transition-enter', 'transition-enter-active']}
-            />
-        </div>
-    );
+    return <>
+        <Popover
+            placement="right"
+            content={<div className='mediaPic mediaPicPopover'>
+                <Lazyimg
+                    animateType="transition"
+                    src={imageUrl}
+                    className={`lazy`}
+                    animateClassName={['transition-enter', 'transition-enter-active']}
+                />
+            </div>}
+            overlayClassName="mediaPicPopoverContent"
+            open={videoUrl ? false : undefined}
+        >
+            <div style={style} className='mediaPic'>
+                <Lazyimg
+                    animateType="transition"
+                    src={imageUrl}
+                    className={`lazy`}
+                    animateClassName={['transition-enter', 'transition-enter-active']}
+                />
+                {videoUrl && <div className='mediaPicVideo'>
+                    <img src={play} onClick={(e) => { e.stopPropagation(); e.preventDefault(); setToPlay(true) }} />
+                </div>}
+            </div>
+        </Popover>
+        {toPlay && <Modal
+            open={toPlay}
+            bodyStyle={{ backgroundColor: 'rgba(0,0,0,0.8)', overflow: 'hidden', borderRadius: 6 }}
+            footer={null}
+            closeIcon={<CloseOutlined style={{ color: '#FFF' }} />}
+            onCancel={(e) => { e.stopPropagation(); setToPlay(false) }}
+        >
+            <video className={'video'} style={{ borderRadius: 6 }} src={videoUrl} autoPlay controls>您的浏览器不支持 video 标签。</video>
+        </Modal>}
+    </>;
 };
 
 export default React.memo(Image1X1);

+ 54 - 0
src/pages/launchSystemV3/components/AdsComponent/ImageXXX.tsx

@@ -0,0 +1,54 @@
+import React from 'react';
+import './index.less';
+import Lazyimg from "react-lazyimg-component"
+import { Popover } from 'antd';
+import { TooltipPlacement } from 'antd/es/tooltip';
+
+interface Props {
+    imageList: { imageUrl: string, imageId: number }[]
+    style?: React.CSSProperties
+    placement?: TooltipPlacement
+}
+
+/**
+ * 多图
+ * @param param0 
+ * @returns 
+ */
+const ImageXXX: React.FC<Props> = ({ imageList, style, placement }) => {
+
+
+    return (
+        <Popover
+            placement={placement || "right"}
+            content={<div className='mediaPic mediaPicPopover'>
+                <div className='mediaPic-imgList' style={[3, 6].includes(imageList.length) ? { width: '100%' } : {}}>
+                    {imageList.map((item, index) => <Lazyimg
+                        style={[3, 6, 9].includes(imageList.length) ? { width: 'calc(33.333% - 1px)' } : [4].includes(imageList.length) ? { width: 'calc(50% - 2px)' } : {}}
+                        key={item.imageId + '_' + index}
+                        animateType="transition"
+                        src={item.imageUrl}
+                        className={`lazy`}
+                        animateClassName={['transition-enter', 'transition-enter-active']}
+                    />)}
+                </div>
+            </div>}
+            overlayClassName="mediaPicPopoverContent"
+        >
+            <div style={style} className='mediaPic'>
+                <div className='mediaPic-imgList' style={[1, 3, 6].includes(imageList.length) ? { width: '100%' } : {}}>
+                    {imageList.map((item, index) => <Lazyimg
+                        style={[1].includes(imageList.length) ? { height: '100%' } : [3, 9].includes(imageList.length) ? { width: 'calc(33.333% - 1px)' } : [6, 4].includes(imageList.length) ? { height: 'calc(50% - 2px)' } : {}}
+                        key={item.imageId + '_' + index}
+                        animateType="transition"
+                        src={item.imageUrl}
+                        className={`lazy`}
+                        animateClassName={['transition-enter', 'transition-enter-active']}
+                    />)}
+                </div>
+            </div>
+        </Popover>
+    );
+};
+
+export default React.memo(ImageXXX);

+ 54 - 0
src/pages/launchSystemV3/components/AdsComponent/ImageXXXC.tsx

@@ -0,0 +1,54 @@
+import React from 'react';
+import './index.less';
+import Lazyimg from "react-lazyimg-component"
+import { Popover } from 'antd';
+import { TooltipPlacement } from 'antd/es/tooltip';
+
+interface Props {
+    imageList: { imageUrl: string, imageId: number }[]
+    style?: React.CSSProperties
+    placement?: TooltipPlacement
+}
+
+/**
+ * 多图
+ * @param param0 
+ * @returns 
+ */
+const ImageXXXC: React.FC<Props> = ({ imageList, style, placement }) => {
+
+
+    return (
+        <Popover
+            placement={placement || "right"}
+            content={<div className='mediaPic mediaPicPopover'>
+                <div className='mediaPic-imgList' style={[3, 6].includes(imageList.length) ? { width: '100%' } : {}}>
+                    {imageList.map((item, index) => <Lazyimg
+                        style={[3, 6, 9].includes(imageList.length) ? { width: 'calc(33.333% - 1px)' } : [4].includes(imageList.length) ? { width: 'calc(50% - 2px)' } : {}}
+                        key={item.imageId + '_' + index}
+                        animateType="transition"
+                        src={item.imageUrl}
+                        className={`lazy`}
+                        animateClassName={['transition-enter', 'transition-enter-active']}
+                    />)}
+                </div>
+            </div>}
+            overlayClassName="mediaPicPopoverContent"
+        >
+            <div style={style} className='mediaPic mediaPic-C'>
+                <div className='mediaPic-imgList' style={[1, 3, 4, 6].includes(imageList.length) ? { width: '100%' } : {}}>
+                    {imageList.map((item, index) => <Lazyimg
+                        style={[1].includes(imageList.length) ? { height: '100%' } : [3, 6, 9].includes(imageList.length) ? { width: 'calc(33.333% - 1px)' } : [4].includes(imageList.length) ? { width: 'calc(50% - 2px)' } : {}}
+                        key={item.imageId + '_' + index}
+                        animateType="transition"
+                        src={item.imageUrl}
+                        className={`lazy`}
+                        animateClassName={['transition-enter', 'transition-enter-active']}
+                    />)}
+                </div>
+            </div>
+        </Popover>
+    );
+};
+
+export default React.memo(ImageXXXC);

+ 78 - 0
src/pages/launchSystemV3/components/AdsComponent/index.less

@@ -14,5 +14,83 @@
         max-height: 100%;
         vertical-align: middle;
         display: inline-block;
+        object-fit: contain;
+    }
+
+    .mediaPicVideo {
+        position: absolute;
+        width: 100%;
+        height: 100%;
+        top: 0;
+        left: 0;
+        background-color: rgba(0, 0, 0, .1);
+        display: flex;
+        justify-content: center;
+        align-items: center;
+
+        &>img {
+            width: 20px;
+            height: 20px;
+            display: inline-block;
+            cursor: pointer;
+        }
+    }
+}
+
+
+.mediaPic-imgList {
+    display: flex;
+    flex-wrap: wrap;
+    width: 36px;
+    height: 36px;
+    justify-content: center;
+    align-items: center;
+    gap: 1px;
+
+    >img {
+        max-width: 100%;
+        box-sizing: border-box;
+        max-height: 100%;
+        object-fit: contain;
+    }
+}
+
+.mediaPicPopover {
+    width: 280px;
+    height: auto;
+
+    >img {
+        width: 100%;
+    }
+
+    .mediaPic-imgList {
+        width: 100%;
+        height: auto;
+    }
+}
+
+.mediaPicPopoverContent {
+    .ant-popover-inner {
+        border-radius: 6px;
+    }
+
+    .ant-popover-inner-content {
+        padding: 4px 6px;
+    }
+}
+
+.video {
+    width: 100%;
+    max-width: 320px;
+    max-height: 600px;
+    display: block;
+    margin: auto;
+}
+
+.mediaPic-C {
+
+    .mediaPic-imgList {
+        width: 100%;
+        height: 100%;
     }
 }

+ 77 - 0
src/pages/launchSystemV3/material/cloudNew/index.less

@@ -92,6 +92,8 @@
             z-index: 100;
             background-color: rgba(255, 224, 224, 0.5);
             border: 1px dashed 9b9b9b;
+            top: 0;
+            left: 0;
         }
 
         .dragOver {
@@ -138,6 +140,7 @@
             position: absolute;
             top: 6px;
             left: 10px;
+            z-index: 1;
         }
 
         .coverImg {
@@ -152,6 +155,7 @@
             display: flex;
             justify-content: space-between;
             width: 100%;
+            z-index: 1;
 
             >div {
                 background-color: rgba(0, 0, 0, 0.75);
@@ -187,6 +191,7 @@
             border-radius: 6px;
             opacity: 0;
             transition: opacity 0.2s;
+            z-index: 1;
         }
 
         .move {
@@ -202,9 +207,11 @@
             .file_info>div {
                 opacity: 0.25;
             }
+
             .imgPreview {
                 opacity: 1;
             }
+
             .move {
                 opacity: 1;
             }
@@ -403,6 +410,7 @@
             transition: all .2;
             display: flex;
             gap: 4px;
+            z-index: 1;
         }
 
         &:hover .cz {
@@ -417,4 +425,73 @@
     height: 100%;
     flex-direction: column;
     gap: 6px;
+}
+
+.mediaPic_select {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    border-radius: 4px;
+    overflow: hidden;
+    justify-content: center;
+    align-items: center;
+
+    .mediaPicImgList {
+        display: flex;
+        width: 100%;
+    }
+
+    .mediaPicImgList1 {
+        >img {
+            height: 100%;
+        }
+    }
+
+    .mediaPicImgList3 {
+        flex-wrap: wrap;
+
+        >img {
+            width: calc(33.33% - 2px);
+            object-fit: contain;
+
+            &:not(:first-child) {
+                margin-left: 3px;
+            }
+        }
+    }
+
+    .mediaPicImgList4,
+    .mediaPicImgList6 {
+        flex-wrap: wrap;
+        height: 120px;
+        width: 120px;
+        column-gap: 3px;
+
+        >img {
+            height: calc(50% - 1.5px);
+            object-fit: contain;
+
+            &:nth-child(1),
+            &:nth-child(2) {
+                margin-bottom: 3px;
+            }
+        }
+    }
+
+    .mediaPicImgList6 {
+        width: 100%;
+        justify-content: center;
+    }
+
+    .mediaPicImgList9 {
+        flex-wrap: wrap;
+        height: 120px;
+        width: 120px;
+        column-gap: 1.5px;
+
+        >img {
+            height: calc(33.333% - 1.5px);
+            object-fit: contain;
+        }
+    }
 }

+ 391 - 0
src/pages/launchSystemV3/material/cloudNew/mediaList.tsx

@@ -0,0 +1,391 @@
+import { Button, Card, Checkbox, Divider, Empty, Form, message, Pagination, Popover, Radio, Select, Space, Spin, Tooltip, Typography } from "antd"
+import React, { useEffect, useRef, useState } from "react"
+import { useAjax } from "@/Hook/useAjax"
+import { getMaterialDataListApi } from "@/services/adqV3/cloudNew"
+import style from './index.less'
+import SelectSearch from "./selectSearch"
+import './global.less'
+import '../../tencentAdPutIn/index.less'
+import { useSize } from "ahooks"
+import useFileDrop from "@/Hook/useFileDrop"
+import { formatBytes, formatSecondsToTime, getVideoImgUrl } from "@/utils/utils"
+import UploadFile from "./uploadFile"
+import { useModel } from "umi"
+import UploadsTable from "./uploadsTable"
+import { SortAscendingOutlined } from "@ant-design/icons"
+import PlayVideo from "./playVideo"
+import { showFieldList } from "./const"
+import SelectFolder from "./selectFolder"
+import { addOnlyDataApi, getOnlyDataApi } from "@/services/adqV3"
+
+const { Text, Paragraph } = Typography;
+
+/**
+ * 素材库
+ * @param param0 
+ * @returns 
+ */
+const MediaList: React.FC<CLOUDNEW.MediaListProps> = ({ defaultParams, num, active, checkedFolderList, setCheckedFolderList, uploadVisible, setUploadVisible }) => {
+
+    /*************************************/
+    const { initialState } = useModel('@@initialState');
+    const ref = useRef<HTMLDivElement>(null);
+    const size = useSize(ref);
+    const [checkFolderAll, setCheckFolderAll] = useState<boolean>(false);
+    const [indeterminateFolder, setIndeterminateFolder] = useState<boolean>(false);
+    const [rowNum, setRowNum] = useState<number>(0)
+
+    const [queryParams, setQueryParams] = useState<CLOUDNEW.GetMaterialDataListProps>({ pageNum: 1, pageSize: 20 })
+    const [searchParams, setSearchParams] = useState<Partial<CLOUDNEW.GetMaterialDataListProps>>({})
+    const [isGetField, setIsGetField] = useState<boolean>(false)
+    const [showField, setShowField] = useState<string[]>(['material.create_time', 'material_data_day.cost', 'material_data_day.ctr', 'material_data_day.conversions_rate', 'material_data_day.dynamic_creative_count'])
+    const [sortData, setSortData] = useState<{ sortField: string | undefined, sortType: boolean }>({ sortField: undefined, sortType: false })
+    const [isShowAll, setIsShowAll] = useState<boolean>(false)
+
+    const getMaterialDataList = useAjax((params) => getMaterialDataListApi(params))
+    const getOnlyData = useAjax((params) => getOnlyDataApi(params))
+    const addOnlyData = useAjax((params) => addOnlyDataApi(params))
+    /************************************/
+
+    useEffect(() => {
+        getOnlyData.run({ type: 'MODE_FIELD_ENT' }).then(res => {
+            if (res?.data) {
+                const { showField, sortData } = JSON.parse(res.data)
+                setShowField(showField)
+                setSortData(sortData)
+                setTimeout(() => setIsGetField(true), 0)
+            } else {
+                setIsGetField(true)
+            }
+        }).catch(() => setIsGetField(true))
+    }, [])
+
+    useEffect(() => {
+        if (isGetField) {
+            let params = { ...searchParams, ...defaultParams, ...queryParams }
+            if (sortData?.sortField) {
+                params = { ...params, ...sortData }
+            }
+            if (params?.sizeList?.length) {
+                params.sizeQueries = params.sizeList.map(item => {
+                    const [width, height, relation] = item.split('*')
+                    return { width: Number(width), height: Number(height), relation: relation || '=' }
+                })
+            }
+            delete params?.sizeList
+            if (isShowAll) {
+                delete params?.sizeQueries
+            }
+            getMaterialDataList.run(params)
+        }
+    }, [queryParams, defaultParams, searchParams, sortData, showField, isShowAll, isGetField])
+
+    // 根据内容宽度计算列数
+    useEffect(() => {
+        if (size?.width) {
+            let rowNum = Math.floor((size?.width - 26) / 200)
+            setRowNum(rowNum || 1)
+        }
+    }, [size?.width])
+
+    // 处理全选按钮状态
+    useEffect(() => {
+        let data: any[] = getMaterialDataList?.data?.records || []
+        let dataIds = data.map(item => item.id)
+        let selectIds = checkedFolderList.map(item => item.id)
+        if (selectIds.length === 0 || dataIds.length === 0) {
+            setCheckFolderAll(false)
+            setIndeterminateFolder(false);
+        } else if (dataIds.every((element) => selectIds.includes(element))) {
+            setCheckFolderAll(true)
+            setIndeterminateFolder(false);
+        } else if (dataIds.some((element) => selectIds.includes(element))) {
+            setCheckFolderAll(false)
+            setIndeterminateFolder(true);
+        } else {
+            setCheckFolderAll(false)
+            setIndeterminateFolder(false);
+        }
+    }, [checkedFolderList, getMaterialDataList?.data?.records])
+
+    // 选择
+    const onCheckboxChange = (checked: boolean, item: any) => {
+        let newCheckedFolderList: any[] = JSON.parse(JSON.stringify(checkedFolderList))
+        if (active || active === 0) {
+            if (checked) { // 选中
+                newCheckedFolderList[active] = { ...item, materialType: 0 }
+            } else { // 取消
+                message.warning('请选择其他图片替换')
+                return
+            }
+        } else {
+            if (checked) { // 选中
+                newCheckedFolderList.push({ ...item, materialType: 0 })
+            } else { // 取消
+                newCheckedFolderList = newCheckedFolderList.filter(i => i.id !== item.id)
+            }
+        }
+        setCheckedFolderList(newCheckedFolderList)
+    };
+
+    // 获取拖拽的文件
+    const [fileList, setFileList] = useState<FileList>()
+    const [uploadsVisible, setUploadsVisible] = useState<boolean>(false)
+    const { isDragOver, dropAreaProps } = useFileDrop((fileList) => {
+        setFileList(fileList)
+        setUploadsVisible(true)
+    });
+
+
+    return <>
+        <div className={style.select_cloudNew_layout}>
+            {/* 搜索 */}
+            <SelectSearch
+                sizeQueries={defaultParams?.sizeQueries}
+                onSearch={(value) => {
+                    ref.current?.scrollTo(0, 0)
+                    setSearchParams(value)
+                }}
+            />
+
+            <Card
+                style={{ height: '100%', flex: '1 0', overflow: 'hidden' }}
+                bodyStyle={{ padding: 0, overflow: 'auto hidden', height: '100%' }}
+                className="cardResetCss buttonResetCss"
+                bordered
+            >
+                <div className={style.cloudNew}>
+                    {/* 选择文件夹 */}
+                    <SelectFolder selectedKeys={queryParams?.folderId} onChange={(value) => { setQueryParams({ ...queryParams, folderId: value, pageNum: 1 }) }} />
+                    <div className={style.material} style={{ height: '100%' }}>
+                        <div className={style.operates}>
+                            <div className={style.left_bts}>
+                                <Checkbox
+                                    onChange={(e) => {
+                                        let newCheckedFolderList: any[] = JSON.parse(JSON.stringify(checkedFolderList))
+                                        const data: any[] = getMaterialDataList?.data?.records
+
+                                        if (e.target.checked) { // 全选
+                                            const selectIds = newCheckedFolderList.map(item => item.id)
+                                            const remainData = data.filter(item => !selectIds.includes(item.id))
+                                            const remainDataLength = remainData.length
+                                            const remainNum = num - newCheckedFolderList.length
+                                            if (remainNum > remainDataLength) {
+                                                newCheckedFolderList.push(...remainData.map(item => ({ ...item, materialType: 0 })))
+                                            } else {
+                                                newCheckedFolderList.push(...remainData.splice(0, remainNum).map(item => ({ ...item, materialType: 0 })))
+                                            }
+                                            newCheckedFolderList = Array.from(new Set(newCheckedFolderList.map(item => JSON.stringify(item)))).map(item => JSON.parse(item));
+                                        } else { // 取消全选
+                                            const dataIds = data.map(item => item.id)
+                                            newCheckedFolderList = newCheckedFolderList.filter(i => !dataIds.includes(i.id))
+                                        }
+                                        setCheckedFolderList(newCheckedFolderList)
+                                    }}
+                                    disabled={checkedFolderList?.length >= num || !!active || active === 0 || isShowAll}
+                                    indeterminate={indeterminateFolder}
+                                    checked={checkFolderAll}
+                                >全选</Checkbox>
+                                <span>已选 <span style={{ color: '#1890FF' }}>{checkedFolderList?.length || 0}</span>/{num} 个素材</span>
+                                {checkedFolderList.length > 0 && <a style={{ color: 'red' }} onClick={() => setCheckedFolderList([])}>清除所有</a>}
+                                {sortData?.sortField && <Text>「排序-{showFieldList.find(item => item.value === sortData.sortField)?.label}-{sortData.sortType ? '正序' : '倒序'}」</Text>}
+                            </div>
+                            <div className={style.left_bts}>
+                                <Checkbox onChange={(e) => setIsShowAll(e.target.checked)} checked={isShowAll}>
+                                    <Tooltip title="勾选只展示,不支持选择投放">
+                                        <span style={{ fontSize: 12 }}>显示所有{defaultParams?.materialType === 'video' ? '视频' : '图片'}素材</span>
+                                    </Tooltip>
+                                </Checkbox>
+                                <Popover
+                                    content={<div style={{ width: 320 }}>
+                                        <Form
+                                            labelCol={{ span: 5 }}
+                                            labelAlign="left"
+                                            colon={false}
+                                        >
+                                            <Form.Item label={<strong>展示指标</strong>}>
+                                                <Select
+                                                    placeholder="选择展示指标"
+                                                    showSearch
+                                                    filterOption={(input, option) =>
+                                                        (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                                                    }
+                                                    mode="multiple"
+                                                    options={showFieldList}
+                                                    value={showField}
+                                                    onChange={(value) => {
+                                                        if (value.length > 6) {
+                                                            message.warn('最多只能选择6个指标')
+                                                            return
+                                                        }
+                                                        if (value.length < 1) {
+                                                            message.warn('最少选择1个指标')
+                                                            return
+                                                        }
+                                                        setSortData({ ...sortData, sortField: undefined })
+                                                        setShowField(() => value as any)
+                                                        addOnlyData.run({ data: JSON.stringify({ showField: value, sortData: { ...sortData, sortField: undefined } }), type: 'MODE_FIELD_ENT' })
+                                                    }}
+                                                />
+                                            </Form.Item>
+                                            <Form.Item label={<strong>排序</strong>}>
+                                                <Space>
+                                                    <Select
+                                                        style={{ width: 125 }}
+                                                        placeholder="选择排序指标"
+                                                        showSearch
+                                                        filterOption={(input, option) =>
+                                                            (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                                                        }
+                                                        options={showFieldList.filter(item => showField.includes(item.value) && item.value !== 'description')}
+                                                        value={sortData.sortField}
+                                                        allowClear
+                                                        onChange={(value) => {
+                                                            setSortData({ ...sortData, sortField: value as any })
+                                                            addOnlyData.run({ data: JSON.stringify({ showField, sortData: { ...sortData, sortField: value as any } }), type: 'MODE_FIELD_ENT' })
+                                                        }}
+                                                    />
+                                                    <Radio.Group
+                                                        value={sortData.sortType}
+                                                        onChange={(e) => {
+                                                            setSortData({ ...sortData, sortType: e.target.value })
+                                                            addOnlyData.run({ data: JSON.stringify({ showField, sortData: { ...sortData, sortType: e.target.value } }), type: 'MODE_FIELD_ENT' })
+                                                        }}
+                                                        buttonStyle="solid"
+                                                    >
+                                                        <Radio.Button value={true}>正序</Radio.Button>
+                                                        <Radio.Button value={false}>倒序</Radio.Button>
+                                                    </Radio.Group>
+                                                </Space>
+                                            </Form.Item>
+                                        </Form>
+                                    </div>}
+                                    trigger={['click']}
+                                    title={<strong>自定义展示指标与排序</strong>}
+                                    placement="bottomRight"
+                                >
+                                    <Button icon={<SortAscendingOutlined />}>自定义展示指标与排序</Button>
+                                </Popover>
+                            </div>
+                        </div>
+                        <div className={`${style.content} content_global`}>
+                            <Spin spinning={getMaterialDataList.loading || getOnlyData.loading}>
+                                <div className={style.content_scroll} ref={ref} {...dropAreaProps}>
+                                    {isDragOver && <div className={`${style.placeholder} ${isDragOver ? style.dragOver : ''}`}><span>拖动文件到这里</span></div>}
+                                    {getMaterialDataList?.data?.records?.length > 0 ? <Checkbox.Group value={checkedFolderList?.filter((_, index) => (active || active === 0) ? index === active : true)?.map(item => item.id)} style={{ width: '100%' }}>
+                                        <div className={style.content_scroll_div}>
+                                            {getMaterialDataList?.data?.records.map((item: any) => {
+                                                const isSelect = checkedFolderList?.filter((_, index) => (active || active === 0) ? index === active : true)?.map(item => item.id).includes(item.id)
+                                                const disabled = ((active || active === 0) ? false : (checkedFolderList.length >= num && !isSelect)) || isShowAll
+                                                return <div key={item.id} className={style.content_row} style={{ width: rowNum ? (1 / rowNum * 100) + '%' : 200 }}>
+                                                    <Card
+                                                        hoverable
+                                                        bodyStyle={{ padding: 0 }}
+                                                        style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}
+                                                        className={`${style.content_col}`}
+                                                        cover={<div style={{ height: 120, padding: 0 }} className={style.content_cover}>
+                                                            <div className={style.checkbox}><Checkbox disabled={disabled} value={item.id} onChange={(e) => onCheckboxChange(e.target.checked, item)} /></div>
+                                                            {item.material_type === 'video' && <div className={style.playr}>
+                                                                <PlayVideo videoUrl={item.oss_url}>{(onPlay) => <img onClick={(e) => {
+                                                                    e.stopPropagation(); e.preventDefault()
+                                                                    onPlay()
+                                                                }} src={require('../../../../../public/image/play.png')} alt="" />}</PlayVideo>
+                                                            </div>}
+                                                            <div className={style.file_info}>
+                                                                <div>{item.width}*{item.height}</div>
+                                                                {item.material_type === 'video' && <div>{formatSecondsToTime(Math.floor(item.video_duration))}</div>}
+                                                            </div>
+                                                            <img src={item.material_type === 'image' ? item.oss_url : getVideoImgUrl(item.oss_url)} className={style.coverImg} alt="" />
+                                                        </div>}
+                                                        onClick={() => !disabled && onCheckboxChange(!isSelect, item)}
+                                                    >
+                                                        <div className={style.body}>
+                                                            <Text ellipsis strong style={{ flex: '1 0' }}>{item?.material_name}</Text>
+                                                            <Text style={{ fontSize: 12 }}>{formatBytes(item?.file_size)}</Text>
+                                                        </div>
+                                                        <Divider style={{ margin: '0 0 4px 0' }} />
+                                                        <div style={{ padding: '0 10px 6px' }}>
+                                                            {showField.map(field => {
+                                                                switch (field) {
+                                                                    case 'material.create_time':
+                                                                        return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>创建时间:{item?.create_time}</Paragraph>
+                                                                    case 'material_data_day.cost':
+                                                                        return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>消耗:{(item?.cost === null || item?.cost === undefined) ? '--' : item?.cost}</Paragraph>
+                                                                    case 'material_data_day.order_pv':
+                                                                        return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>下单次数:{(item?.order_pv === null || item?.order_pv === undefined) ? '--' : item?.order_pv}</Paragraph>
+                                                                    case 'material_data_day.order_cost':
+                                                                        return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>下单成本:{(item?.order_cost === null || item?.order_cost === undefined) ? '--' : item?.order_cost}</Paragraph>
+                                                                    case 'material_data_day.ctr':
+                                                                        return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>点击率:{(item?.ctr === null || item?.ctr === undefined) ? '--' : (item?.ctr * 100).toFixed(2) + '%'}</Paragraph>
+                                                                    case 'material_data_day.conversions_rate':
+                                                                        return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>目标转化率:{(item?.conversions_rate === null || item?.conversions_rate === undefined) ? '--' : (item?.conversions_rate * 100).toFixed(2) + '%'}</Paragraph>
+                                                                    case 'material_data_day.adgroup_count':
+                                                                        return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>广告关联数:{(item?.adgroup_count === null || item?.adgroup_count === undefined) ? '--' : item?.adgroup_count}</Paragraph>
+                                                                    case 'material_data_day.dynamic_creative_count':
+                                                                        return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>创意关联数:{(item?.dynamic_creative_count === null || item?.dynamic_creative_count === undefined) ? '--' : item?.dynamic_creative_count}</Paragraph>
+                                                                    default:
+                                                                        return null
+                                                                }
+                                                            })}
+                                                            {showField.includes('description') && <Paragraph style={{ fontSize: 12, marginBottom: 1 }} ellipsis={{ tooltip: true }}>备注:{item.description || '--'}</Paragraph>}
+                                                        </div>
+                                                    </Card>
+                                                </div>
+                                            })}
+                                        </div>
+                                    </Checkbox.Group> : <div style={{ height: '100%', width: '100%', alignContent: 'center' }}><Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /></div>}
+                                </div>
+                            </Spin>
+                        </div>
+                        <div className={style.fotter}>
+                            <Pagination
+                                size="small"
+                                total={getMaterialDataList?.data?.total || 0}
+                                showSizeChanger
+                                showQuickJumper
+                                pageSize={getMaterialDataList?.data?.size || 20}
+                                current={getMaterialDataList?.data?.current || 1}
+                                onChange={(page: number, pageSize: number) => {
+                                    ref.current?.scrollTo(0, 0)
+                                    setQueryParams({ ...queryParams, pageNum: page, pageSize })
+                                }}
+                                pageSizeOptions={[10, 15, 20, 50, 100]}
+                            />
+                        </div>
+                    </div>
+                </div>
+            </Card>
+        </div>
+        {/* 上传素材 */}
+        {uploadVisible && <UploadFile
+            userId={(initialState?.currentUser?.userId || 0) as number}
+            visible={uploadVisible}
+            onChange={() => {
+                setUploadVisible(false)
+                getMaterialDataList.refresh()
+            }}
+            onClose={() => {
+                setUploadVisible(false)
+            }}
+        />}
+
+        {/* 拖动上传 */}
+        {uploadsVisible && <UploadsTable
+            userId={(initialState?.currentUser?.userId || 0) as number}
+            visible={uploadsVisible}
+            fileList={fileList}
+            isPermission={true}
+            onClose={() => {
+                setUploadsVisible(false)
+                setFileList(undefined)
+            }}
+            onChange={() => {
+                setUploadsVisible(false)
+                setFileList(undefined)
+                getMaterialDataList.refresh()
+            }}
+        />}
+    </>;
+};
+
+export default React.memo(MediaList);

+ 74 - 0
src/pages/launchSystemV3/material/cloudNew/selectCloudComponent.tsx

@@ -0,0 +1,74 @@
+import { Button, message, Modal, Space } from "antd"
+import React, { useState } from "react"
+import style from './index.less'
+import './global.less'
+import '../../tencentAdPutIn/index.less'
+import { getVideoImgUrl } from "@/utils/utils"
+import { DeleteOutlined, PlayCircleOutlined } from "@ant-design/icons"
+import PlayVideo from "./playVideo"
+import SelectGroupCloudNew from "./selectGroupCloudNew"
+
+/**
+ * 选择素材
+ * @param param0 
+ * @returns 
+ */
+const SelectCloudComponent: React.FC<CLOUDNEW.SelectCloudNewProps> = ({ visible, defaultParams, num, isGroup, onChange, onClose, accountCreateLogs, putInType }) => {
+
+    /************************************/
+    const [checkedFolderList, setCheckedFolderList] = useState<any[]>([])
+    const [active, setActive] = useState<number>()
+    /************************************/
+
+
+    const handleOk = () => {
+        if (isGroup && num && checkedFolderList && num > checkedFolderList?.length) {
+            message.error(`当前选择了组图${num}图,当前选择了${checkedFolderList.length}个图片`)
+            return
+        }
+        onChange?.(checkedFolderList)
+    }
+
+    return <Modal
+        title={<strong>素材库</strong>}
+        open={visible}
+        onCancel={onClose}
+        width={1400}
+        maskClosable={false}
+        footer={<div className={style.divFooter}>
+            {checkedFolderList.length > 0 ? <>
+                <div className={style.selectedCloud}>
+                    <div style={{ width: checkedFolderList.length * 58 + (checkedFolderList.length - 1) * 8 }}>
+                        {checkedFolderList.map((item, index) => <div key={item.id + '_' + index} className={`${style.selectedCloud_col} ${active === index ? style.active : ''}`} onClick={() => isGroup && setActive(index)}>
+                            <div className={style.cz}>
+                                {item.material_type === 'video' && <PlayVideo videoUrl={item.oss_url}>{(onPlay) => <a onClick={onPlay}><PlayCircleOutlined /></a>}</PlayVideo>}
+                                <a style={{ color: 'red' }} onClick={() => setCheckedFolderList(data => data.filter(i => i.id !== item.id))}><DeleteOutlined /></a>
+                            </div>
+                            <img src={item.material_type === 'image' ? item.oss_url : item.materialType === 1 ? item.key_frame_image_url : getVideoImgUrl(item.oss_url)} className={style.coverImg} alt="" />
+                        </div>)}
+                    </div>
+                </div>
+                {!!active && <a style={{ color: 'red' }} onClick={() => setActive(undefined)}>清除单独修改</a>}
+                <a style={{ color: 'red' }} onClick={() => setCheckedFolderList([])}>清除所有</a>
+            </> : <div></div>}
+            <Space>
+                <Button onClick={onClose}>取消</Button>
+                <Button type="primary" onClick={handleOk} disabled={checkedFolderList.length === 0}>确定</Button>
+            </Space>
+        </div>}
+        className={`modalResetCss selectModal`}
+        bodyStyle={{ backgroundColor: '#f1f4fc', height: 700, overflow: 'hidden', padding: '10px' }}
+    >
+        <SelectGroupCloudNew
+            num={num}
+            defaultParams={defaultParams}
+            checkedFolderList={checkedFolderList}
+            setCheckedFolderList={setCheckedFolderList}
+            accountCreateLogs={accountCreateLogs}
+            putInType={putInType}
+            active={active}
+        />
+    </Modal>
+}
+
+export default React.memo(SelectCloudComponent)

+ 49 - 379
src/pages/launchSystemV3/material/cloudNew/selectCloudNew.tsx

@@ -1,170 +1,70 @@
-import { Button, Card, Checkbox, Divider, Empty, Form, message, Modal, Pagination, Popover, Radio, Select, Space, Spin, Tooltip, Typography } from "antd"
-import React, { useEffect, useRef, useState } from "react"
-import { useAjax } from "@/Hook/useAjax"
-import { getMaterialDataListApi } from "@/services/adqV3/cloudNew"
+import { Button, message, Modal, Space } from "antd"
+import React, { useEffect, useState } from "react"
 import style from './index.less'
-import SelectSearch from "./selectSearch"
 import './global.less'
 import '../../tencentAdPutIn/index.less'
-import { useSize } from "ahooks"
-import useFileDrop from "@/Hook/useFileDrop"
-import { formatBytes, formatSecondsToTime, getVideoImgUrl } from "@/utils/utils"
-import UploadFile from "./uploadFile"
-import { useModel } from "umi"
-import UploadsTable from "./uploadsTable"
-import { DeleteOutlined, PlayCircleOutlined, SortAscendingOutlined } from "@ant-design/icons"
+import { getVideoImgUrl } from "@/utils/utils"
+import { DeleteOutlined, PlayCircleOutlined } from "@ant-design/icons"
 import PlayVideo from "./playVideo"
-import { showFieldList } from "./const"
-import SelectFolder from "./selectFolder"
 import SelectGroupCloudNew from "./selectGroupCloudNew"
 import SelectGroupUnitNew from "./selectGroupUnitNew"
-import { addOnlyDataApi, getOnlyDataApi } from "@/services/adqV3"
-
-
-const { Text, Paragraph } = Typography;
+import SelectComponentsUnit from "./selectComponentsUnit"
+import ImageXXX from "../../components/AdsComponent/ImageXXX"
+import MediaList from "./mediaList"
 
 /**
  * 选择素材
  * @param param0 
  * @returns 
  */
-const SelectCloudNew: React.FC<CLOUDNEW.SelectCloudNewProps> = ({ visible, defaultParams, num, sliderImgContent, isGroup, onChange, onClose, accountCreateLogs, putInType }) => {
+const SelectCloudNew: React.FC<CLOUDNEW.SelectCloudNewProps> = ({ visible, defaultParams, num, sliderImgContent, isGroup, componentCount, onChange, onClose, accountCreateLogs, putInType, deliveryMode }) => {
 
     /************************************/
-    const { initialState } = useModel('@@initialState');
-    const ref = useRef<HTMLDivElement>(null);
-    const size = useSize(ref);
     const [checkedFolderList, setCheckedFolderList] = useState<any[]>([])
-    const [checkFolderAll, setCheckFolderAll] = useState<boolean>(false);
-    const [indeterminateFolder, setIndeterminateFolder] = useState<boolean>(false);
-    const [rowNum, setRowNum] = useState<number>(0)
-    const [SCKType, setSCKType] = useState<'1' | '2'>('1')
+    const [SCKType, setSCKType] = useState<string>('1')
 
-    const [queryParams, setQueryParams] = useState<CLOUDNEW.GetMaterialDataListProps>({ pageNum: 1, pageSize: 20 })
-    const [searchParams, setSearchParams] = useState<Partial<CLOUDNEW.GetMaterialDataListProps>>({})
     const [uploadVisible, setUploadVisible] = useState<boolean>(false)
-    const [isGetField, setIsGetField] = useState<boolean>(false)
-    const [showField, setShowField] = useState<string[]>(['material.create_time', 'material_data_day.cost', 'material_data_day.ctr', 'material_data_day.conversions_rate', 'material_data_day.dynamic_creative_count'])
-    const [sortData, setSortData] = useState<{ sortField: string | undefined, sortType: boolean }>({ sortField: undefined, sortType: false })
     const [active, setActive] = useState<number>()
-    const [isShowAll, setIsShowAll] = useState<boolean>(false)
-
-    const getMaterialDataList = useAjax((params) => getMaterialDataListApi(params))
-    const getOnlyData = useAjax((params) => getOnlyDataApi(params))
-    const addOnlyData = useAjax((params) => addOnlyDataApi(params))
     /************************************/
 
-    useEffect(() => {
-        getOnlyData.run({ type: 'MODE_FIELD_ENT' }).then(res => {
-            if (res?.data) {
-                const { showField, sortData } = JSON.parse(res.data)
-                setShowField(showField)
-                setSortData(sortData)
-                setTimeout(() => setIsGetField(true), 0)
-            } else {
-                setIsGetField(true)
-            }
-        }).catch(() => setIsGetField(true))
-    }, [])
-
     useEffect(() => {
         if (sliderImgContent && sliderImgContent?.length > 0) {
             setCheckedFolderList(sliderImgContent as any || [])
         }
     }, [])
 
-    useEffect(() => {
-        if (isGetField) {
-            let params = { ...searchParams, ...defaultParams, ...queryParams }
-            if (sortData?.sortField) {
-                params = { ...params, ...sortData }
-            }
-            if (params?.sizeList?.length) {
-                params.sizeQueries = params.sizeList.map(item => {
-                    const [width, height, relation] = item.split('*')
-                    return { width: Number(width), height: Number(height), relation: relation || '=' }
-                })
-            }
-            delete params?.sizeList
-            if (isShowAll) {
-                delete params?.sizeQueries
-            }
-            getMaterialDataList.run(params)
-        }
-    }, [queryParams, defaultParams, searchParams, sortData, showField, isShowAll, isGetField])
-
-    // 根据内容宽度计算列数
-    useEffect(() => {
-        if (size?.width) {
-            let rowNum = Math.floor((size?.width - 26) / 200)
-            setRowNum(rowNum || 1)
-        }
-    }, [size?.width])
-
-    // 处理全选按钮状态
-    useEffect(() => {
-        let data: any[] = getMaterialDataList?.data?.records || []
-        let dataIds = data.map(item => item.id)
-        let selectIds = checkedFolderList.map(item => item.id)
-        if (selectIds.length === 0 || dataIds.length === 0) {
-            setCheckFolderAll(false)
-            setIndeterminateFolder(false);
-        } else if (dataIds.every((element) => selectIds.includes(element))) {
-            setCheckFolderAll(true)
-            setIndeterminateFolder(false);
-        } else if (dataIds.some((element) => selectIds.includes(element))) {
-            setCheckFolderAll(false)
-            setIndeterminateFolder(true);
-        } else {
-            setCheckFolderAll(false)
-            setIndeterminateFolder(false);
-        }
-    }, [checkedFolderList, getMaterialDataList?.data?.records])
 
     const handleOk = () => {
-        if (isGroup && num && checkedFolderList && num > checkedFolderList?.length) {
+        if (isGroup && num && checkedFolderList && num > checkedFolderList?.length && SCKType !== '4') {
             message.error(`当前选择了组图${num}图,当前选择了${checkedFolderList.length}个图片`)
             return
         }
         onChange?.(checkedFolderList)
     }
 
-    // 选择
-    const onCheckboxChange = (checked: boolean, item: any) => {
-        let newCheckedFolderList: any[] = JSON.parse(JSON.stringify(checkedFolderList))
-        if (active || active === 0) {
-            if (checked) { // 选中
-                newCheckedFolderList[active] = { ...item, materialType: 0 }
-            } else { // 取消
-                message.warning('请选择其他图片替换')
-                return
-            }
-        } else {
-            if (checked) { // 选中
-                newCheckedFolderList.push({ ...item, materialType: 0 })
-            } else { // 取消
-                newCheckedFolderList = newCheckedFolderList.filter(i => i.id !== item.id)
-            }
-        }
-        setCheckedFolderList(newCheckedFolderList)
-    };
-
-    // 获取拖拽的文件
-    const [fileList, setFileList] = useState<FileList>()
-    const [uploadsVisible, setUploadsVisible] = useState<boolean>(false)
-    const { isDragOver, dropAreaProps } = useFileDrop((fileList) => {
-        setFileList(fileList)
-        setUploadsVisible(true)
-    });
-
     return <Modal
         title={<div className={'SCK_header'}>
-            {[{ label: '素材库', value: '1' }, { label: '账户组媒体素材', value: '2' }, { label: '业务单元素材', value: '3' }].map(item => <div
+            {[{ label: '素材库', value: '1' }, { label: '账户组媒体素材', value: '2' }, { label: '业务单元素材', value: '3' }, ...(deliveryMode === 'DELIVERY_MODE_COMPONENT' ? [{ label: '业务单元组件', value: '4' }] : [])].map(item => <div
                 className={item.value === SCKType ? 'selected' : ''}
                 key={item.value}
                 onClick={() => {
-                    setSCKType(item.value as any)
-                    // setCheckedFolderList([])
+                    if ((item.value === '4' || SCKType === '4') && checkedFolderList?.length) {
+                        Modal.warning({
+                            title: '警告',
+                            content: '业务单元组件与其它素材库不同,选择将清空当前选择',
+                            className: 'modalResetCss',
+                            okText: '清空跳转',
+                            keyboard: false,
+                            closable: true,
+                            okCancel: true,
+                            onOk() {
+                                setCheckedFolderList([])
+                                setSCKType(item.value as any)
+                            }
+                        });
+                    } else {
+                        setSCKType(item.value as any)
+                    }
                 }}>
                 {item.label}
             </div>)}
@@ -183,7 +83,7 @@ const SelectCloudNew: React.FC<CLOUDNEW.SelectCloudNewProps> = ({ visible, defau
                                 {item.material_type === 'video' && <PlayVideo videoUrl={item.oss_url}>{(onPlay) => <a onClick={onPlay}><PlayCircleOutlined /></a>}</PlayVideo>}
                                 <a style={{ color: 'red' }} onClick={() => setCheckedFolderList(data => data.filter(i => i.id !== item.id))}><DeleteOutlined /></a>
                             </div>
-                            <img src={item.material_type === 'image' ? item.oss_url : item.materialType === 1 ? item.key_frame_image_url : getVideoImgUrl(item.oss_url)} className={style.coverImg} alt="" />
+                            {([4].includes(item.materialType) && item.componentSubType.includes('IMAGE_LIST')) ? <ImageXXX imageList={item?.oss_url} placement="top" /> : <img src={item.material_type === 'image' ? item.oss_url : [1, 4].includes(item.materialType) ? item.key_frame_image_url : getVideoImgUrl(item.oss_url)} className={style.coverImg} alt="" />}
                         </div>)}
                     </div>
                 </div>
@@ -198,224 +98,15 @@ const SelectCloudNew: React.FC<CLOUDNEW.SelectCloudNewProps> = ({ visible, defau
         className={`modalResetCss selectModal SCK`}
         bodyStyle={{ backgroundColor: '#f1f4fc', height: 700, overflow: 'hidden', padding: '10px' }}
     >
-        {SCKType === '1' ? <div className={style.select_cloudNew_layout}>
-            {/* 搜索 */}
-            <SelectSearch
-                sizeQueries={defaultParams?.sizeQueries}
-                onSearch={(value) => {
-                    ref.current?.scrollTo(0, 0)
-                    setSearchParams(value)
-                }}
-            />
-
-            <Card
-                style={{ height: '100%', flex: '1 0', overflow: 'hidden' }}
-                bodyStyle={{ padding: 0, overflow: 'auto hidden', height: '100%' }}
-                className="cardResetCss buttonResetCss"
-                bordered
-            >
-                <div className={style.cloudNew}>
-                    {/* 选择文件夹 */}
-                    <SelectFolder selectedKeys={queryParams?.folderId} onChange={(value) => { setQueryParams({ ...queryParams, folderId: value, pageNum: 1 }) }} />
-                    <div className={style.material} style={{ height: '100%' }}>
-                        <div className={style.operates}>
-                            <div className={style.left_bts}>
-                                <Checkbox
-                                    onChange={(e) => {
-                                        let newCheckedFolderList: any[] = JSON.parse(JSON.stringify(checkedFolderList))
-                                        const data: any[] = getMaterialDataList?.data?.records
-
-                                        if (e.target.checked) { // 全选
-                                            const selectIds = newCheckedFolderList.map(item => item.id)
-                                            const remainData = data.filter(item => !selectIds.includes(item.id))
-                                            const remainDataLength = remainData.length
-                                            const remainNum = num - newCheckedFolderList.length
-                                            if (remainNum > remainDataLength) {
-                                                newCheckedFolderList.push(...remainData.map(item => ({ ...item, materialType: 0 })))
-                                            } else {
-                                                newCheckedFolderList.push(...remainData.splice(0, remainNum).map(item => ({ ...item, materialType: 0 })))
-                                            }
-                                            newCheckedFolderList = Array.from(new Set(newCheckedFolderList.map(item => JSON.stringify(item)))).map(item => JSON.parse(item));
-                                        } else { // 取消全选
-                                            const dataIds = data.map(item => item.id)
-                                            newCheckedFolderList = newCheckedFolderList.filter(i => !dataIds.includes(i.id))
-                                        }
-                                        setCheckedFolderList(newCheckedFolderList)
-                                    }}
-                                    disabled={checkedFolderList?.length >= num || !!active || active === 0 || isShowAll}
-                                    indeterminate={indeterminateFolder}
-                                    checked={checkFolderAll}
-                                >全选</Checkbox>
-                                <span>已选 <span style={{ color: '#1890FF' }}>{checkedFolderList?.length || 0}</span>/{num} 个素材</span>
-                                {checkedFolderList.length > 0 && <a style={{ color: 'red' }} onClick={() => setCheckedFolderList([])}>清除所有</a>}
-                                {sortData?.sortField && <Text>「排序-{showFieldList.find(item => item.value === sortData.sortField)?.label}-{sortData.sortType ? '正序' : '倒序'}」</Text>}
-                            </div>
-                            <div className={style.left_bts}>
-                                <Checkbox onChange={(e) => setIsShowAll(e.target.checked)} checked={isShowAll}>
-                                    <Tooltip title="勾选只展示,不支持选择投放">
-                                        <span style={{ fontSize: 12 }}>显示所有{defaultParams?.materialType === 'video' ? '视频' : '图片'}素材</span>
-                                    </Tooltip>
-                                </Checkbox>
-                                <Popover
-                                    content={<div style={{ width: 320 }}>
-                                        <Form
-                                            labelCol={{ span: 5 }}
-                                            labelAlign="left"
-                                            colon={false}
-                                        >
-                                            <Form.Item label={<strong>展示指标</strong>}>
-                                                <Select
-                                                    placeholder="选择展示指标"
-                                                    showSearch
-                                                    filterOption={(input, option) =>
-                                                        (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
-                                                    }
-                                                    mode="multiple"
-                                                    options={showFieldList}
-                                                    value={showField}
-                                                    onChange={(value) => {
-                                                        if (value.length > 6) {
-                                                            message.warn('最多只能选择6个指标')
-                                                            return
-                                                        }
-                                                        if (value.length < 1) {
-                                                            message.warn('最少选择1个指标')
-                                                            return
-                                                        }
-                                                        setSortData({ ...sortData, sortField: undefined })
-                                                        setShowField(() => value as any)
-                                                        addOnlyData.run({ data: JSON.stringify({ showField: value, sortData: { ...sortData, sortField: undefined } }), type: 'MODE_FIELD_ENT' })
-                                                    }}
-                                                />
-                                            </Form.Item>
-                                            <Form.Item label={<strong>排序</strong>}>
-                                                <Space>
-                                                    <Select
-                                                        style={{ width: 125 }}
-                                                        placeholder="选择排序指标"
-                                                        showSearch
-                                                        filterOption={(input, option) =>
-                                                            (option?.label as any)?.toLowerCase().indexOf(input.toLowerCase()) >= 0
-                                                        }
-                                                        options={showFieldList.filter(item => showField.includes(item.value) && item.value !== 'description')}
-                                                        value={sortData.sortField}
-                                                        allowClear
-                                                        onChange={(value) => {
-                                                            setSortData({ ...sortData, sortField: value as any })
-                                                            addOnlyData.run({ data: JSON.stringify({ showField, sortData: { ...sortData, sortField: value as any }}), type: 'MODE_FIELD_ENT' })
-                                                        }}
-                                                    />
-                                                    <Radio.Group
-                                                        value={sortData.sortType}
-                                                        onChange={(e) => {
-                                                            setSortData({ ...sortData, sortType: e.target.value })
-                                                            addOnlyData.run({ data: JSON.stringify({ showField, sortData: { ...sortData, sortType: e.target.value }}), type: 'MODE_FIELD_ENT' })
-                                                        }}
-                                                        buttonStyle="solid"
-                                                    >
-                                                        <Radio.Button value={true}>正序</Radio.Button>
-                                                        <Radio.Button value={false}>倒序</Radio.Button>
-                                                    </Radio.Group>
-                                                </Space>
-                                            </Form.Item>
-                                        </Form>
-                                    </div>}
-                                    trigger={['click']}
-                                    title={<strong>自定义展示指标与排序</strong>}
-                                    placement="bottomRight"
-                                >
-                                    <Button icon={<SortAscendingOutlined />}>自定义展示指标与排序</Button>
-                                </Popover>
-                            </div>
-                        </div>
-                        <div className={`${style.content} content_global`}>
-                            <Spin spinning={getMaterialDataList.loading || getOnlyData.loading}>
-                                <div className={style.content_scroll} ref={ref} {...dropAreaProps}>
-                                    {isDragOver && <div className={`${style.placeholder} ${isDragOver ? style.dragOver : ''}`}><span>拖动文件到这里</span></div>}
-                                    {getMaterialDataList?.data?.records?.length > 0 ? <Checkbox.Group value={checkedFolderList?.filter((_, index) => (active || active === 0) ? index === active : true)?.map(item => item.id)} style={{ width: '100%' }}>
-                                        <div className={style.content_scroll_div}>
-                                            {getMaterialDataList?.data?.records.map((item: any) => {
-                                                const isSelect = checkedFolderList?.filter((_, index) => (active || active === 0) ? index === active : true)?.map(item => item.id).includes(item.id)
-                                                const disabled = ((active || active === 0) ? false : (checkedFolderList.length >= num && !isSelect)) || isShowAll
-                                                return <div key={item.id} className={style.content_row} style={{ width: rowNum ? (1 / rowNum * 100) + '%' : 200 }}>
-                                                    <Card
-                                                        hoverable
-                                                        bodyStyle={{ padding: 0 }}
-                                                        style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}
-                                                        className={`${style.content_col}`}
-                                                        cover={<div style={{ height: 120, padding: 0 }} className={style.content_cover}>
-                                                            <div className={style.checkbox}><Checkbox disabled={disabled} value={item.id} onChange={(e) => onCheckboxChange(e.target.checked, item)} /></div>
-                                                            {item.material_type === 'video' && <div className={style.playr}>
-                                                                <PlayVideo videoUrl={item.oss_url}>{(onPlay) => <img onClick={(e) => {
-                                                                    e.stopPropagation(); e.preventDefault()
-                                                                    onPlay()
-                                                                }} src={require('../../../../../public/image/play.png')} alt="" />}</PlayVideo>
-                                                            </div>}
-                                                            <div className={style.file_info}>
-                                                                <div>{item.width}*{item.height}</div>
-                                                                {item.material_type === 'video' && <div>{formatSecondsToTime(Math.floor(item.video_duration))}</div>}
-                                                            </div>
-                                                            <img src={item.material_type === 'image' ? item.oss_url : getVideoImgUrl(item.oss_url)} className={style.coverImg} alt="" />
-                                                        </div>}
-                                                        onClick={() => !disabled && onCheckboxChange(!isSelect, item)}
-                                                    >
-                                                        <div className={style.body}>
-                                                            <Text ellipsis strong style={{ flex: '1 0' }}>{item?.material_name}</Text>
-                                                            <Text style={{ fontSize: 12 }}>{formatBytes(item?.file_size)}</Text>
-                                                        </div>
-                                                        <Divider style={{ margin: '0 0 4px 0' }} />
-                                                        <div style={{ padding: '0 10px 6px' }}>
-                                                            {showField.map(field => {
-                                                                switch (field) {
-                                                                    case 'material.create_time':
-                                                                        return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>创建时间:{item?.create_time}</Paragraph>
-                                                                    case 'material_data_day.cost':
-                                                                        return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>消耗:{(item?.cost === null || item?.cost === undefined) ? '--' : item?.cost}</Paragraph>
-                                                                    case 'material_data_day.order_pv':
-                                                                        return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>下单次数:{(item?.order_pv === null || item?.order_pv === undefined) ? '--' : item?.order_pv}</Paragraph>
-                                                                    case 'material_data_day.order_cost':
-                                                                        return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>下单成本:{(item?.order_cost === null || item?.order_cost === undefined) ? '--' : item?.order_cost}</Paragraph>
-                                                                    case 'material_data_day.ctr':
-                                                                        return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>点击率:{(item?.ctr === null || item?.ctr === undefined) ? '--' : (item?.ctr * 100).toFixed(2) + '%'}</Paragraph>
-                                                                    case 'material_data_day.conversions_rate':
-                                                                        return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>目标转化率:{(item?.conversions_rate === null || item?.conversions_rate === undefined) ? '--' : (item?.conversions_rate * 100).toFixed(2) + '%'}</Paragraph>
-                                                                    case 'material_data_day.adgroup_count':
-                                                                        return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>广告关联数:{(item?.adgroup_count === null || item?.adgroup_count === undefined) ? '--' : item?.adgroup_count}</Paragraph>
-                                                                    case 'material_data_day.dynamic_creative_count':
-                                                                        return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>创意关联数:{(item?.dynamic_creative_count === null || item?.dynamic_creative_count === undefined) ? '--' : item?.dynamic_creative_count}</Paragraph>
-                                                                    default:
-                                                                        return null
-                                                                }
-                                                            })}
-                                                            {showField.includes('description') && <Paragraph style={{ fontSize: 12, marginBottom: 1 }} ellipsis={{ tooltip: true }}>备注:{item.description || '--'}</Paragraph>}
-                                                        </div>
-                                                    </Card>
-                                                </div>
-                                            })}
-                                        </div>
-                                    </Checkbox.Group> : <div style={{ height: '100%', width: '100%', alignContent: 'center' }}><Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /></div>}
-                                </div>
-                            </Spin>
-                        </div>
-                        <div className={style.fotter}>
-                            <Pagination
-                                size="small"
-                                total={getMaterialDataList?.data?.total || 0}
-                                showSizeChanger
-                                showQuickJumper
-                                pageSize={getMaterialDataList?.data?.size || 20}
-                                current={getMaterialDataList?.data?.current || 1}
-                                onChange={(page: number, pageSize: number) => {
-                                    ref.current?.scrollTo(0, 0)
-                                    setQueryParams({ ...queryParams, pageNum: page, pageSize })
-                                }}
-                                pageSizeOptions={[10, 15, 20, 50, 100]}
-                            />
-                        </div>
-                    </div>
-                </div>
-            </Card>
-        </div> : SCKType === '2' ? <SelectGroupCloudNew
+        {SCKType === '1' ? <MediaList 
+            num={num}
+            defaultParams={defaultParams}
+            checkedFolderList={checkedFolderList}
+            setCheckedFolderList={setCheckedFolderList}
+            active={active}
+            uploadVisible={uploadVisible}
+            setUploadVisible={setUploadVisible}
+        /> : SCKType === '2' ? <SelectGroupCloudNew
             num={num}
             defaultParams={defaultParams}
             checkedFolderList={checkedFolderList}
@@ -423,7 +114,7 @@ const SelectCloudNew: React.FC<CLOUDNEW.SelectCloudNewProps> = ({ visible, defau
             accountCreateLogs={accountCreateLogs}
             putInType={putInType}
             active={active}
-        /> : <SelectGroupUnitNew
+        /> : SCKType === '3' ? <SelectGroupUnitNew
             num={num}
             defaultParams={defaultParams}
             checkedFolderList={checkedFolderList}
@@ -431,37 +122,16 @@ const SelectCloudNew: React.FC<CLOUDNEW.SelectCloudNewProps> = ({ visible, defau
             accountCreateLogs={accountCreateLogs}
             putInType={putInType}
             active={active}
-        />}
-
-
-        {/* 上传素材 */}
-        {uploadVisible && <UploadFile
-            userId={(initialState?.currentUser?.userId || 0) as number}
-            visible={uploadVisible}
-            onChange={() => {
-                setUploadVisible(false)
-                getMaterialDataList.refresh()
-            }}
-            onClose={() => {
-                setUploadVisible(false)
-            }}
-        />}
-
-        {/* 拖动上传 */}
-        {uploadsVisible && <UploadsTable
-            userId={(initialState?.currentUser?.userId || 0) as number}
-            visible={uploadsVisible}
-            fileList={fileList}
-            isPermission={true}
-            onClose={() => {
-                setUploadsVisible(false)
-                setFileList(undefined)
-            }}
-            onChange={() => {
-                setUploadsVisible(false)
-                setFileList(undefined)
-                getMaterialDataList.refresh()
-            }}
+        /> : <SelectComponentsUnit
+            num={num}
+            defaultParams={defaultParams}
+            checkedFolderList={checkedFolderList}
+            setCheckedFolderList={setCheckedFolderList}
+            accountCreateLogs={accountCreateLogs}
+            putInType={putInType}
+            active={active}
+            isGroup={isGroup}
+            componentCount={componentCount}
         />}
     </Modal>
 }

+ 379 - 0
src/pages/launchSystemV3/material/cloudNew/selectComponentsUnit.tsx

@@ -0,0 +1,379 @@
+import React, { useEffect, useState, useRef } from "react"
+import style from './index.less'
+import { Card, Checkbox, Divider, Empty, message, Pagination, Result, Space, Spin, Typography, Image } from "antd"
+import { useAjax } from "@/Hook/useAjax"
+import './global.less'
+import '../../tencentAdPutIn/index.less'
+import { EyeOutlined } from "@ant-design/icons"
+import PlayVideo from "./playVideo"
+import Lazyimg from "react-lazyimg-component"
+import SyncCloudSc from "./syncCloudSc"
+import { checkAccountUnitApi } from "@/services/launchAdq/adAuthorize"
+import { getCreativeComponentListApi, GetCreativeComponentProps } from "@/services/adqV3/global"
+import { COMPONENT_GENERATION_TYPE_ENUM, COMPONENT_SUB_TYPE, getComponentType } from "../../tencenTasset/manageComponent/const"
+import moment from "moment"
+import SelectComponentsUnitSearch from "./selectComponentsUnitSearch"
+
+const { Text, Paragraph } = Typography;
+
+interface Props extends CLOUDNEW.SelectGroupCloudNewProps {
+    isGroup?: boolean
+    componentCount?: number
+}
+/**
+ * 选择业务单元组件
+ * @param param0 
+ * @returns 
+ */
+const SelectComponentsUnit: React.FC<Props> = ({ num: count, defaultParams, checkedFolderList, setCheckedFolderList, accountCreateLogs, active, isGroup, componentCount }) => {
+
+    /*****************************************/
+    const num = (isGroup ? componentCount : count) || 1
+    const [rowNum, setRowNum] = useState<number>(0)
+    const refScllor = useRef<HTMLDivElement>(null)
+
+    const [checkFolderAll, setCheckFolderAll] = useState<boolean>(false);
+    const [indeterminateFolder, setIndeterminateFolder] = useState<boolean>(false);
+
+    const [queryParams, setQueryParams] = useState<GetCreativeComponentProps>({ pageNum: 1, pageSize: 20 })
+    const [searchParams, setSearchParams] = useState<Partial<GetCreativeComponentProps>>({})
+    const [unitAccountId, setUnitAccountId] = useState<number>()
+    const [previewData, setPreviewData] = useState<{ visible: boolean, url?: string[] }>({ visible: false })
+    const [componentSubType, setComponentSubType] = useState<string[]>([])
+
+    const getCreativeComponentList = useAjax((params) => getCreativeComponentListApi(params))
+    const checkAccountUnit = useAjax((params) => checkAccountUnitApi(params))
+    /*****************************************/
+
+    // 根据内容宽度计算列数
+    useEffect(() => {
+        let rowNum = Math.floor(1350 / 220)
+        setRowNum(rowNum || 1)
+    }, [])
+
+    // 处理全选按钮状态
+    useEffect(() => {
+        let data: any[] = getCreativeComponentList?.data?.records || []
+        let dataIds = data.map(item => item.componentId)
+        let selectIds = checkedFolderList.map(item => item.id)
+        if (selectIds.length === 0 || dataIds.length === 0) {
+            setCheckFolderAll(false)
+            setIndeterminateFolder(false);
+        } else if (dataIds.every((element) => selectIds.includes(element))) {
+            setCheckFolderAll(true)
+            setIndeterminateFolder(false);
+        } else if (dataIds.some((element) => selectIds.includes(element))) {
+            setCheckFolderAll(false)
+            setIndeterminateFolder(true);
+        } else {
+            setCheckFolderAll(false)
+            setIndeterminateFolder(false);
+        }
+    }, [checkedFolderList, getCreativeComponentList?.data?.records])
+
+    useEffect(() => {
+        if (accountCreateLogs?.length) {
+            checkAccountUnit.run(accountCreateLogs.map(item => item.accountId)).then(res => {
+                setUnitAccountId(res?.adUnitAccountId)
+            })
+        }
+    }, [accountCreateLogs])
+
+    useEffect(() => {
+        console.log('defaultParams--->', defaultParams)
+        if (unitAccountId) {
+            const materialType = defaultParams.materialType
+            let componentSubType: string[] = []
+            if (isGroup) {
+                switch (count) {
+                    case 1:
+                        componentSubType = ['IMAGE_LIST_1X1_1']
+                        break
+                    case 3:
+                        componentSubType = ['IMAGE_LIST_1X1_3']
+                        break
+                    case 4:
+                        componentSubType = ['IMAGE_LIST_1X1_4']
+                        break
+                    case 6:
+                        componentSubType = ['IMAGE_LIST_1X1_6']
+                        break
+                    case 9:
+                        componentSubType = ['IMAGE_LIST_1X1_9']
+                        break
+                }
+            } else {
+                // 组件化创意
+                if (defaultParams?.sizeQueries && defaultParams?.sizeQueries?.length > 1) {
+                    if (defaultParams?.materialType === "image") {
+                        componentSubType = ['IMAGE_1X1', 'IMAGE_16X9', 'IMAGE_20X7', 'IMAGE_3X2', 'IMAGE_9X16']
+                    } else if (defaultParams?.materialType === "video") {
+                        componentSubType = ['VIDEO_16X9', 'VIDEO_9X16', 'VIDEO_4X3']
+                    }
+                } else {
+                    const { width, height } = defaultParams?.sizeQueries?.[0] || { width: 0, height: 0 }
+                    if (width === 1280 && height === 720) {
+                        if (materialType === "video") {
+                            componentSubType = ['VIDEO_16X9']
+                        } else {
+                            componentSubType = ['IMAGE_16X9']
+                        }
+                    }
+                }
+            }
+            setComponentSubType(componentSubType)
+            // defaultParams
+            let params = { ...searchParams, ...queryParams, adAccountId: unitAccountId, componentSubType }
+            getCreativeComponentList.run(params)
+        }
+    }, [queryParams, defaultParams, searchParams, unitAccountId, isGroup, count])
+
+    // 选择
+    const onCheckboxChange = (checked: boolean, item: any, type: "IMAGE" | "VIDEO" | "IMAGE_LIST" | "OTHER") => {
+        let newCheckedFolderList: any[] = JSON.parse(JSON.stringify(checkedFolderList))
+        let data: { oss_url: string, key_frame_image_url?: string } = { oss_url: '' }
+        switch (type) {
+            case "IMAGE":
+                data = { oss_url: item?.componentValue?.image?.value?.imageUrl }
+                break
+            case "VIDEO":
+                data = {
+                    oss_url: item?.componentValue?.video?.value?.videoUrl || item?.componentValue?.shortVideo?.value?.shortVideo1Url,
+                    key_frame_image_url: item?.componentValue?.video?.value?.coverUrl || item?.componentValue?.shortVideo?.value?.shortVideo2Url
+                }
+                break
+            case "IMAGE_LIST":
+                data = { oss_url: item?.componentValue?.imageList?.value?.list }
+                break
+        }
+        if (active || active === 0) {
+            if (checked) { // 选中
+                newCheckedFolderList[active] = { ...item, ...data, id: item.componentId, material_type: defaultParams.materialType, materialType: 4, isUnitComponent: true }
+            } else { // 取消
+                message.warning('请选择其他图片替换')
+                return
+            }
+        } else {
+            if (checked) { // 选中
+                newCheckedFolderList.push({ ...item, ...data, id: item.componentId, material_type: defaultParams.materialType, materialType: 4, isUnitComponent: true })
+            } else { // 取消
+                const id = item.componentId
+                newCheckedFolderList = newCheckedFolderList.filter(i => i.id !== id)
+            }
+        }
+        setCheckedFolderList(newCheckedFolderList)
+    };
+
+    return <Spin spinning={checkAccountUnit.loading} wrapperClassName={'select_group_spin'}>
+        <div className={style.select_cloudNew_layout}>
+            {unitAccountId ? <>
+                {/* 搜索 */}
+                <SelectComponentsUnitSearch
+                    type={defaultParams.materialType}
+                    componentSubType={componentSubType}
+                    onSearch={(value) => {
+                        refScllor.current?.scrollTo(0, 0)
+                        setSearchParams(value)
+                    }}
+                />
+                <Card
+                    style={{ height: '100%', flex: '1 0', overflow: 'hidden' }}
+                    bodyStyle={{ padding: 0, overflow: 'auto hidden', height: '100%' }}
+                    className="cardResetCss buttonResetCss"
+                    bordered
+                >
+                    <div className={style.cloudNew}>
+                        <div className={style.material} style={{ height: '100%' }}>
+                            <div className={style.operates}>
+                                <div className={style.left_bts}>
+                                    <Checkbox
+                                        onChange={(e) => {
+                                            let newCheckedFolderList: any[] = JSON.parse(JSON.stringify(checkedFolderList))
+                                            const data: any[] = getCreativeComponentList?.data?.records
+
+                                            if (e.target.checked) { // 全选
+                                                const selectIds = newCheckedFolderList.map(item => item.id)
+                                                const remainData = data.filter(item => {
+                                                    const id = item.componentId
+                                                    return !selectIds.includes(id)
+                                                })
+                                                const remainDataLength = remainData.length
+                                                const remainNum = num - newCheckedFolderList.length
+                                                if (remainNum > remainDataLength) {
+                                                    newCheckedFolderList.push(...remainData.map(i => {
+                                                        let data: { oss_url: string, key_frame_image_url?: string } = { oss_url: '' }
+                                                        const type = getComponentType(i.componentSubType)
+                                                        switch (type) {
+                                                            case "IMAGE":
+                                                                data = { oss_url: i?.componentValue?.image?.value?.imageUrl }
+                                                                break
+                                                            case "VIDEO":
+                                                                data = {
+                                                                    oss_url: i?.componentValue?.video?.value?.videoUrl || i?.componentValue?.shortVideo?.value?.shortVideo1Url,
+                                                                    key_frame_image_url: i?.componentValue?.video?.value?.coverUrl || i?.componentValue?.shortVideo?.value?.shortVideo2Url
+                                                                }
+                                                                break
+                                                            case "IMAGE_LIST":
+                                                                data = { oss_url: i?.componentValue?.imageList?.value?.list }
+                                                                break
+                                                        }
+                                                        return { ...i, ...data, material_type: defaultParams.materialType, id: i.componentId, materialType: 4, isUnitComponent: true }
+                                                    }))
+                                                } else {
+                                                    newCheckedFolderList.push(...remainData.splice(0, remainNum).map(i => {
+                                                        let data: { oss_url: string, key_frame_image_url?: string } = { oss_url: '' }
+                                                        const type = getComponentType(i.componentSubType)
+                                                        switch (type) {
+                                                            case "IMAGE":
+                                                                data = { oss_url: i?.componentValue?.image?.value?.imageUrl }
+                                                                break
+                                                            case "VIDEO":
+                                                                data = {
+                                                                    oss_url: i?.componentValue?.video?.value?.videoUrl || i?.componentValue?.shortVideo?.value?.shortVideo1Url,
+                                                                    key_frame_image_url: i?.componentValue?.video?.value?.coverUrl || i?.componentValue?.shortVideo?.value?.shortVideo2Url
+                                                                }
+                                                                break
+                                                            case "IMAGE_LIST":
+                                                                data = { oss_url: i?.componentValue?.imageList?.value?.list }
+                                                                break
+                                                        }
+                                                        return { ...i, ...data, material_type: defaultParams.materialType, id: i.componentId, materialType: 4, isUnitComponent: true }
+                                                    }))
+                                                }
+                                                newCheckedFolderList = Array.from(new Set(newCheckedFolderList.map(item => JSON.stringify(item)))).map(item => JSON.parse(item));
+                                            } else { // 取消全选
+                                                const dataIds = data.map(item => item.componentId)
+                                                newCheckedFolderList = newCheckedFolderList.filter(i => !dataIds.includes(i.id))
+                                            }
+                                            setCheckedFolderList(newCheckedFolderList)
+                                        }}
+                                        disabled={checkedFolderList?.length >= num || !!active || active === 0}
+                                        indeterminate={indeterminateFolder}
+                                        checked={checkFolderAll}
+                                    >全选</Checkbox>
+                                    <span>已选 <span style={{ color: '#1890FF' }}>{checkedFolderList?.length || 0}</span>/{num} 个素材</span>
+                                    {checkedFolderList.length > 0 && <a style={{ color: 'red' }} onClick={() => setCheckedFolderList([])}>清除所有</a>}
+                                    {/* 同步素材 */}
+                                    <SyncCloudSc
+                                        accountId={unitAccountId}
+                                    />
+                                </div>
+                                <div className={style.left_bts}>
+
+                                </div>
+                            </div>
+                            <div className={`${style.content} content_global`}>
+                                <Spin spinning={getCreativeComponentList.loading}>
+                                    <div className={`${style.content_scroll} scroll`} ref={refScllor}>
+                                        {getCreativeComponentList?.data?.records?.length > 0 ? <Checkbox.Group value={checkedFolderList?.filter((_, index) => (active || active === 0) ? index === active : true)?.map(item => item.id)} style={{ width: '100%' }}>
+                                            <div className={style.content_scroll_div}>
+                                                {getCreativeComponentList?.data?.records.map((item: any) => {
+                                                    let id = item.componentId
+                                                    const componentSubType = item.componentSubType
+                                                    const type = getComponentType(componentSubType)
+                                                    const isSelect = checkedFolderList?.filter((_, index) => (active || active === 0) ? index === active : true)?.map(item => item.id).includes(id)
+                                                    const disabled = ((active || active === 0) ? false : (checkedFolderList.length >= num && !isSelect))
+                                                    const imageListLength = item?.componentValue?.imageList?.value?.list?.length || 0
+                                                    return <div key={id} className={style.content_row} style={{ width: rowNum ? (1 / rowNum * 100) + '%' : 220 }}>
+                                                        <Card
+                                                            hoverable
+                                                            bodyStyle={{ padding: 0 }}
+                                                            style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}
+                                                            className={`${style.content_col}`}
+                                                            cover={<div style={{ height: 120, padding: 0 }} className={style.content_cover}>
+                                                                <div className={style.checkbox}><Checkbox disabled={disabled} value={id} onChange={(e) => onCheckboxChange(e.target.checked, item, type)} /></div>
+                                                                {defaultParams.materialType === 'video' ? <div className={style.playr}>
+                                                                    <PlayVideo videoUrl={item?.componentValue?.video?.value?.videoUrl || item?.componentValue?.shortVideo?.value?.shortVideo1Url}>{(onPlay) => <img onClick={(e) => {
+                                                                        e.stopPropagation(); e.preventDefault()
+                                                                        onPlay()
+                                                                    }} src={require('../../../../../public/image/play.png')} alt="" />}</PlayVideo>
+                                                                </div> : <div className={style.imgPreview} onClick={(e) => {
+                                                                    e.stopPropagation()
+                                                                    setPreviewData({
+                                                                        visible: true,
+                                                                        url: item?.componentValue?.image?.value?.imageUrl ? [item?.componentValue?.image?.value?.imageUrl] : item?.componentValue?.imageList?.value?.list?.map((i: { imageUrl: any }) => i.imageUrl)
+                                                                    })
+                                                                }}>
+                                                                    <Space><EyeOutlined /><span>预览</span></Space>
+                                                                </div>}
+                                                                <div className={style.file_info}>
+                                                                    <span></span>
+                                                                    <div>{COMPONENT_SUB_TYPE?.find(item => item.value === componentSubType)?.label || '--'}</div>
+                                                                </div>
+                                                                {type === 'IMAGE' ? <Lazyimg
+                                                                    animateType="transition"
+                                                                    src={item?.componentValue?.image?.value?.imageUrl}
+                                                                    className={`${style.coverImg} lazy`}
+                                                                    animateClassName={['transition-enter', 'transition-enter-active']}
+                                                                /> : type === 'IMAGE_LIST' ? <div className={style.mediaPic_select}>
+                                                                    <div className={`${style.mediaPicImgList} ${style['mediaPicImgList' + imageListLength]}`}>
+                                                                        {item?.componentValue?.imageList?.value?.list?.map((item: { componentValue: { imageList: { value: { list: string | any[] } } }; imageId: string; imageUrl: string }, _: any, records: any[]) => <Lazyimg
+                                                                            key={item.imageId}
+                                                                            animateType="transition"
+                                                                            src={item.imageUrl}
+                                                                            className={`lazy`}
+                                                                            animateClassName={['transition-enter', 'transition-enter-active']}
+                                                                        />)}
+                                                                    </div>
+                                                                </div> : type === 'VIDEO' ? <Lazyimg
+                                                                    animateType="transition"
+                                                                    src={item?.componentValue?.video?.value?.coverUrl || item?.componentValue?.shortVideo?.value?.shortVideo2Url}
+                                                                    className={`${style.coverImg} lazy`}
+                                                                    animateClassName={['transition-enter', 'transition-enter-active']}
+                                                                /> : '联系技术添加'}
+
+                                                            </div>}
+                                                            onClick={() => !disabled && onCheckboxChange(!isSelect, item, type)}
+                                                        >
+                                                            <div className={style.body}>
+                                                                <Text ellipsis strong style={{ flex: '1 0' }}>{item?.componentCustomName}</Text>
+                                                            </div>
+                                                            <Divider style={{ margin: '0 0 4px 0' }} />
+                                                            <div style={{ padding: '0 10px 6px' }}>
+                                                                <Paragraph style={{ fontSize: 12, marginBottom: 1 }}>创建时间:{moment.unix(item?.createdTime).format('YYYY-MM-DD')}</Paragraph>
+                                                                <Paragraph style={{ fontSize: 12, marginBottom: 1 }}>组件ID:{item?.componentId}</Paragraph>
+                                                                <Paragraph style={{ fontSize: 12, marginBottom: 1 }}>来源:{COMPONENT_GENERATION_TYPE_ENUM['COMPONENT_GENERATION_TYPE_' + item.generationType as keyof typeof COMPONENT_GENERATION_TYPE_ENUM] || '--'}</Paragraph>
+                                                            </div>
+                                                        </Card>
+                                                    </div>
+                                                })}
+                                            </div>
+                                        </Checkbox.Group> : <div style={{ height: '100%', width: '100%', alignContent: 'center' }}><Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /></div>}
+                                    </div>
+                                </Spin>
+                            </div>
+                            <div className={style.fotter}>
+                                <Pagination
+                                    size="small"
+                                    total={getCreativeComponentList?.data?.total || 0}
+                                    showSizeChanger
+                                    showQuickJumper
+                                    pageSize={getCreativeComponentList?.data?.size || 20}
+                                    current={getCreativeComponentList?.data?.current || 1}
+                                    onChange={(page: number, pageSize: number) => {
+                                        refScllor.current?.scrollTo(0, 0)
+                                        setQueryParams({ ...queryParams, pageNum: page, pageSize })
+                                    }}
+                                    pageSizeOptions={[10, 15, 20, 50, 100]}
+                                />
+                            </div>
+                        </div>
+                    </div>
+                </Card>
+            </> : checkAccountUnit?.data ? <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
+                <Result
+                    title="警告"
+                    status="500"
+                    subTitle="选择的账户不在一个业务单元中或者没有加入业务单元"
+                />
+            </div> : undefined}
+        </div>
+
+        {/* 预览 */}
+        {previewData.visible && <Image.PreviewGroup preview={{ visible: previewData.visible, onVisibleChange: vis => setPreviewData({ visible: vis, url: [] }) }}>
+            {previewData?.url?.map((url: string, index: number) => <Image src={url} key={index} />)}
+        </Image.PreviewGroup>}
+    </Spin>
+}
+
+export default React.memo(SelectComponentsUnit)

+ 125 - 0
src/pages/launchSystemV3/material/cloudNew/selectComponentsUnitSearch.tsx

@@ -0,0 +1,125 @@
+import { Button, Card, Col, DatePicker, Form, Input, InputNumber, Row, Select, Space } from "antd"
+import React from "react"
+import { SearchOutlined } from "@ant-design/icons";
+import moment from "moment";
+import { SOURCE_TYPE_IMAGE_ENUM, SOURCE_TYPE_VIDEO_ENUM } from "../../tencentAdPutIn/const";
+import { COMMON_POTENTIAL_STATUS_ENUM, COMPONENT_GENERATION_TYPE_ENUM, DEFAULT_COMPONENT_SUB_IMAGE_TYPE, DEFAULT_COMPONENT_SUB_VIDEO_TYPE } from "../../tencenTasset/manageComponent/const";
+import { DefaultOptionType } from "antd/lib/select";
+
+interface Props {
+    type: 'image' | 'video'
+    componentSubType?: string[]
+    onSearch?: (value: Partial<CLOUDNEW.GetMaterialListProps>) => void
+}
+
+// 选择素材搜索
+const SelectComponentsUnitSearch: React.FC<Props> = ({ type, onSearch, componentSubType }) => {
+
+    /**********************************/
+    const [form] = Form.useForm();
+    /**********************************/
+
+    const handleOk = (values: any) => {
+        console.log(values)
+        let params: any = []
+        Object.keys(values).forEach(key => {
+            let value = values[key]
+            if (['idList'].includes(key) && value !== undefined && value !== null) {
+                let value1 = value.replace(/[,,\s]/g, ',')
+                params[type === 'image' ? 'imageId' : 'videoId'] = value1.split(',').filter((a: any) => a)
+            } if ('createTime' === key && value?.length === 2) {
+                params.createTimeMin = moment(value?.[0]).format('YYYY-MM-DD')
+                params.createTimeMax = moment(value?.[1]).format('YYYY-MM-DD')
+            } else {
+                params[key] = value
+            }
+        })
+        onSearch?.(params)
+    }
+
+    return <Card
+        bodyStyle={{ padding: '5px 10px', overflow: 'auto hidden', display: 'flex', gap: 6, flexWrap: 'wrap' }}
+        className="cardResetCss buttonResetCss"
+        bordered
+    >
+        <Form
+            layout="inline"
+            name="basicSelectSearch"
+            colon={false}
+            form={form}
+            onFinish={handleOk}
+        >
+            <Row gutter={[0, 6]}>
+                <Col><Form.Item name={'idList'}>
+                    <Input.TextArea rows={1} style={{ width: 190 }} allowClear placeholder="素材ID(多个空格or换行隔开)" />
+                </Form.Item></Col>
+                <Col><Form.Item name={'componentSubType'}>
+                    <Select
+                        showSearch
+                        placeholder="二级组件类型"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                        }
+                        style={{ minWidth: 160 }}
+                        maxTagCount={1}
+                        mode="multiple"
+                        allowClear
+                        options={(type === 'image' ? DEFAULT_COMPONENT_SUB_IMAGE_TYPE : DEFAULT_COMPONENT_SUB_VIDEO_TYPE).filter(item => componentSubType?.includes(item.value)) as DefaultOptionType[]}
+                    />
+                </Form.Item></Col>
+                {/* <Col><Form.Item name={'isDeleted'}>
+                    <Select
+                        showSearch
+                        placeholder="已删除?"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                        }
+                        style={{ width: 120 }}
+                        allowClear
+                        options={[
+                            { label: '已删除', value: true },
+                            { label: '未删除', value: false }
+                        ]}
+                    />
+                </Form.Item></Col> */}
+                <Col><Form.Item name={'potentialStatus'}>
+                    <Select
+                        showSearch
+                        placeholder="组件潜力"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                        }
+                        style={{ minWidth: 120 }}
+                        maxTagCount={1}
+                        mode="multiple"
+                        options={Object.keys(COMMON_POTENTIAL_STATUS_ENUM).map(key => ({ label: COMMON_POTENTIAL_STATUS_ENUM[key as keyof typeof COMMON_POTENTIAL_STATUS_ENUM], value: key }))}
+                    />
+                </Form.Item></Col>
+                <Col><Form.Item name={'generationType'}>
+                    <Select
+                        showSearch
+                        placeholder="来源"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                        }
+                        style={{ minWidth: 120 }}
+                        maxTagCount={1}
+                        mode="multiple"
+                        options={Object.keys(COMPONENT_GENERATION_TYPE_ENUM).map(key => ({ label: COMPONENT_GENERATION_TYPE_ENUM[key as keyof typeof COMPONENT_GENERATION_TYPE_ENUM], value: key }))}
+                    />
+                </Form.Item></Col>
+                <Col><Form.Item name={'createTime'}>
+                    <DatePicker.RangePicker style={{ width: 260 }} placeholder={['创建时间开始', '创建时间结束']} />
+                </Form.Item></Col>
+                <Col><Form.Item>
+                    <Space>
+                        <Button onClick={() => form.resetFields()}>重置</Button>
+                        <Button type="primary" htmlType="submit" icon={<SearchOutlined />} className="modalResetCss">搜索</Button>
+                    </Space>
+                </Form.Item></Col>
+            </Row>
+        </Form>
+    </Card>
+}
+
+export default React.memo(SelectComponentsUnitSearch)

+ 22 - 20
src/pages/launchSystemV3/material/cloudNew/selectGroupCloudNew.tsx

@@ -185,28 +185,30 @@ const SelectGroupCloudNew: React.FC<CLOUDNEW.SelectGroupCloudNewProps> = ({ num,
                                 <div className={style.left_bts}>
                                     <Checkbox
                                         onChange={(e) => {
-                                            let newCheckedFolderList: any[] = JSON.parse(JSON.stringify(checkedFolderList))
-                                            const data: any[] = getPageRemoteImageDataList?.data?.records
-
-                                            if (e.target.checked) { // 全选
-                                                const selectIds = newCheckedFolderList.map(item => item.id)
-                                                const remainData = data.filter(item => {
-                                                    const id = item.image_id || item.video_id
-                                                    return !selectIds.includes(id)
-                                                })
-                                                const remainDataLength = remainData.length
-                                                const remainNum = num - newCheckedFolderList.length
-                                                if (remainNum > remainDataLength) {
-                                                    newCheckedFolderList.push(...remainData.map(i => ({ ...i, material_type: defaultParams.materialType, id: i.image_id || i.video_id, oss_url: i.preview_url, materialType: 1 })))
-                                                } else {
-                                                    newCheckedFolderList.push(...remainData.splice(0, remainNum).map(i => ({ ...i, material_type: defaultParams.materialType, id: i.image_id || i.video_id, oss_url: i.preview_url, materialType: 1 })))
+                                            if (getPageRemoteImageDataList?.data?.records) {
+                                                let newCheckedFolderList: any[] = JSON.parse(JSON.stringify(checkedFolderList))
+                                                const data: any[] = getPageRemoteImageDataList?.data?.records
+    
+                                                if (e.target.checked) { // 全选
+                                                    const selectIds = newCheckedFolderList.map(item => item.id)
+                                                    const remainData = data.filter(item => {
+                                                        const id = item.image_id || item.video_id
+                                                        return !selectIds.includes(id)
+                                                    })
+                                                    const remainDataLength = remainData.length
+                                                    const remainNum = num - newCheckedFolderList.length
+                                                    if (remainNum > remainDataLength) {
+                                                        newCheckedFolderList.push(...remainData.map(i => ({ ...i, material_type: defaultParams.materialType, id: i.image_id || i.video_id, oss_url: i.preview_url, materialType: 1 })))
+                                                    } else {
+                                                        newCheckedFolderList.push(...remainData.splice(0, remainNum).map(i => ({ ...i, material_type: defaultParams.materialType, id: i.image_id || i.video_id, oss_url: i.preview_url, materialType: 1 })))
+                                                    }
+                                                    newCheckedFolderList = Array.from(new Set(newCheckedFolderList.map(item => JSON.stringify(item)))).map(item => JSON.parse(item));
+                                                } else { // 取消全选
+                                                    const dataIds = data.map(item => item.image_id || item.video_id)
+                                                    newCheckedFolderList = newCheckedFolderList.filter(i => !dataIds.includes(i.id))
                                                 }
-                                                newCheckedFolderList = Array.from(new Set(newCheckedFolderList.map(item => JSON.stringify(item)))).map(item => JSON.parse(item));
-                                            } else { // 取消全选
-                                                const dataIds = data.map(item => item.image_id || item.video_id)
-                                                newCheckedFolderList = newCheckedFolderList.filter(i => !dataIds.includes(i.id))
+                                                setCheckedFolderList(newCheckedFolderList)
                                             }
-                                            setCheckedFolderList(newCheckedFolderList)
                                         }}
                                         disabled={checkedFolderList?.length >= num || !!active || active === 0 || isShowAll}
                                         indeterminate={indeterminateFolder}

+ 21 - 10
src/pages/launchSystemV3/material/cloudNew/uploadsTable.tsx

@@ -123,7 +123,7 @@ const UploadsTable: React.FC<Props> = ({ isPermission, fileList, visible, onClos
                     if (bole?.size > fileSize) {
                         bole = await compressAccurately(file, fileSize / 1024 - 200)
                     }
-                    let newFile = await blobToBase64(bole)
+                    const newFile = await blobToBase64(bole)
                     file = await dataURLtoFile(newFile, file?.name)
                 }
             } else if (file.type.startsWith('video/')) {
@@ -136,18 +136,29 @@ const UploadsTable: React.FC<Props> = ({ isPermission, fileList, visible, onClos
             let height = 0
             let videoDuration = 0
             if (file.type.startsWith('image/')) {
-                let imgData = await getImgSize(file as any)
+                const imgData = await getImgSize(file as any)
                 width = imgData.width
                 height = imgData.height
             } else if (file.type.startsWith('video/')) {
-                let videoInfo: { width: number, height: number, videoLength: number }[] = await videoMessage([file as any])
-                width = videoInfo[0].width
-                height = videoInfo[0].height
-                videoDuration = videoInfo[0].videoLength
+                const videoInfo: { width: number, height: number, videoLength: number }[] = await videoMessage([file as any])
+                const readChunk = async (chunkSize: number, offset: number) => new Uint8Array(await file.slice(offset, offset + chunkSize).arrayBuffer());
+                // @ts-ignore
+                const mediaInfo = await MediaInfo.mediaInfoFactory({ format: 'object' })
+                const result = await mediaInfo.analyzeData(file.size, readChunk)
+                if (result?.media?.track) {
+                    const { Height, Width, Duration } = result?.media?.track.find((item: { [x: string]: string }) => item['@type'] === 'Video')
+                    width = Width
+                    height = Height
+                    videoDuration = Duration
+                } else {
+                    width = videoInfo[0].width
+                    height = videoInfo[0].height
+                    videoDuration = videoInfo[0].videoLength
+                }
             }
             /**修改文件名以用户设置的文件title命名*/
-            let newFile = new File([file], file?.name, { type: file?.type })
-            let formData = new FormData();
+            const newFile = new File([file], file?.name, { type: file?.type })
+            const formData = new FormData();
             /**向阿里云请求上传地址*/
             getFileUrlAjx.run({ type: newFile.type, fileType: file.type.startsWith('video/') ? 'video' : 'image' }).then(res1 => {
                 Object.keys(res1).forEach((key: string) => {
@@ -161,8 +172,8 @@ const UploadsTable: React.FC<Props> = ({ isPermission, fileList, visible, onClos
                     if (res2.code === 200) {
                         /**取到返回的文件地址向后端发送具体数据*/
                         if (res2?.data?.url) {
-                            let fileMd5 = await getMD5(newFile)
-                            let obj: CLOUDNEW.AddMaterialProps = { ...queryForm, materialType: file.type.startsWith('video/') ? 'video' : 'image', width, height, md5: fileMd5, ossUrl: res2?.data?.url, fileSize: newFile?.size, fileMime: newFile.type, aspectRatio: width / height }
+                            const fileMd5 = await getMD5(newFile)
+                            const obj: CLOUDNEW.AddMaterialProps = { ...queryForm, materialType: file.type.startsWith('video/') ? 'video' : 'image', width, height, md5: fileMd5, ossUrl: res2?.data?.url, fileSize: newFile?.size, fileMime: newFile.type, aspectRatio: width / height }
                             delete obj?.file
                             if (!obj?.folderId) obj.folderId = folderId
                             if (!obj?.designerId) obj.designerId = userId;

+ 11 - 0
src/pages/launchSystemV3/material/typings.d.ts

@@ -157,6 +157,17 @@ declare namespace CLOUDNEW {
         onChange?: (data: any[]) => void
         accountCreateLogs?: PULLIN.AccountCreateLogsProps[]
         putInType?: 'NOVEL' | 'GAME'
+        componentCount?: number
+        deliveryMode?: string
+    }
+    interface MediaListProps {
+        defaultParams: DefaultParams
+        num: number,
+        active: number | undefined
+        checkedFolderList: any[]
+        setCheckedFolderList: React.Dispatch<React.SetStateAction<any[]>>
+        uploadVisible: boolean
+        setUploadVisible: React.Dispatch<React.SetStateAction<boolean>>
     }
     interface SelectGroupCloudNewProps {
         num: number

+ 277 - 29
src/pages/launchSystemV3/tencenTasset/manageComponent/addComponents.tsx

@@ -1,8 +1,18 @@
-import React from 'react';
+import React, { useState } from 'react';
 import '../../tencentAdPutIn/index.less'
-import { Button, Form, message, Modal, Space } from 'antd';
+import { Button, Card, Dropdown, Empty, Form, message, Modal, Space, Table } from 'antd';
+import { CloseCircleOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons';
+import { DEFAULT_COMPONENT_SUB_IMAGE_TYPE, DEFAULT_COMPONENT_SUB_VIDEO_TYPE, IComponent } from './const';
+import style from './index.less'
+import SelectCloudComponent from '../../material/cloudNew/selectCloudComponent';
+import VideoNews from '@/pages/launchSystemNew/components/newsModal/videoNews';
+import { useAjax } from '@/Hook/useAjax';
+import { addBatchCreativeComponentApi, CreativeComponentDetailDTOS } from '@/services/adqV3/global';
 
 interface Props {
+    type: 'IMAGE' | 'VIDEO'
+    putInType: 'NOVEL' | 'GAME',
+    accountId: number;
     visible?: boolean;
     onClose?: () => void;
     onChange?: () => void;
@@ -13,39 +23,125 @@ interface Props {
  * @param param0 
  * @returns 
  */
-const AddComponents: React.FC<Props> = ({ visible, onChange, onClose }) => {
+const AddComponents: React.FC<Props> = ({ type, putInType, accountId, visible, onChange, onClose }) => {
 
     /****************************************/
     const [form] = Form.useForm();
-    /****************************************/
+    const componentsGroup = Form.useWatch('componentsGroup', form)
+
+    const [selectCloudData, setSelectCloudData] = useState<{
+        defaultParams: {
+            sizeQueries?: {
+                width: number,
+                height: number,
+                relation: string
+            }[],
+            materialType: 'image' | 'video'
+            fileSize: number
+        }
+        num: number
+    }>()
+    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)
 
-    // <Dropdown menu={{
-    //     items: [
-    //         {
-    //             key: '1',
-    //             label: (
-    //                 <a target="_blank" rel="noopener noreferrer" href="https://www.antgroup.com">
-    //                     1st menu item
-    //                 </a>
-    //             ),
-    //         }
-    //     ]
-    // }}>
-    //     <Button icon={<PlusOutlined />} type="primary">新建组件</Button>
-    // </Dropdown>
+    const addBatchCreativeComponent = useAjax((params) => addBatchCreativeComponentApi(params))
+    /****************************************/
 
     const handleOk = (values: any) => {
-        // const { mediaType, dynamicGroup } = values
-        // if (mediaType === 2 && dynamicGroup.length > adLength) {
-        //     message.error({
-        //         content: `创意组分配规则选择“顺序分配到广告”时,创意组总数必须小于等于广告总数。当前广告总数:${adLength},创意组总数:${dynamicGroup.length}`,
-        //         duration: 8
-        //     })
-        //     return
-        // }
-        // onChange?.({ mediaType, dynamicMaterialDTos: { dynamicGroup } })
+        if (values?.componentsGroup?.length) {
+            const params = values.componentsGroup.reduce((pre: CreativeComponentDetailDTOS[], cur: { componentSubType: string; list: any[] }) => {
+                pre.push(...cur?.list?.map((item) => {
+                    if (cur?.componentSubType?.includes('IMAGE_LIST')) {
+                        return {
+                            componentSubType: cur.componentSubType,
+                            materialIdList: item.map((i: { id: any; }) => i.id),
+                            componentType: 'imageList'
+                        }
+                    } else if (cur?.componentSubType?.includes('VIDEO')) {
+                        return {
+                            componentSubType: cur.componentSubType,
+                            materialId: item.id,
+                            componentType: 'video',
+                            coverUrl: item.key_frame_image_url
+                        }
+                    }
+                    return {
+                        componentSubType: cur.componentSubType,
+                        materialId: item.id,
+                        componentType: 'image'
+                    }
+                }))
+                return pre
+            }, [])
+            console.log(params);
+            addBatchCreativeComponent.run({
+                adAccountId: accountId,
+                creativeComponentDetailDTOS: params
+            }).then((res) => {
+                if (res?.length === 0) {
+                    message.success('创建成功')
+                    onChange?.()
+                } else {
+                    console.log(res);
+                    Modal.confirm({
+                        okText: '关闭',
+                        cancelText: '继续',
+                        className: 'modalResetCss',
+                        icon: false,
+                        width: 750,
+                        content: <Table
+                            dataSource={res?.map((text: string, index: number) => ({ des: text, id: index + 1 }))}
+                            columns={[
+                                {
+                                    title: '新增详情',
+                                    dataIndex: 'des',
+                                    key: 'des',
+                                    ellipsis: true
+                                }
+                            ]}
+                            size="small"
+                            bordered
+                            rowKey={'id'}
+                        />,
+                        onOk() {
+                            onChange?.()
+                        },
+                        onCancel() {}
+                    });
+                }
+            })
+        }
+    }
+
+    /** 删除单个 */
+    const clearTem = (index: number, count: number) => {
+        const newComponentsGroup = componentsGroup?.map((item: any, i: number) => {
+            if (i === index) {
+                const oldList = item?.list?.filter((_: any, li: number) => count !== li)
+                return { ...item, list: oldList }
+            }
+            return item
+        })
+        form.setFieldsValue({ componentsGroup: newComponentsGroup })
     }
 
+    const componentsGroupSubTypeList = componentsGroup?.map((item: { componentSubType: any; }) => item.componentSubType) || []
     return <Modal
         title={<strong style={{ fontSize: 20 }}>新建组件</strong>}
         open={visible}
@@ -72,18 +168,170 @@ const AddComponents: React.FC<Props> = ({ visible, onChange, onClose }) => {
             }}
             onFinish={handleOk}
             initialValues={{
-                dynamicGroup: [undefined]
+                // componentsGroup: [{ componentSubType: 'IMAGE_16X9' }, { componentSubType: 'IMAGE_LIST_1X1_3' }]
             }}
         >
+            <Form.List name="componentsGroup">
+                {(fields, { add, remove }) => (<>
+                    <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10 }}>
+                        {fields.map((field, num) => {
+                            const componentData = (type === 'IMAGE' ? DEFAULT_COMPONENT_SUB_IMAGE_TYPE : DEFAULT_COMPONENT_SUB_VIDEO_TYPE).find(item => item.value === componentsGroup?.[num]?.componentSubType) as IComponent
+                            const title = componentData?.label
+                            return <Card
+                                title={<Space>
+                                    <strong>{title || '组件'} 组件</strong>
+                                    <Button
+                                        type="link"
+                                        disabled={componentsGroup?.[num]?.['list']?.length >= 200}
+                                        onClick={() => {
+                                            let count = 200 - (componentsGroup?.[num]?.['list']?.length || 0)
+                                            let isGroup = false
+                                            if (componentData?.value?.includes('IMAGE_LIST')) {
+                                                count = componentData?.restriction.minNumber || count
+                                                isGroup = true
+                                            }
+                                            setSelectCloudData({
+                                                num: count,
+                                                defaultParams: {
+                                                    materialType: type === 'IMAGE' ? 'image' : 'video',
+                                                    sizeQueries: [
+                                                        { relation: '=', width: componentData?.restriction.width || 0, height: componentData?.restriction.height || 0 }
+                                                    ],
+                                                    fileSize: (componentData?.restriction.fileSize || 0) * 1024
+                                                }
+                                            })
+                                            setMaterialConfig({
+                                                ...materialConfig,
+                                                type: type === 'IMAGE' ? 'image' : 'video',
+                                                max: count,
+                                                index: num,
+                                                isGroup
+                                            })
+                                            setTimeout(() => {
+                                                setSelectVideoVisible(true)
+                                            }, 100)
+                                        }}
+                                    >添加</Button>
+                                </Space>}
+                                className="cardResetCss"
+                                key={field.key}
+                                style={{ width: '100%' }}
+                                extra={fields?.length > 1 && <DeleteOutlined className={style.clear} onClick={() => remove(field.name)} style={{ color: 'red' }} />}
+                            >
+                                <Form.Item
+                                    {...field}
+                                    label={<strong>素材</strong>}
+                                    rules={[{ required: true, message: '请选择素材!' }]}
+                                    name={[field.name, 'list']}
+                                >
+                                    <div className={`${style.box} ${style.video}`} style={{ width: '100%', height: 'auto', backgroundColor: 'rgb(247, 249, 252)' }}>
+                                        {componentsGroup?.length && componentsGroup?.[num]?.['list']?.length > 0 ? <div className={style.boxList}>
+                                            <div className={style.boxList_title}>
+                                                <span>上传素材 <span style={{ marginLeft: 5, color: '#999', fontWeight: 'normal' }}>已选:{componentsGroup?.[num]?.['list']?.length || 0} 个素材</span></span>
+                                                <a onClick={() => {
+                                                    const newComponentsGroup = componentsGroup?.map((item: any, i: number) => {
+                                                        if (i === num) {
+                                                            return { ...item, list: [] }
+                                                        }
+                                                        return item
+                                                    })
+                                                    form.setFieldsValue({ componentsGroup: newComponentsGroup })
+                                                }}>清空</a>
+                                            </div>
+                                            <div className={style.boxList_body}>
+                                                {componentsGroup?.[num]?.['list']?.map((item: any, index: number) => {
+                                                    if (componentsGroup?.[num]?.componentSubType?.includes('IMAGE_LIST')) {
+                                                        let length = item.length
+                                                        return <div className={style.boxList_body_item} key={index}>
+                                                            <div className={style.tag}>{length}图</div>
+                                                            <div className={style.content}>
+                                                                {item?.map((l: { oss_url: string | undefined; }, i: number) => <img src={l?.oss_url} key={i} style={{ width: length >= 6 ? 33.3 : 49.9 }} />)}
+                                                            </div>
+                                                            <div className={style.clear} onClick={() => { clearTem(num, index) }}><CloseCircleOutlined /></div>
+                                                        </div>
+                                                    } else if (componentsGroup?.[num]?.componentSubType?.includes('VIDEO')) {
+                                                        return <div className={style.boxList_body_item} key={index}>
+                                                            <div className={style.content}>
+                                                                <VideoNews src={item?.oss_url} keyFrameImageUrl={item?.key_frame_image_url} style={{ width: 100, height: 100 }} maskBodyStyle={{ backgroundColor: "rgba(242, 246, 254, 0.1)" }} />
+                                                            </div>
+                                                            <div className={style.clear} onClick={() => { clearTem(num, index) }}><CloseCircleOutlined /></div>
+                                                        </div>
+                                                    }
+                                                    return <div className={style.boxList_body_item} key={index}>
+                                                        <div className={style.content}><img src={item?.oss_url} /></div>
+                                                        <div className={style.clear} onClick={() => { clearTem(num, index) }}><CloseCircleOutlined /></div>
+                                                    </div>
+                                                })}
+                                            </div>
+                                        </div> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无素材内容,可通过上方添加" />}
+                                    </div>
+                                </Form.Item>
+                            </Card>
+                        })}
+                    </div>
+                    <Form.Item style={{ marginBottom: 0, marginTop: 10 }}>
+                        <Dropdown
+                            menu={{
+                                items: (type === 'IMAGE' ? DEFAULT_COMPONENT_SUB_IMAGE_TYPE : DEFAULT_COMPONENT_SUB_VIDEO_TYPE).map(item => ({ label: item.label, key: item.value, disabled: componentsGroupSubTypeList.includes(item.value) })),
+                                onClick: (e) => {
+                                    setTimeout(() => add({ componentSubType: e.key }), 100)
+                                },
+                                style: { maxHeight: 260, overflowY: 'auto' }
+                            }}
+                            overlayStyle={{ minWidth: 350 }}
+                            trigger={['click']}
+                            placement='top'
+                        >
+                            <Button type="dashed" style={{ color: '#1890ff' }} block icon={<PlusOutlined />}>添加组件组</Button>
+                        </Dropdown>
+                    </Form.Item>
+                </>)}
+            </Form.List>
+
+            {(componentsGroup?.length === 0 || !componentsGroup) && <Card className="cardResetCss" style={{ marginTop: 10 }}><Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无组内容,可通过上方添加" /></Card>}
             <Form.Item className="submit_pull">
                 <Space>
                     <Button onClick={onClose}>取消</Button>
-                    <Button type="primary" htmlType="submit" className="modalResetCss">
+                    <Button type="primary" htmlType="submit" className="modalResetCss" loading={addBatchCreativeComponent.loading}>
                         确定
                     </Button>
                 </Space>
             </Form.Item>
         </Form>
+
+        {/* 选择素材 */}
+        {(selectVideoVisible && selectCloudData) && <SelectCloudComponent
+            {...selectCloudData}
+            accountCreateLogs={[{ accountId: accountId }]}
+            visible={selectVideoVisible}
+            isGroup={materialConfig?.isGroup}
+            onClose={() => {
+                setSelectVideoVisible(false)
+                setSelectCloudData(undefined)
+            }}
+            putInType={putInType}
+            onChange={(content: any) => {
+                console.log(content)
+                if (content.length > 0) {
+                    const newComponentsGroup = componentsGroup?.map((item: any, index: number) => {
+                        if (materialConfig.index === index) {
+                            let oldList = item?.list || []
+                            if (materialConfig.isGroup) {
+                                oldList = oldList.concat([content])
+                                return { ...item, list: oldList }
+                            } else {
+                                oldList = oldList.concat(content)
+                                return { ...item, list: oldList }
+                            }
+                        }
+                        return item
+                    })
+                    form.setFieldsValue({ componentsGroup: newComponentsGroup })
+                }
+                setSelectVideoVisible(false)
+                setSelectCloudData(undefined)
+            }}
+        />}
     </Modal>
 };
 

+ 216 - 28
src/pages/launchSystemV3/tencenTasset/manageComponent/const.ts

@@ -1,88 +1,259 @@
+export interface IComponent {
+    label: string;
+    value: string;
+    restriction: {
+        width: number;
+        height: number;
+        fileSize: number;
+        maxNumber: number;
+        minNumber: number;
+        name: string;
+    };
+}
 /** 二级视频组件类型 */
 export const DEFAULT_COMPONENT_SUB_VIDEO_TYPE = [
     {
         label: '16:9 视频',
-        value: 'VIDEO_16X9'
+        value: 'VIDEO_16X9',
+        restriction: {
+            width: 1280,
+            height: 720,
+            fileSize: 512000,
+            maxNumber: 1,
+            minNumber: 1,
+            maxDuration: 300000,
+            name: 'video'
+        }
     },
     {
         label: '9:16 视频',
-        value: 'VIDEO_9X16'
+        value: 'VIDEO_9X16',
+        restriction: {
+            width: 720,
+            height: 1280,
+            fileSize: 512000,
+            maxNumber: 1,
+            minNumber: 1,
+            maxDuration: 300000,
+            name: 'video'
+        }
     },
     {
         label: '4:3 视频',
-        value: 'VIDEO_4X3'
-    },
-    {
-        label: '橱窗视频',
-        value: 'VIDEO_SHOWCASE'
-    },
-    {
-        label: '橱窗视频',
-        value: 'SHORT_VIDEO_4X3'
-    }
+        value: 'VIDEO_4X3',
+        restriction: {
+            width: 1280,
+            height: 960,
+            fileSize: 512000,
+            maxNumber: 1,
+            minNumber: 1,
+            maxDuration: 300000,
+            name: 'video'
+        }
+    },
+    // {
+    //     label: '橱窗视频',
+    //     value: 'VIDEO_SHOWCASE'
+    // },
+    // {
+    //     label: '橱窗视频',
+    //     value: 'SHORT_VIDEO_4X3'
+    // }
 ]
 
 /** 二级图片组件类型 */
 export const DEFAULT_COMPONENT_SUB_IMAGE_TYPE = [
     {
         label: '16:9单图',
-        value: 'IMAGE_16X9'
+        value: 'IMAGE_16X9',
+        restriction: {
+            width: 1280,
+            height: 720,
+            fileSize: 400,
+            maxNumber: 1,
+            minNumber: 1,
+            name: 'image'
+        }
     },
     {
         label: '9:16单图',
-        value: 'IMAGE_9X16'
+        value: 'IMAGE_9X16',
+        restriction: {
+            width: 1080,
+            height: 1920,
+            fileSize: 400,
+            maxNumber: 1,
+            minNumber: 1,
+            name: 'image'
+        }
     },
     {
         label: '1:1单图',
-        value: 'IMAGE_1X1'
+        value: 'IMAGE_1X1',
+        restriction: {
+            width: 800,
+            height: 800,
+            fileSize: 400,
+            maxNumber: 1,
+            minNumber: 1,
+            name: 'image'
+        }
     },
     {
         label: '20:7banner图',
-        value: 'IMAGE_20X7'
+        value: 'IMAGE_20X7',
+        restriction: {
+            width: 960,
+            height: 334,
+            fileSize: 400,
+            maxNumber: 1,
+            minNumber: 1,
+            name: 'image'
+        }
     },
     {
         label: '3:4单图',
-        value: 'IMAGE_3X4'
+        value: 'IMAGE_3X4',
+        restriction: {
+            width: 960,
+            height: 1280,
+            fileSize: 400,
+            maxNumber: 1,
+            minNumber: 1,
+            name: 'image'
+        }
     },
     {
         label: '4:3单图',
-        value: 'IMAGE_4X3'
+        value: 'IMAGE_4X3',
+        restriction: {
+            width: 1280,
+            height: 960,
+            fileSize: 400,
+            maxNumber: 1,
+            minNumber: 1,
+            name: 'image'
+        }
     },
     {
         label: '3:2单图',
-        value: 'IMAGE_3X2'
+        value: 'IMAGE_3X2',
+        restriction: {
+            width: 480,
+            height: 320,
+            fileSize: 400,
+            maxNumber: 1,
+            minNumber: 1,
+            name: 'image'
+        }
     },
     {
         label: '7:2通栏大图',
-        value: 'IMAGE_7X2'
+        value: 'IMAGE_7X2',
+        restriction: {
+            width: 960,
+            height: 274,
+            fileSize: 400,
+            maxNumber: 1,
+            minNumber: 1,
+            name: 'image'
+        }
     },
     {
         label: '5:4单图',
-        value: 'IMAGE_5X4'
+        value: 'IMAGE_5X4',
+        restriction: {
+            width: 1280,
+            height: 1024,
+            fileSize: 400,
+            maxNumber: 1,
+            minNumber: 1,
+            name: 'image'
+        }
     },
     {
         label: '4:5单图',
-        value: 'IMAGE_4X5'
+        value: 'IMAGE_4X5',
+        restriction: {
+            width: 1024,
+            height: 1280,
+            fileSize: 400,
+            maxNumber: 1,
+            minNumber: 1,
+            name: 'image'
+        }
+    },
+    {
+        label: '1:1一图',
+        value: 'IMAGE_LIST_1X1_1',
+        restriction: {
+            width: 800,
+            height: 800,
+            fileSize: 400,
+            maxNumber: 1,
+            minNumber: 1,
+            name: 'image_list'
+        }
     },
     {
         label: '1:1三图',
-        value: 'IMAGE_LIST_1X1_3'
+        value: 'IMAGE_LIST_1X1_3',
+        restriction: {
+            width: 800,
+            height: 800,
+            fileSize: 400,
+            maxNumber: 3,
+            minNumber: 3,
+            name: 'image_list'
+        }
     },
     {
         label: '1:1四图',
-        value: 'IMAGE_LIST_1X1_4'
+        value: 'IMAGE_LIST_1X1_4',
+        restriction: {
+            width: 800,
+            height: 800,
+            fileSize: 400,
+            maxNumber: 4,
+            minNumber: 4,
+            name: 'image_list'
+        }
     },
     {
         label: '1:1六图',
-        value: 'IMAGE_LIST_1X1_6'
+        value: 'IMAGE_LIST_1X1_6',
+        restriction: {
+            width: 800,
+            height: 800,
+            fileSize: 400,
+            maxNumber: 6,
+            minNumber: 6,
+            name: 'image_list'
+        }
     },
     {
         label: '1:1九图',
-        value: 'IMAGE_LIST_1X1_9'
+        value: 'IMAGE_LIST_1X1_9',
+        restriction: {
+            width: 800,
+            height: 800,
+            fileSize: 400,
+            maxNumber: 9,
+            minNumber: 9,
+            name: 'image_list'
+        }
     },
     {
         label: '3:2三图',
-        value: 'IMAGE_LIST_3X2_3'
+        value: 'IMAGE_LIST_3X2_3',
+        restriction: {
+            width: 480,
+            height: 320,
+            fileSize: 400,
+            maxNumber: 3,
+            minNumber: 3,
+            name: 'image_list'
+        }
     }
 ]
 
@@ -423,4 +594,21 @@ export const COMPONENT_SUB_TYPE = [
         "label": "QQ 小游戏",
         "value": "JUMP_INFO_QQ_MINI_GAME"
     }
-]
+]
+
+/**
+ * 判断组件类型
+ * @param componentSubType 
+ * @returns 
+ */
+export const getComponentType = (componentSubType: string) => {
+    if (['IMAGE_16X9', 'IMAGE_1X1', 'IMAGE_9X16', 'IMAGE_20X7', 'IMAGE_3X4', 'IMAGE_4X3', 'IMAGE_3X2', 'IMAGE_7X2', 'IMAGE_5X4', 'IMAGE_4X5'].includes(componentSubType)) {
+        return 'IMAGE'
+    } else if (componentSubType.includes('VIDEO')) {
+        return 'VIDEO'
+    } else if (['IMAGE_LIST_1X1_9', 'IMAGE_LIST_1X1_3', 'IMAGE_LIST_1X1_4', 'IMAGE_LIST_1X1_6', 'IMAGE_LIST_3X2_3', 'IMAGE_LIST_1X1_1'].includes(componentSubType)) {
+        return 'IMAGE_LIST'
+    } else {
+        return 'OTHER'
+    }
+}

+ 124 - 0
src/pages/launchSystemV3/tencenTasset/manageComponent/index.less

@@ -42,4 +42,128 @@
         padding: 10px 16px;
         border-top: 1px solid #e8e8e8;
     }
+}
+
+.clear {
+    color: red;
+}
+
+
+.box {
+    width: 60%;
+    height: 200px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background-color: #f5f7fa;
+    flex-direction: column;
+    color: rgba(0, 0, 0, .3);
+    border-radius: 6px;
+
+    /* TWEENER - IE 10 */
+    >p,
+    .p {
+        display: flex;
+        align-items: center;
+        flex-flow: column;
+        font-size: 10px;
+        cursor: pointer;
+        max-height: 150px;
+        margin-bottom: 0;
+        max-width: 100%;
+
+        img {
+            max-height: 99%;
+            max-width: 99%;
+        }
+
+        video {
+            max-height: 99%;
+            max-width: 99%;
+        }
+    }
+
+    >.p>div {
+        height: 100%;
+
+        >div {
+            height: 100%;
+        }
+    }
+}
+
+
+.boxList {
+    min-height: 150px;
+    padding: 16px;
+    box-sizing: border-box;
+    width: 100%;
+    border: 1px solid #e6e8ed;
+    border-radius: 6px;
+}
+
+.boxList_title {
+    display: flex;
+    justify-content: space-between;
+    font-size: 12px;
+    margin-bottom: 10px;
+
+    >span {
+        font-weight: bold;
+        color: #000;
+    }
+}
+
+.boxList_body {
+    display: grid;
+    grid-template-columns: repeat(7, 1fr);
+    gap: 15px;
+}
+
+.boxList_body_item {
+    border: 1px solid #e6e8ed;
+    border-radius: 6px;
+    background-color: rgb(242, 246, 254);
+    position: relative;
+
+    .content {
+        width: 100px;
+        height: 100px;
+        overflow: hidden;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        flex-wrap: wrap;
+        border-radius: 6px;
+
+        >img {
+            max-width: 100%;
+            max-height: 100%;
+        }
+    }
+
+    .tag {
+        position: absolute;
+        top: 4px;
+        left: 4px;
+        padding: 0 3px;
+        border-radius: 3px;
+        background: rgba(255, 255, 255, 0.8);
+        color: rgb(98, 99, 102);
+        font-size: 12px;
+    }
+
+    .clear {
+        position: absolute;
+        top: -9px;
+        right: -6px;
+        color: red;
+        cursor: pointer;
+        opacity: 0;
+        transition: opacity ease-out .1s;
+    }
+
+    &:hover .clear {
+        opacity: 1;
+    }
 }

+ 68 - 12
src/pages/launchSystemV3/tencenTasset/manageComponent/index.tsx

@@ -1,17 +1,17 @@
 import React, { useEffect, useRef, useState } from "react"
 import '../../tencentAdPutIn/index.less'
-import { Button, Card, DatePicker, Dropdown, Input, Pagination, Select, Space, Spin, Table, Tabs } from "antd"
-import { getCreativeComponentListApi, GetCreativeComponentProps } from "@/services/adqV3/global"
+import { Button, Card, DatePicker, Input, Pagination, Select, Space, Switch, Table, Tabs } from "antd"
+import { getCreativeComponentListApi, GetCreativeComponentProps, getDefaultSharingApi, updateDefaultSharingApi } from "@/services/adqV3/global"
 import './index.less'
-import { PlusOutlined } from "@ant-design/icons"
+import { PlusOutlined, SyncOutlined } from "@ant-design/icons"
 import { useAjax } from "@/Hook/useAjax"
 import SelectAdAccount from "@/components/SelectAdAccount"
 import { COMMON_POTENTIAL_STATUS_ENUM, COMPONENT_GENERATION_TYPE_ENUM, DEFAULT_COMPONENT_SUB_IMAGE_TYPE, DEFAULT_COMPONENT_SUB_SHOW_IMAGE, DEFAULT_COMPONENT_SUB_SHOW_VIDEO, DEFAULT_COMPONENT_SUB_VIDEO_TYPE } from "./const"
 import moment from "moment"
 import { useDebounce, useSize } from "ahooks"
 import TableConfig from "./tableConfig"
-const COMPONENT_SUB_IMAGE_ENUM = DEFAULT_COMPONENT_SUB_IMAGE_TYPE.map(item => item.value)
-const COMPONENT_SUB_VIDEO_ENUM = DEFAULT_COMPONENT_SUB_VIDEO_TYPE.map(item => item.value)
+import AddComponents from "./addComponents"
+import { DefaultOptionType } from "antd/lib/select"
 
 /**
  * 创意组件
@@ -25,8 +25,15 @@ const ManageComponent: React.FC = () => {
     const debouncedIdSting = useDebounce(idSting, { wait: 500 });
     const ref = useRef<HTMLDivElement>(null);
     const size = useSize(ref);
+    const [addVisible, setAddVisible] = useState<boolean>(false)
+    const [loading, setLoading] = useState<boolean>(true)
+    const [adUnitAccount, setAdUnitAccount] = useState<number>()
+    const [shared, setShared] = useState<'DEFAULT_SHARED_NONE' | 'DEFAULT_SHARED_ALL_ORGANIZATION'>()
+    const [putInType, setPutInType] = useState<'NOVEL' | 'GAME'>('NOVEL')
 
     const getCreativeComponentList = useAjax((params) => getCreativeComponentListApi(params))
+    const getDefaultSharing = useAjax((params) => getDefaultSharingApi(params))
+    const updateDefaultSharing = useAjax((params) => updateDefaultSharingApi(params))
     /*************************************/
 
     useEffect(() => {
@@ -37,16 +44,34 @@ const ManageComponent: React.FC = () => {
             } else {
                 delete params?.[activeKey === 'IMAGE' ? 'imageId' : 'videoId']
             }
-            getCreativeComponentList.run({ ...params, componentSubType: componentSubType || (activeKey === 'IMAGE' ? DEFAULT_COMPONENT_SUB_SHOW_IMAGE : DEFAULT_COMPONENT_SUB_SHOW_VIDEO) })
+            getCreativeComponentList.run({ ...params, componentSubType: componentSubType && componentSubType?.length > 0 ? componentSubType : (activeKey === 'IMAGE' ? DEFAULT_COMPONENT_SUB_SHOW_IMAGE : DEFAULT_COMPONENT_SUB_SHOW_VIDEO) })
         }
     }, [queryParams, debouncedIdSting])
 
+    const handleGet = (adAccountId: number) => {
+        getDefaultSharing.run({ adAccountId }).then(res => {
+            if (res) {
+                setShared(res)
+            } else {
+                setShared(undefined)
+            }
+        }).catch(() => setShared(undefined))
+    }
+
+    const handleSet = (e?: boolean) => {
+        updateDefaultSharing.run({ adAccountId: adUnitAccount, type: e ? 'DEFAULT_SHARED_ALL_ORGANIZATION' : 'DEFAULT_SHARED_NONE' }).then(res => {
+            if (res) {
+                handleGet(adUnitAccount as number)
+            }
+        })
+    }
+
     return <div className="manageComponent">
         <Card
             className="cardResetCss"
             bodyStyle={{ paddingBottom: 0, paddingTop: 0 }}
         >
-            <Space size={40}>
+            <Space>
                 <Tabs
                     items={[
                         { label: '图片', key: 'IMAGE', children: false },
@@ -59,13 +84,22 @@ const ManageComponent: React.FC = () => {
                             activeKey: e
                         })
                     }}
-
+                    style={{ marginRight: 40 }}
                 />
                 <SelectAdAccount
                     isReturnFirstValue
                     value={queryParams?.adAccountId ? [queryParams.adAccountId] : undefined}
                     type="radio"
-                    onChange={(value) => {
+                    onChange={(value, data) => {
+                        if (data?.adUnitAccount && value) {
+                            // 是否业务单元账号
+                            setAdUnitAccount(value as number)
+                            handleGet(value as number)
+                        } else {
+                            setAdUnitAccount(undefined)
+                        }
+                        setPutInType(data?.adUnitType || 'NOVEL')
+                        setLoading(false)
                         setQueryParams({
                             ...queryParams,
                             adAccountId: value as number,
@@ -73,6 +107,15 @@ const ManageComponent: React.FC = () => {
                     }}
                     allowClear={false}
                 />
+                <Button icon={<SyncOutlined />} type="link" onClick={() => { getCreativeComponentList.refresh() }} loading={getCreativeComponentList.loading}></Button>
+                {adUnitAccount && <Switch
+                    checkedChildren="业务单元默认全部共享"
+                    unCheckedChildren="业务单元默认不共享"
+                    checked={shared === 'DEFAULT_SHARED_ALL_ORGANIZATION'}
+                    onChange={handleSet}
+                    loading={updateDefaultSharing.loading}
+                />}
+                <span style={{ color: 'red' }}>平台只支持选择业务单元的组件创建广告</span>
             </Space>
         </Card>
         <Card
@@ -88,7 +131,7 @@ const ManageComponent: React.FC = () => {
             }}
         >
             <div className="manageComponent-header">
-                <Button icon={<PlusOutlined />} type="primary">新建组件</Button>
+                <Button icon={<PlusOutlined />} type="primary" onClick={() => { setAddVisible(true) }}>新建组件</Button>
                 <Input.TextArea
                     style={{ width: 160 }}
                     value={idSting}
@@ -111,7 +154,7 @@ const ManageComponent: React.FC = () => {
                     onChange={(e) => {
                         setQueryParams({ ...queryParams, componentSubType: e, pageNum: 1 })
                     }}
-                    options={queryParams?.activeKey === 'IMAGE' ? DEFAULT_COMPONENT_SUB_IMAGE_TYPE : DEFAULT_COMPONENT_SUB_VIDEO_TYPE}
+                    options={(queryParams?.activeKey === 'IMAGE' ? DEFAULT_COMPONENT_SUB_IMAGE_TYPE : DEFAULT_COMPONENT_SUB_VIDEO_TYPE) as DefaultOptionType[]}
                 />
                 <Select
                     showSearch
@@ -170,7 +213,7 @@ const ManageComponent: React.FC = () => {
                 <Table
                     columns={TableConfig(queryParams?.activeKey as any)}
                     dataSource={getCreativeComponentList.data?.records || []}
-                    loading={getCreativeComponentList.loading}
+                    loading={getCreativeComponentList.loading || loading}
                     scroll={{ x: 1000, y: (size?.height || 0) - 36 }}
                     pagination={false}
                     size="small"
@@ -193,6 +236,19 @@ const ManageComponent: React.FC = () => {
                 />
             </div>
         </Card>
+
+        {/* 新建 */}
+        {addVisible && <AddComponents
+            type={queryParams?.activeKey as any}
+            putInType={putInType}
+            accountId={queryParams?.adAccountId as number}
+            visible={addVisible}
+            onChange={() => {
+                setAddVisible(false);
+                getCreativeComponentList.refresh();
+            }}
+            onClose={() => setAddVisible(false)}
+        />}
     </div>
 }
 

+ 19 - 11
src/pages/launchSystemV3/tencenTasset/manageComponent/tableConfig.tsx

@@ -1,9 +1,10 @@
 import { ColumnsType } from "antd/es/table";
 import React from "react";
-import { COMPONENT_GENERATION_TYPE_ENUM, COMPONENT_SUB_TYPE } from "./const";
-import { Popconfirm, Tag } from "antd";
+import { COMPONENT_GENERATION_TYPE_ENUM, COMPONENT_SUB_TYPE, getComponentType } from "./const";
+import { Tag } from "antd";
 import moment from "moment";
 import Image1X1 from "../../components/AdsComponent/Image1X1";
+import ImageXXX from "../../components/AdsComponent/ImageXXX";
 
 const TableConfig = (activeKey: 'IMAGE' | 'VIDEO'): ColumnsType<any> => {
 
@@ -14,10 +15,16 @@ const TableConfig = (activeKey: 'IMAGE' | 'VIDEO'): ColumnsType<any> => {
             key: 'componentSubType',
             width: 380,
             render(value, records) {
-                if (['IMAGE_16X9', 'IMAGE_1X1', 'IMAGE_9X16'].includes(value)) {
+                const type = getComponentType(value)
+                if (type === 'IMAGE') {
                     return <Image1X1 imageUrl={records?.componentValue?.image?.value?.imageUrl} />
-                } else if (['IMAGE_LIST_1X1_9'].includes(value)) {
-                    // records?.componentValue?.imageList?.value?.list imageId imageUrl
+                } else if (type === 'IMAGE_LIST') {
+                    return <ImageXXX imageList={records?.componentValue?.imageList?.value?.list || []} />
+                } else if (type === 'VIDEO') {
+                    return <Image1X1 
+                        imageUrl={records?.componentValue?.video?.value?.coverUrl || records?.componentValue?.shortVideo?.value?.shortVideo2Url}
+                        videoUrl={records?.componentValue?.video?.value?.videoUrl || records?.componentValue?.shortVideo?.value?.shortVideo1Url}
+                    />
                 }
                 return <div className='image-9x16'>联系技术添加</div>
             },
@@ -125,12 +132,13 @@ const TableConfig = (activeKey: 'IMAGE' | 'VIDEO'): ColumnsType<any> => {
             dataIndex: 'cz',
             key: 'cz',
             render(_, record) {
-                return <Popconfirm
-                    title="确定删除?"
-                    onConfirm={() => {}}
-                >
-                    <a style={{ color: 'red', fontSize: 12 }}>删除</a>
-                </Popconfirm>
+                // return <Popconfirm
+                //     title="确定删除?"
+                //     onConfirm={() => {}}
+                // >
+                //     <a style={{ color: 'red', fontSize: 12 }}>删除</a>
+                // </Popconfirm>
+                return '--'
             },
         },
     ]

+ 46 - 16
src/pages/launchSystemV3/tencentAdPutIn/create/Material/addMaterial.tsx

@@ -7,6 +7,8 @@ import { chunkArray1, getVideoImgUrl } from "@/utils/utils"
 import VideoFrameSelect from "@/pages/launchSystemV3/components/VideoFrameSelect"
 import New1Radio from "@/pages/launchSystemV3/components/New1Radio"
 import SelectCloudNew from "@/pages/launchSystemV3/material/cloudNew/selectCloudNew"
+import ImageXXX from "@/pages/launchSystemV3/components/AdsComponent/ImageXXX"
+import ImageXXXC from "@/pages/launchSystemV3/components/AdsComponent/ImageXXXC"
 
 interface Props {
     adLength: number,
@@ -38,6 +40,7 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
         max: number,
         sliderImgContent: any,
         isGroup?: boolean
+        componentCount?: number
     }>({
         type: '',//类型
         cloudSize: [],//素材搜索条件
@@ -144,7 +147,7 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
         form.setFieldsValue({ dynamicGroup: newDynamicGroup })
     }
 
-    const selectGroupImg = (index: number, num: number) => {
+    const selectGroupImg = (index: number, num: number, length?: number) => {
         setSelectCloudData({ defaultParams: { sizeQueries: [{ width: 800, height: 800, relation: '=' }], fileSize: 400 * 1024, materialType: 'image' }, num })
         setMaterialConfig({
             ...materialConfig,
@@ -152,7 +155,8 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
             max: num,
             index,
             adcreativeTemplateId: creativeTemplateId,
-            isGroup: true
+            isGroup: true,
+            componentCount: length
         })
         setTimeout(() => {
             setSelectVideoVisible(true)
@@ -161,11 +165,14 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
 
     // 获取组件化创意素材数量
     const getComponentCount = (list: any[]): string => {
+        console.log('list', list)
         let arrayImgCount = 0, imageCount = 0, videoCount = 0
         list?.forEach((item: any) => {
-            if (Array.isArray(item)) {
+            if(item?.componentSubType?.includes('IMAGE_LIST')) {
                 arrayImgCount++
-            } else if (item?.url?.includes('mp4')) {
+            } else if (Array.isArray(item)) {
+                arrayImgCount++
+            } else if (item?.url?.includes('mp4') || item?.keyFrameImageUrl) {
                 videoCount++
             } else {
                 imageCount++
@@ -387,10 +394,10 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                                     <Dropdown
                                         menu={{
                                             items: [
-                                                { 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) } },
+                                                { label: '1:1 九图', key: '4', disabled: dynamicGroup?.[num]?.['list']?.length >= 15, onClick: () => { selectGroupImg(num, 9, 15 - (dynamicGroup?.[num]?.['list']?.length || 0)) } },
+                                                { label: '1:1 六图', key: '1', disabled: dynamicGroup?.[num]?.['list']?.length >= 15, onClick: () => { selectGroupImg(num, 6, 15 - (dynamicGroup?.[num]?.['list']?.length || 0)) } },
+                                                { label: '1:1 三图', key: '2', disabled: dynamicGroup?.[num]?.['list']?.length >= 15, onClick: () => { selectGroupImg(num, 3, 15 - (dynamicGroup?.[num]?.['list']?.length || 0)) } },
+                                                { label: '1:1 四图', key: '3', disabled: dynamicGroup?.[num]?.['list']?.length >= 15, onClick: () => { selectGroupImg(num, 4, 15 - (dynamicGroup?.[num]?.['list']?.length || 0)) } },
                                             ]
                                         }}
                                         placement="bottomLeft"
@@ -569,7 +576,15 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                                         </div>
                                         <div className={styles.boxList_body}>
                                             {dynamicGroup?.[num]?.['list']?.map((item: any, index: number) => {
-                                                if (Array.isArray(item)) {
+                                                if (item?.componentSubType?.includes('IMAGE_LIST')) {
+                                                    return <div className={styles.boxList_body_item} key={index}>
+                                                         <div className={styles.tag}>{item?.urlList?.length}图</div>
+                                                        <div className={styles.content}>
+                                                            <ImageXXXC imageList={item?.urlList} placement="top" style={{ width: '100%', height: '100%' }} />
+                                                        </div>
+                                                        <div className={styles.clear} onClick={() => { clearTem(num, index) }}><CloseCircleOutlined /></div>
+                                                    </div>
+                                                } else if (Array.isArray(item)) {
                                                     let length = item.length
                                                     return <div className={styles.boxList_body_item} key={index}>
                                                         <div className={styles.tag}>{length}图</div>
@@ -630,7 +645,9 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                 setSelectVideoVisible(false)
                 setSelectCloudData(undefined)
             }}
+            componentCount={materialConfig?.componentCount}
             putInType={putInType}
+            deliveryMode={deliveryMode}
             onChange={(content: any) => {
                 if (content.length > 0) {
                     if (deliveryMode === 'DELIVERY_MODE_COMPONENT' && !dynamicCreativeSwitch) { // 组件化创意
@@ -663,19 +680,32 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                                 form.setFieldsValue({ dynamicGroup: newDynamicGroup })
                             }
                         } else {
-                            let newDynamicGroup = dynamicGroup?.map((item: any, index: number) => {
+                            const newDynamicGroup = dynamicGroup?.map((item: any, index: number) => {
                                 if (materialConfig.index === index) {
                                     let oldList = item?.list || []
                                     if (materialConfig.isGroup) {
-                                        oldList = oldList.concat([content.map((m: any) => {
-                                            if (selectCloudData?.defaultParams?.materialType === 'video' && m?.materialType === 1) {
-                                                return { id: m?.id, url: m?.oss_url, materialType: m?.materialType || 0, isUnit: m?.isUnit || false, keyFrameImageUrl: m?.key_frame_image_url, accountId: m?.owner_account_id }
-                                            }
-                                            return { id: m?.id, url: m?.oss_url, materialType: m?.materialType || 0, isUnit: m?.isUnit || false, accountId: m?.owner_account_id }
-                                        })])
+                                        if (content?.[0]?.materialType === 4) { // 组件库
+                                            oldList = oldList.concat(content.map((m: any) => {
+                                                return { id: m?.componentId, urlList: m?.oss_url, materialType: m?.materialType || 0, isUnit: true, accountId: m?.organizationId, componentSubType: m?.componentSubType }
+                                            }))
+                                        } else {
+                                            oldList = oldList.concat([content.map((m: any) => {
+                                                if (selectCloudData?.defaultParams?.materialType === 'video' && m?.materialType === 1) {
+                                                    return { id: m?.id, url: m?.oss_url, materialType: m?.materialType || 0, isUnit: m?.isUnit || false, keyFrameImageUrl: m?.key_frame_image_url, accountId: m?.owner_account_id }
+                                                }
+                                                return { id: m?.id, url: m?.oss_url, materialType: m?.materialType || 0, isUnit: m?.isUnit || false, accountId: m?.owner_account_id }
+                                            })])
+                                        }
                                         return { list: oldList }
                                     } else {
                                         oldList = oldList.concat(content.map((m: any) => {
+                                            if (m?.materialType === 4) { // 组件库
+                                                if (selectCloudData?.defaultParams?.materialType === 'video') {
+                                                    return { id: m?.componentId, url: m?.oss_url, materialType: m?.materialType || 0, isUnit: true, keyFrameImageUrl: m?.key_frame_image_url, accountId: m?.organizationId, componentSubType: m?.componentSubType }
+                                                } else {
+                                                    return { id: m?.componentId, url: m?.oss_url, materialType: m?.materialType || 0, isUnit: true, accountId: m?.organizationId, componentSubType: m?.componentSubType }
+                                                }
+                                            }
                                             if (selectCloudData?.defaultParams?.materialType === 'video' && m?.materialType === 1) {
                                                 return { id: m?.id, url: m?.oss_url, materialType: m?.materialType || 0, isUnit: m?.isUnit || false, keyFrameImageUrl: m?.key_frame_image_url, accountId: m?.owner_account_id }
                                             }

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

@@ -111,7 +111,6 @@
     border-radius: 6px;
     background-color: rgb(242, 246, 254);
     position: relative;
-    overflow: hidden;
 
     .content {
         width: 100px;
@@ -121,6 +120,7 @@
         align-items: center;
         justify-content: center;
         flex-wrap: wrap;
+        border-radius: 6px;
 
         >img {
             max-width: 100%;
@@ -137,6 +137,7 @@
         background: rgba(255, 255, 255, 0.8);
         color: rgb(98, 99, 102);
         font-size: 12px;
+        z-index: 1;
     }
 
     .clear {

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

@@ -8,6 +8,7 @@ import AddMaterial from "./addMaterial";
 import VideoNews from "@/pages/launchSystemNew/components/newsModal/videoNews";
 import { shuffleArray } from "@/utils/utils";
 import SaveUseImg from "../Save/saveUseImg";
+import ImageXXXC from "@/pages/launchSystemV3/components/AdsComponent/ImageXXXC";
 const { Title } = Typography;
 
 const Material: React.FC<{ adData?: any[] }> = ({ adData }) => {
@@ -133,7 +134,14 @@ const Material: React.FC<{ adData?: any[] }> = ({ adData }) => {
                                         <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)) {
+                                                if (item?.componentSubType?.includes('IMAGE_LIST')) {
+                                                    return <div className={styles.boxList_body_item} key={index} style={{ width: 60, height: 60 }}>
+                                                         <div className={styles.tag}>{item?.urlList?.length}图</div>
+                                                        <div className={styles.content} style={{ width: 60, height: 60 }}>
+                                                            <ImageXXXC imageList={item?.urlList} placement="top" style={{ width: '100%', height: '100%' }} />
+                                                        </div>
+                                                    </div>
+                                                } else 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>

+ 1 - 1
src/pages/launchSystemV3/tencentAdPutIn/create/MaterialText/newText.tsx

@@ -320,7 +320,7 @@ const NewText: React.FC<Props> = ({ visible, onClose, onChange, value, textData,
                         {TextTypeList
                             .filter(item => (mediaType !== 1 && mediaType !== 3) ? ![4, 5].includes(item.value) : mediaType === 3 ? true : item.value !== 5)
                             .map(item => {
-                                if (item.value === 5 && adDataGroup ? Object.keys(adDataGroup).some((key: any) => adDataGroup[key].length > dynamicMaterialDTos.dynamicGroup.length) : accountCreateLogs.some((pre) => ((pre?.productList || [{}]).length * targetingLength) > dynamicMaterialDTos.dynamicGroup.length)) {
+                                if (item.value === 5 && (adDataGroup ? Object.keys(adDataGroup).some((key: any) => adDataGroup[key].length > dynamicMaterialDTos.dynamicGroup.length) : accountCreateLogs.some((pre) => ((pre?.productList || [{}]).length * targetingLength) > dynamicMaterialDTos.dynamicGroup.length))) {
                                     return <Radio value={item.value} key={item.value} disabled><Tooltip title={'账号下平均分配到广告下的创意组数量不够,不能选择此项'}>
                                         {item.label}
                                     </Tooltip></Radio>

+ 8 - 6
src/pages/launchSystemV3/tencentAdPutIn/create/index.tsx

@@ -827,13 +827,13 @@ const Create: React.FC = () => {
                 })
             }
         }
-        let accountIdParamDTOMap: any = {}
+        const accountIdParamDTOMap: any = {}
         accountCreateLogs.forEach(item => {
-            let { pageList, productList, userActionSetsList, unionPositionPackage, excludeUnionPositionPackage, accountId, wechatChannelList, newConversionList, videoChannelList } = item
+            const { pageList, productList, userActionSetsList, unionPositionPackage, excludeUnionPositionPackage, accountId, wechatChannelList, newConversionList, videoChannelList } = item
 
-            let userActionSetsListDto = userActionSetsList?.map((item: any) => ({ id: item?.userActionSetId, type: item?.type })) // dataSourceId
+            const userActionSetsListDto = userActionSetsList?.map((item: any) => ({ id: item?.userActionSetId, type: item?.type })) // dataSourceId
 
-            let map: any = {
+            const map: any = {
                 userActionSetsList: userActionSetsListDto,
                 unionPositionPackage: unionPositionPackage?.map(item => item.unionPackageId),
                 excludeUnionPositionPackage: excludeUnionPositionPackage?.map(item => item.unionPackageId),
@@ -862,12 +862,12 @@ const Create: React.FC = () => {
         if (dynamic.deliveryMode === 'DELIVERY_MODE_COMPONENT') {
             dynamic.dynamicCreativeType = dynamic?.dynamicCreativeSwitch ? 'DYNAMIC_CREATIVE_TYPE_COMMON' : 'DYNAMIC_CREATIVE_TYPE_PROGRAM'
         }
-        let dynamicCreativesDTO = { ...dynamic, mediaType }
+        const dynamicCreativesDTO = { ...dynamic, mediaType }
         if (dynamic.deliveryMode === 'DELIVERY_MODE_COMPONENT' && !dynamic?.dynamicCreativeSwitch) {
             dynamicCreativesDTO.creativeTemplateId = 711
         }
         delete adgroups?.isConversion  // 前端控制新链路字段
-        let params = {
+        const params = {
             ...values,
             adgroupDTO: adgroups,
             targetings: targeting.map(item => ({ targetingName: item.targetingName, ...item?.targeting || {} })),
@@ -878,6 +878,8 @@ const Create: React.FC = () => {
             taskType: putInType,
             ...copyTask
         }
+        console.log('2222222222---->', params)
+        return
         if (values?.submitRule !== 0) {
             createAdgroupTaskV2.run(params).then(res => {
                 if (res) {

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

@@ -5,6 +5,7 @@ import style from './index.less'
 import VideoNews from "@/pages/launchSystemNew/components/newsModal/videoNews";
 import styles from './Material/index.less'
 import { MARKETING_TARGET_TYPE_ENUM, OPTIMIZATIONGOAL_ENUM } from "../const";
+import ImageXXXC from "../../components/AdsComponent/ImageXXXC";
 
 const columns = (): TableProps<any>['columns'] => {
 
@@ -135,7 +136,7 @@ const columns = (): TableProps<any>['columns'] => {
                                         {(keys.includes('video_id') || keys.includes('short_video1')) ? <>
                                             <Title style={{ fontSize: 12, color: '#1890ff', marginBottom: 0, width: '100%' }}>已选1个视频,0张图片</Title>
                                             <div className={style.video}>
-                                                <VideoNews src={dynamicGroup?.video_id?.url || dynamicGroup?.short_video1?.url} keyFrameImageUrl={dynamicGroup?.video_id?.keyFrameImageUrl || dynamicGroup?.short_video1?.keyFrameImageUrl}/>
+                                                <VideoNews src={dynamicGroup?.video_id?.url || dynamicGroup?.short_video1?.url} keyFrameImageUrl={dynamicGroup?.video_id?.keyFrameImageUrl || dynamicGroup?.short_video1?.keyFrameImageUrl} />
                                                 {dynamicGroup?.cover_id && <div className={style.cover_image} style={{ marginLeft: 4 }}>
                                                     <img src={dynamicGroup?.cover_id?.url} />
                                                 </div>}
@@ -156,7 +157,13 @@ const columns = (): TableProps<any>['columns'] => {
                             } else {
                                 return <div style={{ display: 'flex', gap: 5, flexWrap: 'wrap' }}>
                                     {dynamicGroup?.list?.map((item: any, index: number) => {
-                                        if (Array.isArray(item)) {
+                                        if (item?.componentSubType?.includes('IMAGE_LIST')) {
+                                            return <div className={styles.boxList_body_item} key={index} style={{ width: 30, height: 30 }}>
+                                                <div className={styles.content} style={{ width: 30, height: 30 }}>
+                                                    <ImageXXXC imageList={item?.urlList} placement="top" style={{ width: '100%', height: '100%' }} />
+                                                </div>
+                                            </div>
+                                        } else if (Array.isArray(item)) {
                                             let length = item.length
                                             return <div className={styles.boxList_body_item} key={index} style={{ width: 30, height: 30 }}>
                                                 <div className={styles.content} style={{ width: 30, height: 30 }}>
@@ -305,7 +312,7 @@ export const columnsAddDynamic = (): TableProps<any>['columns'] => {
                                         {(keys.includes('video_id') || keys.includes('short_video1')) ? <>
                                             <Title style={{ fontSize: 12, color: '#1890ff', marginBottom: 0, width: '100%' }}>已选1个视频,0张图片</Title>
                                             <div className={style.video}>
-                                                <VideoNews src={dynamicGroup?.video_id?.url || dynamicGroup?.short_video1?.url} keyFrameImageUrl={dynamicGroup?.video_id?.keyFrameImageUrl || dynamicGroup?.short_video1?.keyFrameImageUrl}/>
+                                                <VideoNews src={dynamicGroup?.video_id?.url || dynamicGroup?.short_video1?.url} keyFrameImageUrl={dynamicGroup?.video_id?.keyFrameImageUrl || dynamicGroup?.short_video1?.keyFrameImageUrl} />
                                                 {dynamicGroup?.cover_id && <div className={style.cover_image} style={{ marginLeft: 4 }}>
                                                     <img src={dynamicGroup?.cover_id?.url} />
                                                 </div>}
@@ -326,7 +333,13 @@ export const columnsAddDynamic = (): TableProps<any>['columns'] => {
                             } else {
                                 return <div style={{ display: 'flex', gap: 5, flexWrap: 'wrap' }}>
                                     {dynamicGroup?.list?.map((item: any, index: number) => {
-                                        if (Array.isArray(item)) {
+                                        if (item?.componentSubType?.includes('IMAGE_LIST')) {
+                                            return <div className={styles.boxList_body_item} key={index} style={{ width: 30, height: 30 }}>
+                                                <div className={styles.content} style={{ width: 30, height: 30 }}>
+                                                    <ImageXXXC imageList={item?.urlList} placement="top" style={{ width: '100%', height: '100%' }} />
+                                                </div>
+                                            </div>
+                                        } else if (Array.isArray(item)) {
                                             let length = item.length
                                             return <div className={styles.boxList_body_item} key={index} style={{ width: 30, height: 30 }}>
                                                 <div className={styles.content} style={{ width: 30, height: 30 }}>

+ 32 - 7
src/services/adqV3/global.ts

@@ -1204,15 +1204,18 @@ export async function getCreativeComponentListApi(data: GetCreativeComponentProp
     })
 }
 
+export interface CreativeComponentDetailDTOS {
+    materialId?: string,
+    componentSubType: string
+    componentType?: string, //'image' | 'imageList' | 'video' | 'videoList' | 'description',
+    materialIdList?: string[]
+    coverUrl?: string
+    text?: string
+}
+
 export interface AddBatchCreativeComponent {
     adAccountId: number,
-    componentType: 'image' | 'imageList' | 'video' | 'videoList' | 'description',
-    creativeComponentDetailDTOS: {
-        materialId?: string,
-        materialIdList?: string[]
-        coverId?: string
-        text?: string
-    }[]
+    creativeComponentDetailDTOS: CreativeComponentDetailDTOS[]
 }
 /**
  * 批量创建创意组件
@@ -1224,4 +1227,26 @@ export async function addBatchCreativeComponentApi(data: AddBatchCreativeCompone
         method: 'POST',
         data
     })
+}
+
+/**
+ * 查询业务单元共享状态
+ * @returns 
+ */
+export async function getDefaultSharingApi(params: { adAccountId: number }) {
+    return request(api + `/adq/creative/component/getDefaultSharing/${params.adAccountId}`, {
+        method: 'GET'
+    })
+}
+
+/**
+ * 修改业务单元组件共享状态
+ * @param params 
+ * @returns 
+ */
+export async function updateDefaultSharingApi(params: { adAccountId: number, type: string }) {
+    return request(api + `/adq/creative/component/updateDefaultSharing`, {
+        method: 'POST',
+        params
+    })
 }