Browse Source

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

wjx 7 months ago
parent
commit
775f96dd0e
29 changed files with 2476 additions and 345 deletions
  1. 4 4
      src/Hook/useAjax.tsx
  2. 50 1
      src/Hook/useFileDrop.tsx
  3. 128 0
      src/components/FileBoxAD/components/Migrate/index.tsx
  4. 4 4
      src/components/FileBoxAD/index.less
  5. 14 0
      src/components/FileBoxAD/index.tsx
  6. 2 2
      src/global.less
  7. 22 16
      src/models/useLaunchAdq/useBdMedia.ts
  8. 4 4
      src/models/useLaunchAdq/useBdMediaPup.ts
  9. 32 16
      src/pages/launchSystemV3/components/BrandImage/index.tsx
  10. 20 18
      src/pages/launchSystemV3/components/VideoFrameSelect/index.tsx
  11. 11 1
      src/pages/launchSystemV3/material/cloudNew/const.ts
  12. 277 0
      src/pages/launchSystemV3/material/cloudNew/details.tsx
  13. 281 94
      src/pages/launchSystemV3/material/cloudNew/index.less
  14. 1 2
      src/pages/launchSystemV3/material/cloudNew/index.tsx
  15. 114 43
      src/pages/launchSystemV3/material/cloudNew/material.tsx
  16. 38 0
      src/pages/launchSystemV3/material/cloudNew/playVideo.tsx
  17. 86 20
      src/pages/launchSystemV3/material/cloudNew/search.tsx
  18. 364 0
      src/pages/launchSystemV3/material/cloudNew/selectCloudNew.tsx
  19. 174 0
      src/pages/launchSystemV3/material/cloudNew/selectSearch.tsx
  20. 88 0
      src/pages/launchSystemV3/material/cloudNew/tableConfig.tsx
  21. 3 2
      src/pages/launchSystemV3/material/cloudNew/uploadFile.tsx
  22. 402 0
      src/pages/launchSystemV3/material/cloudNew/uploadsTable.tsx
  23. 54 2
      src/pages/launchSystemV3/material/typings.d.ts
  24. 37 18
      src/pages/launchSystemV3/tencenTasset/profiles/index.tsx
  25. 74 34
      src/pages/launchSystemV3/tencenTasset/wechatCanvasPage/copyPage.tsx
  26. 110 63
      src/pages/launchSystemV3/tencentAdPutIn/create/Material/addMaterial.tsx
  27. 6 0
      src/pages/launchSystemV3/tencentAdPutIn/index.less
  28. 60 0
      src/services/adqV3/cloudNew.ts
  29. 16 1
      src/utils/utils.ts

+ 4 - 4
src/Hook/useAjax.tsx

@@ -51,10 +51,10 @@ export function useAjax(fnc: CombineService<any, any>, options?: Options) {
         formatResult: (res) => {
             let reqTime = moment().format('YYYY-MM-DD HH:mm:ss')
             res['reqTime'] = reqTime
-            if(res.data){
-                return options?.formatResult ? { ...res, reqTime } : res.data 
-            }else{
-                console.log('res===>',res)
+            if (res.data || res.data === null || res.data === undefined) {
+                return options?.formatResult ? { ...res, reqTime } : res.data
+            } else {
+                console.log('res===>', res)
                 return res
             }
         },

+ 50 - 1
src/Hook/useFileDrop.tsx

@@ -21,10 +21,27 @@ const useFileDrop = (onFileDrop: (files: FileList) => void): FileDropHook => {
         setIsDragOver(false);
     };
 
-    const handleDrop: React.DragEventHandler = (event) => {
+    const handleDrop: React.DragEventHandler = async (event) => {
         event.preventDefault();
         setIsDragOver(false);
 
+        // const items = event.dataTransfer.items;
+        // let fileList: File[] = []
+        // await Array.from(items).forEach(async (itemD, index, data) => {
+        //     const item: any = itemD.webkitGetAsEntry();
+        //     if (item && item.isDirectory) {
+        //         const directoryFiles = await getFilesFromDirectory(item);
+        //         fileList.push(...directoryFiles)
+        //     } else if (item && item.isFile){
+        //         const file = await getFile(item as FileSystemFileEntry);
+        //         fileList.push(file)
+        //     }
+        //     if (data.length === index + 1) {
+        //         console.log('---------->', fileList)
+        //         onFileDrop(fileList as any);
+        //     }
+        // })
+
         const files = event.dataTransfer.files;
 
         if (onFileDrop && typeof onFileDrop === 'function') {
@@ -32,6 +49,38 @@ const useFileDrop = (onFileDrop: (files: FileList) => void): FileDropHook => {
         }
     };
 
+
+    const getFilesFromDirectory = async (directoryEntry: FileSystemDirectoryEntry): Promise<File[]> => {
+        const fileList: File[] = [];
+
+        const reader = directoryEntry.createReader();
+        const entries = await new Promise<FileSystemEntry[]>((resolve) => {
+            reader.readEntries((results) => {
+                resolve(results);
+            });
+        });
+
+        for (const entry of entries) {
+            if (entry.isFile) {
+                const file = await getFile(entry as FileSystemFileEntry);
+                fileList.push(file);
+            } else if (entry.isDirectory) {
+                const subFiles = await getFilesFromDirectory(entry as FileSystemDirectoryEntry);
+                fileList.push(...subFiles);
+            }
+        }
+
+        return fileList;
+    };
+
+    const getFile = async (fileEntry: FileSystemFileEntry): Promise<File> => {
+        return new Promise<File>((resolve, reject) => {
+            fileEntry.file((file) => {
+                resolve(file);
+            }, reject);
+        });
+    };
+
     return {
         isDragOver,
         dropAreaProps: {

+ 128 - 0
src/components/FileBoxAD/components/Migrate/index.tsx

@@ -0,0 +1,128 @@
+import { Button, Card, Form, message, Modal, Space, TreeSelect } from "antd"
+import React, { useEffect, useState } from "react"
+import '../../../../pages/launchSystemV3/tencentAdPutIn/index.less'
+import { getFolderListApi, moveOldMaterialApi } from "@/services/adqV3/cloudNew"
+import { useAjax } from "@/Hook/useAjax"
+import { DataNode } from "antd/lib/tree"
+import { updateTreeData } from "@/pages/launchSystemV3/material/cloudNew/const"
+
+interface Props {
+    mediaType: 'IMG' | 'VIDEO'
+    selectSc: any[]
+    visible?: boolean
+    onClose?: () => void
+}
+
+/**
+ * 批量迁移素材
+ * @returns 
+ */
+const Migrate: React.FC<Props> = ({ visible, onClose, mediaType, selectSc }) => {
+
+    /******************************/
+    const [form] = Form.useForm();
+    const [treeData, setTreeData] = useState<DataNode[]>([]);
+
+    const getFolderList = useAjax((params) => getFolderListApi(params))
+    const moveOldMaterial = useAjax((params) => moveOldMaterialApi(params))
+    /******************************/
+
+    useEffect(() => {
+        getFolder()
+    }, [])
+
+    const getFolder = () => {
+        getFolderList.run({}).then(res => {
+            setTreeData(() => res?.map((item: { folderName: any; id: any; createBy: number }) => ({
+                title: item.folderName,
+                value: item.id,
+                key: item.id,
+                disabled: localStorage.getItem('userId') !== item.createBy.toString()
+            })) || [])
+        })
+    }
+
+    // 下级目录
+    const handleUpdateFolder = (parentId: number) => {
+        return getFolderListApi({ parentId }).then(res => {
+            setTreeData(origin =>
+                updateTreeData(origin, parentId, res?.data?.map((item: { folderName: any; id: any; createBy: number }) => ({
+                    title: item.folderName,
+                    value: item.id,
+                    key: item.id,
+                    disabled: localStorage.getItem('userId') !== item.createBy.toString()
+                })) || []),
+            );
+        })
+    }
+
+    const handleOk = (values: any) => {
+        console.log(values)
+        moveOldMaterial.run({ ...values, materialType: mediaType === 'IMG' ? 'image' : 'video', materialIds: selectSc.map(item => item.id) }).then(res => {
+            console.log(res)
+            if (res) {
+                message.success('迁移成功')
+                onClose?.()
+            }
+        })
+    }
+
+    return <Modal
+        title={<strong>批量迁移素材</strong>}
+        className='modalResetCss'
+        open={visible}
+        onCancel={onClose}
+        footer={null}
+        bodyStyle={{ padding: '0 0 40px', position: 'relative', borderRadius: '0 0 8px 8px' }}
+        maskClosable={false}
+    >
+
+        <Form
+            form={form}
+            name="migrate"
+            labelAlign='left'
+            labelCol={{ span: 4 }}
+            layout="horizontal"
+            colon={false}
+            style={{ backgroundColor: '#f1f4fc', maxHeight: 650, overflow: 'hidden', overflowY: 'auto', padding: '10px', borderRadius: '0 0 8px 8px' }}
+            scrollToFirstError={{
+                behavior: 'smooth',
+                block: 'center'
+            }}
+            onFinish={handleOk}
+            onFinishFailed={({ errorFields }) => {
+                message.error(errorFields?.[0]?.errors?.[0])
+            }}
+        >
+            <Card className="cardResetCss">
+                <Form.Item label={<strong>文件夹</strong>} name={'targetFolderId'} rules={[{ message: '请选择文件夹', required: true }]}>
+                    <TreeSelect
+                        loading={getFolderList.loading}
+                        allowClear
+                        style={{ width: '100%' }}
+                        dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
+                        placeholder="请选择文件夹"
+                        loadData={({ value }) => {
+                            return new Promise<void>(async (resolve) => {
+                                await handleUpdateFolder(Number(value))
+                                resolve()
+                            })
+                        }}
+                        treeData={treeData}
+                    />
+                </Form.Item>
+            </Card>
+
+            <Form.Item className="submit_pull">
+                <Space>
+                    <Button onClick={onClose}>取消</Button>
+                    <Button type="primary" htmlType="submit" loading={moveOldMaterial.loading} className="modalResetCss">
+                        确定
+                    </Button>
+                </Space>
+            </Form.Item>
+        </Form>
+    </Modal>
+}
+
+export default React.memo(Migrate)

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

@@ -44,9 +44,9 @@
   user-select: none;
 
   &:hover {
-    background-color: #c5d8f0;
+    background-color: #dfe7f2;
     border-radius: 5px;
-    border: 1px solid #c5d8f0;
+    border: 1px solid #dfe7f2;
     box-sizing: border-box;
 
     .select {
@@ -177,9 +177,9 @@
   audio {}
 
   &:hover {
-    background-color: #c5d8f0;
+    background-color: #dfe7f2;
     border-radius: 5px;
-    border: 1px solid #c5d8f0;
+    border: 1px solid #dfe7f2;
     box-sizing: border-box;
 
     .select {

+ 14 - 0
src/components/FileBoxAD/index.tsx

@@ -15,6 +15,7 @@ import { getVideoImgUrl } from "@/utils/utils"
 import useFileDrop from "@/Hook/useFileDrop"
 import { RcFile } from "antd/lib/upload"
 import UploadsTable from "./components/uploadsTable"
+import Migrate from "./components/Migrate"
 
 interface News {
     id: number,
@@ -75,6 +76,7 @@ function FlieBox(props: Props) {
     const [isSelect, setIsSelect] = useState<boolean>(false)  // 记录是否框选过素材
     const [sourceList, setSourceList] = useState<any>({})
     const [moveShow, setMoveShow] = useState<boolean>(false)
+    const [migrateSc, setMigrateSc] = useState<boolean>(false)
     /********************************/
 
     // 处理数据
@@ -103,6 +105,7 @@ function FlieBox(props: Props) {
             return <ul style={{ top: xy?.y, left: xy?.x }} className={style.menu} >
                 {(isAll || num === 100) && <li onClick={() => allFile('xz')}>全选</li>}
                 {(isAll || num === 100) && listData?.records && listData?.records?.some((item: { id: number }) => selectFile?.includes(item.id)) && <li onClick={() => allFile('qx')} style={{ color: 'red' }}>取消选择</li>}
+                {(isAll && selectItem?.length > 0 && ['IMG', 'VIDEO'].includes(mediaType || '') && selectItem.every((item: any) => !item.folder)) && <li onClick={() => { setMigrateSc(true) }} style={{ color: 'red' }}>迁移素材</li>}
                 {mediaType === 'PAGE' && !rightClickPup.folder ? <>
                     {/* <li onClick={() => { setPage && setPage(1, rightClickPup.id) }}>查看</li> */}
                     <li onClick={() => { setPage && setPage(2, rightClickPup.id) }}>复制</li>
@@ -122,6 +125,7 @@ function FlieBox(props: Props) {
         return <ul style={{ top: xy?.y, left: xy?.x }} className={style.menu}>
             {(isAll || num === 100) && <li onClick={() => allFile('xz')}>全选</li>}
             {(isAll || num === 100) && listData?.records && listData?.records?.some((item: { id: number }) => selectFile?.includes(item.id)) && <li onClick={() => allFile('qx')} style={{ color: 'red' }}>取消选择</li>}
+            {(isAll && selectItem?.length > 0 && ['IMG', 'VIDEO'].includes(mediaType || '') && selectItem.every((item: any) => !item.folder)) && <li onClick={() => { setMigrateSc(true) }} style={{ color: 'red' }}>迁移素材</li>}
             {//防止K图文无限嵌套创建判断
                 (isAll !== false) && <li onClick={() => { set({ fileVisible: true }) }}>新建文件夹</li> //: <li>此处无法新建操作</li>
             }
@@ -560,6 +564,16 @@ function FlieBox(props: Props) {
                 list.refresh()
             }}
         />}
+
+        {/* 迁移素材 */}
+        {migrateSc && <Migrate 
+            visible={migrateSc}
+            mediaType={mediaType as any}
+            selectSc={selectItem}
+            onClose={() => {
+                setMigrateSc(false)
+            }}
+        />}
     </div>
 }
 export default React.memo(FlieBox)

+ 2 - 2
src/global.less

@@ -89,8 +89,8 @@ select:-webkit-autofill {
 
 ::-webkit-scrollbar {
   // display: none; /* Chrome Safari */
-  width: 6px;
-  height: 8px;
+  width: 4px;
+  height: 4px;
 }
 
 ::-webkit-scrollbar-thumb {

+ 22 - 16
src/models/useLaunchAdq/useBdMedia.ts

@@ -87,7 +87,7 @@ const initData: State = {
 /**本地素材管理器 */
 function useBdMediaPup() {
     const [state, dispatch]: [State, Dispatch<Action>] = useReducer(reducer, initData)
-    const { fileName, sort, belongUser, mediaType, parentId, selectFile, delPupId, rightClickPup, actionItem, path, publicPath, size, videoTitle, videoDescription } = state
+    const { fileName, sort, belongUser, mediaType, parentId, selectFile, delPupId, selectItem, rightClickPup, actionItem, path, publicPath, size, videoTitle, videoDescription } = state
     const list = useAjax((params) => bdSysMediaList(params))
     const add = useAjax((params) => bdSysMediaAdd(params), { msgNmae: '新增' })
     const adds = useAjax((params) => bdSysMediaAddsApi(params), { msgNmae: '新增' })
@@ -384,7 +384,7 @@ function useBdMediaPup() {
         }
     }, [selectFile])
     /**点击文件夹 */
-    const fileClick = useCallback((item) => {
+    const fileClick = useCallback((item: any) => {
         if (isOk) {
             setIsOk(false)
             console.log(item, belongUser == '1')
@@ -400,7 +400,7 @@ function useBdMediaPup() {
 
     }, [path, publicPath, mediaType, belongUser, isOk])
     /**点击目录树*/
-    const treeClick = useCallback((item) => {
+    const treeClick = useCallback((item: any) => {
         if (isOk) {
             setIsOk(false)
             // console.log(item, belongUser == '1')
@@ -415,7 +415,7 @@ function useBdMediaPup() {
         }
     }, [path, publicPath, mediaType, belongUser, isOk])
     /**点击路径 */
-    const pathClick = useCallback((item) => {
+    const pathClick = useCallback((item: any) => {
         let newPath: any[] = []
         if (belongUser == '1') {
             path?.forEach((paths, index) => {//用传入的index和path循环的index对比小于等于index代表该保留的路径
@@ -449,9 +449,9 @@ function useBdMediaPup() {
         let state = selectFile?.some((i) => i === id)
         if (isAll) {
             if (state) {//存在就是删除
-                set({ selectFile: selectFile?.filter(i => i !== id) })
+                set({ selectFile: selectFile?.filter(i => i !== id), selectItem: selectItem?.filter((i: { id: string }) => i.id !== id) })
             } else {//否则新增
-                set({ selectFile: [...selectFile as number[], id] })
+                set({ selectFile: [...selectFile as number[], id], selectItem: [...selectItem as number[], item] })
             }
         } else {//单选情况存在于选择素材弹窗组件
             if (item?.folder && mediaType !== 'IMG') {//假如是文件不让选择
@@ -460,7 +460,7 @@ function useBdMediaPup() {
             }
             if (state) {//存在就是删除
                 set({ selectFile: selectFile?.filter(i => i !== id) })
-                set({ selectItem: null })
+                set({ selectItem: [] })
             } else {//否则新增
                 set({ selectFile: [id] })
                 set({ selectItem: item })
@@ -468,7 +468,7 @@ function useBdMediaPup() {
         }
     }, [selectFile, mediaType])
     /**开启删除弹窗 */
-    const delPupOn = useCallback((delPupId) => {
+    const delPupOn = useCallback((delPupId: any) => {
         set({ delPupId })
     }, [])
     /**关闭删除弹窗并去除选中 */
@@ -493,19 +493,25 @@ function useBdMediaPup() {
     }, [selectFile, actionItem])
     /**全选反选文件*/
     const allFile = useCallback((type: 'xz' | 'qx') => {
-        let allArr: any[] = []
+        let allArr: any[] = [], allFile: any[] = [];
         list?.data?.records?.forEach((item: { id: any }) => {
-            allArr.push(item.id)
-        })
+          allArr.push(item.id);
+          allFile.push(item)
+        });
         let newSelectFile = selectFile || []
+        let newSelectItem = selectItem || []
         if (type === 'xz') {
-            newSelectFile = newSelectFile.concat(allArr.filter((i) => selectFile?.every((id) => id !== i)))
+          newSelectFile = newSelectFile.concat(allArr.filter((i) => selectFile?.every((id) => id !== i)))
+          newSelectItem = newSelectItem.concat(allFile.filter((i) => selectItem?.every((item: any) => item.id !== i.id)))
         } else {
-            newSelectFile = newSelectFile.filter(item => !allArr.includes(item))
+          newSelectFile = newSelectFile.filter(item => !allArr.includes(item))
+          newSelectItem = newSelectItem.filter((item: { id: any; }) => !allArr.includes(item.id))
         }
-        
-        set({ selectFile: newSelectFile })
-    }, [selectFile, list, mediaType])
+        set({
+          selectFile: newSelectFile,
+          selectItem: newSelectItem
+        });
+      }, [selectFile, list, mediaType, selectItem]);
     return {
         state,
         init,

+ 4 - 4
src/models/useLaunchAdq/useBdMediaPup.ts

@@ -509,7 +509,7 @@ function useBdMediaPup() {
   );
   /**点击文件夹 */
   const fileClick = useCallback(
-    (item) => {
+    (item: any) => {
       if (isOk) {
         setIsOk(false);
         if (belongUser == '1' && path) {
@@ -526,7 +526,7 @@ function useBdMediaPup() {
   );
   /**点击目录树*/
   const treeClick = useCallback(
-    (item) => {
+    (item: any) => {
       if (isOk) {
         setIsOk(false);
         console.log(item, belongUser == '1');
@@ -544,7 +544,7 @@ function useBdMediaPup() {
   );
   /**点击路径 */
   const pathClick = useCallback(
-    (item) => {
+    (item: any) => {
       let newPath: any[] = [];
       if (belongUser == '1') {
         path?.forEach((paths, index) => {
@@ -618,7 +618,7 @@ function useBdMediaPup() {
     [selectFile, mediaType, num],
   );
   /**开启删除弹窗 */
-  const delPupOn = useCallback((delPupId) => {
+  const delPupOn = useCallback((delPupId: any) => {
     set({ delPupId });
   }, []);
   /**关闭删除弹窗并去除选中 */

+ 32 - 16
src/pages/launchSystemV3/components/BrandImage/index.tsx

@@ -1,11 +1,10 @@
 import { useAjax } from "@/Hook/useAjax"
-import SelectCloud from "@/pages/launchSystemNew/components/selectCloud"
 import { PlusOutlined } from "@ant-design/icons"
 import { Button, Divider, Form, Input, message, Modal, Select, Space, Table } from "antd"
 import React, { useEffect, useState } from "react"
-import { useModel } from "umi"
 import brandColumns from "./tableConfig"
 import { addSysBrandApi, delSysBrandApi, editSysBrandApi, getSysBrandApi } from "@/services/adqV3/global"
+import SelectCloudNew from "../../material/cloudNew/selectCloudNew"
 
 interface Props {
     onChange?: (data: any) => void,
@@ -160,26 +159,35 @@ export const UploadImage: React.FC<ImageProps> = (props) => {
     /*********************/
     const { onChange, value } = props
     const [selectImgVisible, setSelectImgVisible] = useState<boolean>(false)
-    const [sliderImgContent, setSliderImgContent] = useState<{ url: string, width?: number, height?: number }[]>([])  // 保存回填数据
-    const { init } = useModel('useLaunchAdq.useBdMediaPup')
-    /*********************/
-
-    useEffect(() => {
-        if (value) {
-            setSliderImgContent([{ url: value }])
-        } else {
-            setSliderImgContent([])
+    const [selectCloudData, setSelectCloudData] = useState<{
+        defaultParams: {
+            sizeQueries?: {
+                width: number,
+                height: number,
+                relation: string
+            }[],
+            materialType: 'image' | 'video'
+            fileSize: number
         }
-    }, [value])
+        num: number
+    }>()
+    /*********************/
 
     const setImg = (content: any[]) => {
-        onChange && onChange(content[0]?.url)
+        onChange && onChange(content[0]?.oss_url)
         setSelectImgVisible(false)
     }
 
     const selectImg = () => {
-        init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: 512, height: 512 }]], maxSize: 50 * 1024 })
-        setTimeout(() => { setSelectImgVisible(true) }, 50)
+        setSelectCloudData({
+            defaultParams: {
+                sizeQueries: [{ relation: '=', width: 512, height: 512 }],
+                fileSize: 50 * 1024,
+                materialType: 'image'
+            },
+            num: 1
+        })
+        setSelectImgVisible(true)
     }
 
     return <>
@@ -190,7 +198,15 @@ export const UploadImage: React.FC<ImageProps> = (props) => {
         </div>
 
         {/* 选择素材 */}
-        {selectImgVisible && <SelectCloud visible={selectImgVisible} sliderImgContent={sliderImgContent} onClose={() => setSelectImgVisible(false)} onChange={setImg} />}
+        {(selectImgVisible && selectCloudData) && <SelectCloudNew
+            {...selectCloudData}
+            visible={selectImgVisible}
+            onClose={() => {
+                setSelectImgVisible(false)
+                setSelectCloudData(undefined)
+            }}
+            onChange={setImg}
+        />}
     </>
 }
 

+ 20 - 18
src/pages/launchSystemV3/components/VideoFrameSelect/index.tsx

@@ -14,7 +14,7 @@ interface Props {
  * @returns 
  */
 const VideoFrameSelect: React.FC<Props> = ({ url, onChange }) => {
-    
+
     /************************************/
     const [urlList, setUrlList] = useState<string[]>([])
     const [urlNum, seturlNum] = useState<number>(30)
@@ -28,7 +28,7 @@ const VideoFrameSelect: React.FC<Props> = ({ url, onChange }) => {
         if (selectUrl) {
             console.log(selectUrl)
             onChange?.(selectUrl)
-            setOpen(false);
+            setTimeout(() => setOpen(false), 100)
         }
     };
 
@@ -48,22 +48,24 @@ const VideoFrameSelect: React.FC<Props> = ({ url, onChange }) => {
 
     return <Popover
         content={<div className={style.popover}>
-            <div className={style.content}>
-                {urlList.map((url, index) => <div className={`${style.imgBox} ${selectUrl === url ? style.select : ''}`} key={index}>
-                    <Lazyimg onClick={() => { selectHandle(url) }} className={`lazy`} src={url} />
-                    <span className={style.look} onClick={() => { setLookUrl(url); setVisible(true) }}>查看</span>
-                </div>)}
-            </div>
-            <Image width={200} style={{ display: 'none' }} src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?x-oss-process=image/blur,r_50,s_50/quality,q_1/resize,m_mfit,h_200,w_200"
-                preview={{
-                    visible,
-                    src: lookUrl,
-                    onVisibleChange: (value) => {
-                        setVisible(value);
-                    },
-                }}
-            />
-            <Button type="primary" style={{ backgroundColor: '#00b96b', borderColor: '#00b96b' }} onClick={handleOk} disabled={!selectUrl} size="small">确定</Button>
+            {open && <>
+                <div className={style.content}>
+                    {urlList.map((url, index) => <div className={`${style.imgBox} ${selectUrl === url ? style.select : ''}`} key={index}>
+                        <Lazyimg onClick={() => { selectHandle(url) }} className={`lazy`} src={url} />
+                        <span className={style.look} onClick={() => { setLookUrl(url); setVisible(true) }}>查看</span>
+                    </div>)}
+                </div>
+                <Image width={200} style={{ display: 'none' }} src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?x-oss-process=image/blur,r_50,s_50/quality,q_1/resize,m_mfit,h_200,w_200"
+                    preview={{
+                        visible,
+                        src: lookUrl,
+                        onVisibleChange: (value) => {
+                            setVisible(value);
+                        },
+                    }}
+                />
+                <Button type="primary" style={{ backgroundColor: '#00b96b', borderColor: '#00b96b' }} onClick={handleOk} disabled={!selectUrl} size="small">确定</Button>
+            </>}
         </div>}
         destroyTooltipOnHide={true}
         trigger="click"

+ 11 - 1
src/pages/launchSystemV3/material/cloudNew/const.ts

@@ -23,4 +23,14 @@ export const updateTreeData = (list: DataNode[], key: React.Key, children: DataN
         }
         return node;
     });
-}
+}
+
+/** 选择素材 展示字段 */
+export const showFieldList = [
+    { label: '创建时间', value: 'material.create_time' },
+    { label: '消耗', value: 'material_data_day.cost' },
+    { label: '点击率', value: 'material_data_day.ctr' },
+    { label: '目标转化率', value: 'material_data_day.conversions_rate' },
+    { label: '创意关联数', value: 'material_data_day.adgroup_count' },
+    { label: '创意关联数', value: 'material_data_day.dynamic_creative_count' }
+]

+ 277 - 0
src/pages/launchSystemV3/material/cloudNew/details.tsx

@@ -0,0 +1,277 @@
+import { useAjax } from "@/Hook/useAjax"
+import { getCreativeReportApi, getMaterialDetailApi, getReportMaterialDetailApi, updateMaterialApi } from "@/services/adqV3/cloudNew"
+import { Button, Card, DatePicker, Divider, Drawer, Input, InputNumber, message, Popover, Space, Spin, Statistic, Table, Tag, Typography } from "antd"
+import React, { useEffect, useState } from "react"
+import '../../tencentAdPutIn/index.less'
+import style from './index.less'
+import { formatBytes, formatSecondsToTime } from "@/utils/utils"
+import moment from "moment"
+import { DownOutlined, EditOutlined } from "@ant-design/icons"
+import columns from "./tableConfig"
+const { Title, Text, Paragraph } = Typography;
+
+
+interface Props {
+    data: any
+    visible?: boolean
+    onClose?: () => void
+    onChange?: () => void
+}
+
+/**
+ * 素材详情
+ * @param param0 
+ * @returns 
+ */
+const Details: React.FC<Props> = ({ data, visible, onClose, onChange }) => {
+
+    /************************************/
+    const [detailsData, setDetailsData] = useState<any>({})
+    const [dataOverview, setDataOverview] = useState<{ conversions_cost: number | null, conversions_count: number | null, conversions_rate: number | null, cost: number | null, ctr: number | null }>({ conversions_cost: null, conversions_count: null, conversions_rate: null, cost: null, ctr: null })
+    const [dataOverviewParams, setDataOverviewParams] = useState<{ dataTimeMix?: string, dataTimeMax?: string }>({ dataTimeMix: moment().subtract(12, 'months').format('YYYY-MM-DD'), dataTimeMax: moment().format('YYYY-MM-DD') })
+    const [dynimicParams, setDynimicParams] = useState<CLOUDNEW.GetCreativeReport>({ pageNum: 1, pageSize: 20 })
+    const [openDynimic, setOpenDynimic] = useState<boolean>(false)
+    const [oldLastDay, setOldLastDay] = useState<number>(30)
+    const [lastDay, setLastDay] = useState<number>(30)
+    const [updateDayVisible, setUpdateDayVisible] = useState<boolean>(false)
+
+    const getMaterialDetail = useAjax((params) => getMaterialDetailApi(params))
+    const getReportMaterialDetail = useAjax((params) => getReportMaterialDetailApi(params))
+    const updateMaterial = useAjax((params) => updateMaterialApi(params))
+    const getCreativeReport = useAjax((params) => getCreativeReportApi(params))
+    /************************************/
+
+    useEffect(() => {
+        getDetails()
+    }, [data])
+
+    useEffect(() => {
+        getReportMaterialDetail.run({ ...dataOverviewParams, signature: data.md5 }).then(res => {
+            setDataOverview(res || { conversions_cost: null, conversions_count: null, conversions_rate: null, cost: null, ctr: null })
+        })
+    }, [data, dataOverviewParams])
+
+    // 创意列表
+    useEffect(() => {
+        if (openDynimic) {
+            getCreativeReport.run({ ...dataOverviewParams, ...dynimicParams, signature: data.md5, creativeTimeMin: moment().subtract(lastDay, 'day').format('YYYY-MM-DD'), creativeTimeMax: moment().format('YYYY-MM-DD') })
+        }
+    }, [dynimicParams, data, dataOverviewParams, lastDay, openDynimic])
+
+    // 获取素材详情
+    const getDetails = (isChange?: boolean) => {
+        getMaterialDetail.run({ id: data.id }).then(res => {
+            if (res) {
+                setDetailsData(res)
+                if (isChange) {
+                    onChange?.()
+                }
+            }
+        })
+    }
+
+    // 修改名称
+    const onSetEditable = (value: { materialName: string, description?: string }, fiele: string) => {
+        if (fiele === 'materialName' && !value.materialName) {
+            return
+        }
+        if (value[fiele as keyof typeof value] !== detailsData[fiele]) {
+            updateMaterial.run({ ...value, id: detailsData.id }).then(res => {
+                if (res) {
+                    getDetails(true)
+                    message.success('修改成功')
+                }
+            })
+        }
+    }
+
+    return <Drawer
+        title={<strong>{data.materialName} 素材详情</strong>}
+        placement="right"
+        onClose={onClose}
+        open={visible}
+        width={1000}
+        headerStyle={{ padding: '10px 16px' }}
+        maskClosable={false}
+        className={`modalResetCss targetingSelect`}
+        bodyStyle={{ overflow: 'auto' }}
+    >
+        <div className={style.file_details}>
+            <Card
+                title={<strong>基础信息</strong>}
+                className={`cardResetCss 
+                ${style.details_left}`}
+            >
+                <Spin spinning={getMaterialDetail.loading}>
+                    <div style={{ minHeight: 350 }}>
+                        {detailsData?.materialType === 'image' ? <img src={detailsData.ossUrl} /> : <video src={detailsData.ossUrl} controls></video>}
+                        <Title level={5} ellipsis editable={{ onChange: (value) => onSetEditable({ materialName: value, description: detailsData?.description }, 'materialName') }}>{detailsData.materialName}</Title>
+                        <Divider />
+                        <ul className={style.details_xq}>
+                            <li>
+                                <span>创建时间</span>
+                                <span>{detailsData?.createTime}</span>
+                            </li>
+                            <li>
+                                <span>大小</span>
+                                <span>{formatBytes(detailsData?.fileSize)}</span>
+                            </li>
+                            <li>
+                                <span>尺寸</span>
+                                <span>{detailsData?.width} * {detailsData?.height}</span>
+                            </li>
+                            {detailsData?.materialType === 'video' && <>
+                                <li>
+                                    <span>时长</span>
+                                    <span>{formatSecondsToTime(Math.floor(detailsData?.videoDuration))}</span>
+                                </li>
+                            </>}
+                            <li>
+                                <span>备注</span>
+                                <div><Text editable={{
+                                    onChange: (value) => onSetEditable({ materialName: detailsData.materialName, description: value }, 'description')
+                                }} ellipsis>{detailsData?.description || '--'}</Text></div>
+                            </li>
+                        </ul>
+                    </div>
+                </Spin>
+            </Card>
+            <div className={style.details_right}>
+                <Card
+                    className="cardResetCss"
+                    title={<strong>数据概览</strong>}
+                    extra={<DatePicker.RangePicker
+                        ranges={{
+                            '此刻': [moment(), moment()],
+                            '昨天': [moment().subtract(1, 'day'), moment().subtract(1, 'day')],
+                            '近7天': [moment().subtract(7, 'day'), moment()],
+                            '本月': [moment().startOf('month'), moment()],
+                            '上月': [moment().subtract(1, 'months').startOf('month'), moment().subtract(1, 'months').endOf('month')],
+                        }}
+                        style={{ width: 230 }}
+                        value={(dataOverviewParams?.dataTimeMix && dataOverviewParams?.dataTimeMax) ? [moment(dataOverviewParams?.dataTimeMix), moment(dataOverviewParams?.dataTimeMax)] : undefined}
+                        onChange={(_, option) => setDataOverviewParams({ dataTimeMix: option[0], dataTimeMax: option[1] })}
+                    />}
+                >
+                    <Spin spinning={getReportMaterialDetail.loading}>
+                        <div style={{ display: 'flex', gap: 10 }}>
+                            <Card style={{ flex: 1, overflow: 'hidden' }} bordered={false} bodyStyle={{ backgroundColor: '#f6ffed' }} className="cardResetCss">
+                                <Statistic
+                                    title={<strong style={{ color: '#52c41a' }}>消耗</strong>}
+                                    value={dataOverview?.cost === null ? '--' : dataOverview?.cost || 0}
+                                    precision={2}
+                                    valueStyle={{ color: '#52c41a' }}
+                                />
+                            </Card>
+                            <Card style={{ flex: 1, overflow: 'hidden' }} bordered={false} bodyStyle={{ backgroundColor: '#e6f7ff' }} className="cardResetCss">
+                                <Statistic
+                                    title={<strong style={{ color: '#40a9ff' }}>转化成本</strong>}
+                                    value={dataOverview?.conversions_cost === null ? '--' : dataOverview?.conversions_cost || 0}
+                                    precision={2}
+                                    valueStyle={{ color: '#40a9ff' }}
+                                />
+                            </Card>
+                            <Card style={{ flex: 1, overflow: 'hidden' }} bordered={false} bodyStyle={{ backgroundColor: '#fff0f6' }} className="cardResetCss">
+                                <Statistic
+                                    title={<strong style={{ color: '#f759ab' }}>点击率</strong>}
+                                    value={dataOverview?.ctr === null ? '--' : (dataOverview?.ctr || 0) * 100}
+                                    precision={2}
+                                    valueStyle={{ color: '#f759ab' }}
+                                    suffix={dataOverview?.ctr === null ? undefined : '%'}
+                                />
+                            </Card>
+                            <Card style={{ flex: 1, overflow: 'hidden' }} bordered={false} bodyStyle={{ backgroundColor: '#f9f0ff' }} className="cardResetCss">
+                                <Statistic
+                                    title={<strong style={{ color: '#9254de' }}>转化率</strong>}
+                                    value={dataOverview?.conversions_rate === null ? '--' : (dataOverview?.conversions_rate || 0) * 100}
+                                    precision={2}
+                                    valueStyle={{ color: '#9254de' }}
+                                    suffix={dataOverview?.conversions_rate === null ? undefined : '%'}
+                                />
+                            </Card>
+                            <Card style={{ flex: 1, overflow: 'hidden' }} bordered={false} bodyStyle={{ backgroundColor: '#f0f5ff' }} className="cardResetCss">
+                                <Statistic
+                                    title={<strong style={{ color: '#597ef7' }}>转化量</strong>}
+                                    value={dataOverview?.conversions_count === null ? '--' : dataOverview?.conversions_count || 0}
+                                    valueStyle={{ color: '#597ef7' }}
+                                />
+                            </Card>
+                        </div>
+                    </Spin>
+                </Card>
+                <Card
+                    className="cardResetCss"
+                    title={<strong>
+                        最近{lastDay}天创建的创意
+                        <Popover
+                            onOpenChange={(e) => {
+                                setUpdateDayVisible(e)
+                                if (e)
+                                    setOldLastDay(lastDay)
+                            }}
+                            trigger={['click']}
+                            open={updateDayVisible}
+                            content={<div>
+                                <Title level={5} style={{ fontSize: 12 }}>请设置创建时间范围</Title>
+                                <Space>
+                                    <InputNumber min={1} max={30} style={{ width: 300 }} value={oldLastDay} onChange={(value) => setOldLastDay(value || 1)} />
+                                    <Button
+                                        type="primary"
+                                        onClick={() => {
+                                            setLastDay(oldLastDay);
+                                            setUpdateDayVisible(false)
+                                        }}
+                                    >
+                                        确定
+                                    </Button>
+                                </Space>
+                                <Paragraph style={{ marginTop: 10, fontSize: 12 }}>修改后,创建时间在最近{oldLastDay}天的关联广告信息被显示</Paragraph>
+                            </div>}
+                            title={<strong style={{ fontSize: 14 }}>调整创意创建时间范围</strong>}
+                        >
+                            <Button type="link"><EditOutlined /></Button>
+                        </Popover>
+                    </strong>}
+                >
+                    {
+                        openDynimic ?
+                            <Table
+                                columns={columns()}
+                                dataSource={getCreativeReport?.data?.records}
+                                size="small"
+                                bordered
+                                loading={getCreativeReport.loading}
+                                rowKey={'dynamic_creative_id'}
+                                scroll={{ x: 1000, y: 500 }}
+                                pagination={{
+                                    current: getCreativeReport?.data?.current || 1,
+                                    pageSize: getCreativeReport?.data?.size || 20,
+                                    total: getCreativeReport?.data?.total || 0,
+                                    showTotal: (total) => <Tag color="cyan">总共{total}数据</Tag>,
+                                    showSizeChanger: true
+                                }}
+                                onChange={(pagination, filters, sorter: any) => {
+                                    console.log(pagination, sorter)
+                                    let { current, pageSize } = pagination
+                                    let newDynimicParams = JSON.parse(JSON.stringify(dynimicParams))
+                                    if (sorter && sorter?.order) {
+                                        newDynimicParams['sortType'] = sorter?.order === 'ascend' ? true : false
+                                        newDynimicParams['sortFiled'] = sorter?.field
+                                    } else {
+                                        delete newDynimicParams['sortType']
+                                        delete newDynimicParams['sortFiled']
+                                    }
+                                    newDynimicParams.pageNum = current || newDynimicParams.pageNum
+                                    newDynimicParams.pageSize = pageSize || newDynimicParams.pageSize
+                                    setDynimicParams({ ...newDynimicParams })
+                                }}
+                            />
+                            : <Button style={{ width: '100%' }} type="primary" onClick={() => { setOpenDynimic(true) }}>点击展开查看 <DownOutlined /></Button>
+                    }
+                </Card>
+            </div>
+        </div>
+    </Drawer>
+}
+
+export default React.memo(Details)

+ 281 - 94
src/pages/launchSystemV3/material/cloudNew/index.less

@@ -37,136 +37,180 @@
             overflow-y: auto;
         }
     }
+}
+
+.material {
+    flex: 1 0;
+    overflow: hidden;
+    display: flex;
+    flex-direction: column;
+
+    .operates {
+        border-bottom: 1px solid #e8eaec;
+        padding: 8px 16px;
+        flex: 0 1;
+        display: flex;
+        justify-content: space-between;
+    }
 
-    .material {
+    .left_bts {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 10px;
+        align-items: center;
+    }
+
+    .content {
+        width: 100%;
         flex: 1 0;
         overflow: hidden;
-        display: flex;
-        flex-direction: column;
+        min-height: 100px;
+    }
 
-        .operates {
-            border-bottom: 1px solid #e8eaec;
-            padding: 8px 16px;
-            flex: 0 1;
+    .content_scroll {
+        height: 100%;
+        overflow-y: auto;
+        padding: 12px;
+
+        div.content_scroll_div {
             display: flex;
-            justify-content: space-between;
+            // gap: 10px;
+            flex-wrap: wrap;
         }
 
-        .left_bts {
+        .placeholder {
+            position: absolute;
+            pointer-events: none;
+            color: #6c6c6c;
+            width: 100%;
+            height: 100%;
+            font-weight: 600;
+            font-size: 18px;
             display: flex;
-            flex-wrap: wrap;
-            gap: 10px;
+            justify-content: center;
             align-items: center;
+            z-index: 100;
+            background-color: rgba(255, 224, 224, 0.5);
+            border: 1px dashed 9b9b9b;
         }
 
-        .content {
-            width: 100%;
-            flex: 1 0;
-            overflow: hidden;
-            min-height: 100px;
+        .dragOver {
+            color: red;
+            border: 1px dashed red;
         }
+    }
 
-        .content_scroll {
-            height: 100%;
-            overflow-y: auto;
-            padding: 12px;
+    .content_row {
+        padding: 4px;
 
-            div.content_scroll_div {
-                display: flex;
-                // gap: 10px;
-                flex-wrap: wrap;
-            }
+        >div {
+            border-radius: 6px;
         }
+    }
 
-        .content_row {
-            padding: 4px;
+    .content_col {
+        user-select: none;
+        /* 对大多数浏览器有效 */
+        -webkit-user-select: none;
+        /* Safari */
+        -moz-user-select: none;
+        /* Firefox */
+        -ms-user-select: none;
+        /* Internet Explorer/Edge */
 
-            >div {
-                border-radius: 6px;
-            }
+        &:hover .body .detailBt {
+            opacity: 1;
         }
+    }
 
-        .content_col {
-            user-select: none;
-            /* 对大多数浏览器有效 */
-            -webkit-user-select: none;
-            /* Safari */
-            -moz-user-select: none;
-            /* Firefox */
-            -ms-user-select: none;
-            /* Internet Explorer/Edge */
+    .content_cover {
+        padding: 10px;
+        background-color: #ececec;
+        border-top-left-radius: 6px;
+        border-top-right-radius: 6px;
+        position: relative;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+
+        .checkbox {
+            position: absolute;
+            top: 6px;
+            left: 10px;
         }
 
-        .content_cover {
-            padding: 10px;
-            background-color: #ececec;
-            border-top-left-radius: 6px;
-            border-top-right-radius: 6px;
-            position: relative;
+        .coverImg {
+            max-width: 100%;
+            max-height: 100%;
+        }
+
+        .file_info {
+            position: absolute;
+            bottom: 5px;
+            padding: 0 10px;
             display: flex;
-            justify-content: center;
-            align-items: center;
+            justify-content: space-between;
+            width: 100%;
 
-            .checkbox {
-                position: absolute;
-                top: 6px;
-                left: 10px;
+            >div {
+                background-color: rgba(0, 0, 0, 0.75);
+                padding: 0 4px;
+                border-radius: 4px;
+                color: #fff;
+                font-size: 12px;
             }
+        }
 
-            .coverImg {
-                max-width: 100%;
-                max-height: 100%;
-            }
+        .playr {
+            position: absolute;
+            width: 35px;
+            height: 35px;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
 
-            .file_info {
-                position: absolute;
-                bottom: 5px;
-                padding: 0 10px;
-                display: flex;
-                justify-content: space-between;
+            >img {
                 width: 100%;
-
-                >div {
-                    background-color: rgba(0, 0, 0, 0.75);
-                    padding: 0 4px;
-                    border-radius: 4px;
-                    color: #fff;
-                    font-size: 12px;
-                }
+                height: 100%;
             }
+        }
+    }
 
-            .playr {
-                position: absolute;
-                width: 35px;
-                height: 35px;
-                top: 50%;
-                left: 50%;
-                transform: translate(-50%, -50%);
+    .body {
+        padding: 6px 10px;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        gap: 10px;
 
-                >img {
-                    width: 100%;
-                    height: 100%;
-                }
+        .title {
+            flex: 1 0;
+            overflow: hidden;
+
+            span:hover {
+                color: #1890ff;
             }
         }
 
-        .body {
-            padding: 6px 10px;
+        .detailBt {
+            font-size: 12px;
+            opacity: 0;
+            transition: opacity ease-in-out .2s;
         }
+    }
 
-        .actions {
-            // border-top: 1px solid rgba(0, 0, 0, .06);
-            padding: 4px 10px;
-            display: flex;
-            justify-content: space-between;
-            line-height: 22px;
-        }
+    .actions {
+        // border-top: 1px solid rgba(0, 0, 0, .06);
+        padding: 4px 10px;
+        display: flex;
+        justify-content: space-between;
+        line-height: 22px;
+    }
 
-        .fotter {
-            flex: 0 1;
-            border-top: 1px solid #e8eaec;
-            padding: 8px 16px;
-        }
+    .fotter {
+        flex: 0 1;
+        border-top: 1px solid #e8eaec;
+        padding: 8px 16px;
     }
 }
 
@@ -190,4 +234,147 @@
             display: none;
         }
     }
+}
+
+.file_details {
+    display: flex;
+    gap: 10px;
+
+    .details_left {
+        width: 300px;
+
+        img,
+        video {
+            width: 100%;
+            margin-bottom: 10px;
+        }
+    }
+
+    .details_right {
+        flex: 1 0;
+        overflow: hidden;
+        display: flex;
+        flex-direction: column;
+        gap: 10px;
+    }
+
+    .details_xq {
+        width: 100%;
+        margin-block-start: 0;
+        margin-block-end: 0;
+        padding-inline-start: 0;
+
+        li {
+            display: flex;
+            justify-content: space-between;
+
+            >span {
+                font-size: 12px;
+                display: inline-block;
+                white-space: nowrap;
+                overflow: hidden;
+                text-overflow: ellipsis;
+
+                &:first-child {
+                    width: 85px;
+                }
+
+                &:last-child {
+                    width: 175px;
+                    text-align: right;
+                }
+            }
+
+            >div {
+                width: 175px;
+                text-align: right;
+                font-size: 12px;
+
+                >span {
+                    width: 100%;
+                }
+            }
+        }
+
+    }
+}
+
+.divFooter {
+    height: 40px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    gap: 10px;
+
+    .selectedCloud {
+        flex: 1 0;
+        overflow: hidden;
+        overflow-x: auto;
+        height: 40px;
+
+        >div {
+            height: 38px;
+            display: flex;
+            gap: 8px;
+            align-items: center;
+
+            &::-webkit-scrollbar {
+                width: 1px;
+                /* 垂直滚动条宽度 */
+                height: 1px;
+                /* 水平滚动条高度 */
+            }
+
+            @-moz-document url-prefix() {
+                .selectedCloud {
+                    scrollbar-width: none;
+                }
+            }
+        }
+    }
+
+
+
+    .selectedCloud_col {
+        width: 58px;
+        height: 28px;
+        background-color: #d4d4d4;
+        border-radius: 6px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        overflow: hidden;
+        position: relative;
+
+        >img {
+            max-width: 100%;
+            max-height: 100%;
+        }
+
+        .cz {
+            opacity: 0;
+            padding: 0 4px;
+            border-radius: 4px;
+            background-color: rgba(0, 0, 0, 0.75);
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+            transition: all .2;
+            display: flex;
+            gap: 4px;
+        }
+
+        &:hover .cz {
+            opacity: 1;
+        }
+    }
+}
+
+.select_cloudNew_layout {
+    display: flex;
+    width: 100%;
+    height: 100%;
+    flex-direction: column;
+    gap: 6px;
 }

+ 1 - 2
src/pages/launchSystemV3/material/cloudNew/index.tsx

@@ -11,6 +11,7 @@ import ManageFolder from "./manageFolder";
 import { ExclamationCircleOutlined, FolderOpenOutlined, FolderOutlined } from "@ant-design/icons";
 import { updateTreeData } from "./const";
 import Search from "./search";
+import Details from "./details";
 
 export const DispatchCloudNew = React.createContext<CLOUDNEW.CloudNewReactContent | null>(null);
 
@@ -34,7 +35,6 @@ const CloudNew: React.FC = () => {
     const [folderCreateBy, setFolderCreateBy] = useState<number>()     // 获取最上层文件夹创建人
     const [batchFolderVisible, setBatchFolderVisible] = useState<boolean>(false) // 批量操作文件夹控制
     const [handleType, setHandleType] = useState<'folder' | 'file'>('file') // 操作类型
-    const [searchParams, setSearchParams] = useState<{ searchType: 'file' | 'folder', keyword?: string }>({ searchType: 'file' })
     const [materialParams, setMaterialParams] = useState<CLOUDNEW.GetMaterialListProps>({ pageNum: 1, pageSize: 30 })
 
     const getFolderList = useAjax((params) => getFolderListApi(params))
@@ -148,7 +148,6 @@ const CloudNew: React.FC = () => {
                 folderCreateBy, setFolderCreateBy,
                 batchFolderVisible, setBatchFolderVisible,
                 handleType, setHandleType,
-                searchParams, setSearchParams,
                 materialParams, setMaterialParams
             }}>
                 {/* 搜索 */}

+ 114 - 43
src/pages/launchSystemV3/material/cloudNew/material.tsx

@@ -1,6 +1,6 @@
 import React, { forwardRef, Ref, useContext, useEffect, useImperativeHandle, useRef, useState } from "react"
 import style from './index.less'
-import { Breadcrumb, Button, Card, Checkbox, Dropdown, message, Modal, Pagination, Radio, Spin, Typography } from "antd"
+import { Breadcrumb, Button, Card, Checkbox, Dropdown, Empty, message, Modal, Pagination, Radio, Spin, Typography } from "antd"
 import { DispatchCloudNew } from "."
 import { delMaterialApi, getFolderListApi, getMaterialListApi } from "@/services/adqV3/cloudNew"
 import { useAjax } from "@/Hook/useAjax"
@@ -15,6 +15,9 @@ import UpdateCreate from "./updateCreate"
 import { formatSecondsToTime, getVideoImgUrl } from "@/utils/utils"
 import { ExclamationCircleOutlined } from "@ant-design/icons"
 import UpdateFile from "./updateFile"
+import Details from "./details"
+import useFileDrop from "@/Hook/useFileDrop"
+import UploadsTable from "./uploadsTable"
 const { Text } = Typography;
 
 interface Props {
@@ -39,7 +42,7 @@ const Material = forwardRef(({ onAddFolder, onUpdateFolder, onDelFolder }: Props
 
     /********************************/
     const { initialState } = useModel('@@initialState');
-    const { breadcrumdData, setSelectedKeys, setBreadcrumdData, setSearchParams, setMaterialParams, materialParams, searchParams, selectedKeys, treeData, folderCreateBy, findParentKeys, loadedKeys, handleUpdateFolder, batchFolderVisible, setBatchFolderVisible, handleType, setHandleType } = useContext(DispatchCloudNew)!;
+    const { breadcrumdData, setSelectedKeys, setBreadcrumdData, setMaterialParams, materialParams, selectedKeys, treeData, folderCreateBy, findParentKeys, loadedKeys, handleUpdateFolder, batchFolderVisible, setBatchFolderVisible, handleType, setHandleType } = useContext(DispatchCloudNew)!;
     const ref = useRef<HTMLDivElement>(null);
     const size = useSize(ref);
     const [folderList, setFolderList] = useState<{ id: number, folderName: string, createBy: number, description?: string }[]>([])
@@ -54,6 +57,7 @@ const Material = forwardRef(({ onAddFolder, onUpdateFolder, onDelFolder }: Props
     const [uploadVisible, setUploadVisible] = useState<boolean>(false)
     const [updateOwnerData, setUpdateOwnerData] = useState<{ visible?: boolean, folderId: number }>({ folderId: 0 })
     const [updateFileData, setUpdateFileData] = useState<{ visible?: boolean, initialValues: any }>({ visible: false, initialValues: {} })
+    const [detailsData, setDetailsData] = useState<{ visible?: boolean, data: any }>({ visible: false, data: {} })
 
     const getFolderList = useAjax((params) => getFolderListApi(params))
     const delMaterial = useAjax((params) => delMaterialApi(params))
@@ -66,12 +70,8 @@ const Material = forwardRef(({ onAddFolder, onUpdateFolder, onDelFolder }: Props
             getFolder()
         },
         search(value) {
-            setSearchParams(value)
-            if (value.searchType === 'folder') {
-                getFolder(value.keyword)
-            } else {
-                setMaterialParams(data => ({ ...data, materialName: value.keyword, pageNum: 1 }))
-            }
+            setHandleType('file')
+            setMaterialParams(data => ({ ...data, ...value, pageNum: 1 }))
         }
     }));
 
@@ -85,24 +85,24 @@ const Material = forwardRef(({ onAddFolder, onUpdateFolder, onDelFolder }: Props
 
     useEffect(() => {
         cancelSelect()
-        if (isShowFolder() && !(searchParams.keyword && searchParams.searchType)) {
+        if (isShowFolder()) {
             // 文件夹
             getFolder()
         } else {
             // 文件
             getFile()
         }
-    }, [selectedKeys, handleType, materialParams, searchParams])
+    }, [selectedKeys, handleType, materialParams])
 
     /** 获取下级文件夹 */
-    const getFolder = (folderName?: string) => {
+    const getFolder = () => {
         let parentId: number | undefined;
         if (selectedKeys?.length) {
             const parentIdArr = (selectedKeys[0] as string).split('-')
             const parentIdArrLength = parentIdArr.length
             parentId = Number(parentIdArr[parentIdArrLength - 1])
         }
-        getFolderList.run({ parentId, folderName }).then(res => {
+        getFolderList.run({ parentId }).then(res => {
             setFolderList(() => res || [])
         })
     }
@@ -115,7 +115,15 @@ const Material = forwardRef(({ onAddFolder, onUpdateFolder, onDelFolder }: Props
             const parentIdArrLength = parentIdArr.length
             parentId = Number(parentIdArr[parentIdArrLength - 1])
         }
-        getMaterialList.run({ ...materialParams, folderId: parentId })
+        let params = JSON.parse(JSON.stringify(materialParams))
+        if (params?.width || params?.height) {
+            params.sizeQueries = [{ width: params?.width, height: params?.height, relation: '=' }]
+            delete params?.width
+            delete params?.height
+        } else {
+            delete params?.sizeQueries
+        }
+        getMaterialList.run({ ...params, folderId: parentId })
     }
 
     // 文件夹选择
@@ -209,51 +217,77 @@ const Material = forwardRef(({ onAddFolder, onUpdateFolder, onDelFolder }: Props
     const getItemsFile = (item: any) => {
         let data: ItemType[] = []
         if (isPermission(item.createBy)) {
-            data.push({ label: '编辑', style: { fontSize: 12 }, key: 'edit', onClick: () => setUpdateFileData({ visible: true, initialValues: item }) })
+            data.push({
+                label: <div onClick={(e) => {
+                    e.stopPropagation()
+                    e.preventDefault()
+                    setUpdateFileData({ visible: true, initialValues: item })
+                }}>编辑</div>, style: { fontSize: 12 }, key: 'edit'
+            })
             if (!!selectedKeys?.[0]) {
                 data.push({
-                    label: '移动', style: { fontSize: 12 }, key: 'move', onClick: () => {
+                    label: <div onClick={(e) => {
+                        e.stopPropagation()
+                        e.preventDefault()
                         setCheckedList([item.id])
                         setMoveType('file')
                         setMoveVisible(true)
-                    }
+                    }}>移动</div>, style: { fontSize: 12 }, key: 'move'
                 })
             }
-            data.push({ label: <span style={{ color: 'red', fontSize: 12 }}>删除</span>, key: 'del', onClick: () => delFile(item.id, item?.materialName) })
+            data.push({
+                label: <div style={{ color: 'red', fontSize: 12 }} onClick={(e) => {
+                    e.stopPropagation()
+                    e.preventDefault()
+                    delFile(item.id, item?.materialName)
+                }}>删除</div>, key: 'del'
+            })
         }
         return data
     }
 
     // 是否展示文件夹
     const isShowFolder = () => {
-        return !selectedKeys?.[0] || handleType === 'folder'
+        return handleType === 'folder'
     }
 
+    // 获取拖拽的文件
+    const [fileList, setFileList] = useState<FileList>()
+    const [uploadsVisible, setUploadsVisible] = useState<boolean>(false)
+    const { isDragOver, dropAreaProps } = useFileDrop((fileList) => {
+        console.log('fileList--->', fileList)
+        setFileList(fileList)
+        setUploadsVisible(true)
+    });
+
     return <div className={style.material}>
         <div className={style.operates}>
             <div className={style.left_bts}>
-                {!!selectedKeys?.[0] && <Checkbox
+                <Checkbox
                     onChange={(e) => {
                         setHandleType(e.target.checked ? 'folder' : 'file')
+                        setMaterialParams({ pageNum: 1, pageSize: 30 })
                         setBatchFolderVisible(false)
                         cancelSelect()
                     }}
                     checked={handleType === 'folder'}
-                ><span style={{ fontSize: 12 }}>是否操作文件夹</span></Checkbox>}
-                {(!selectedKeys?.[0] || handleType === 'folder') && <Button onClick={() => onAddFolder?.()} disabled={folderCreateBy ? !isPermission(folderCreateBy) : false}>新建文件夹</Button>}
-                {!selectedKeys?.[0] && <>
+                ><span style={{ fontSize: 12 }}>是否操作文件夹</span></Checkbox>
+                {handleType === 'folder' && <Button onClick={() => onAddFolder?.()} disabled={folderCreateBy ? !isPermission(folderCreateBy) : false}>新建文件夹</Button>}
+                {(!selectedKeys?.[0] && handleType === 'folder') && <>
                     <Button type="primary" disabled={folderCreateBy ? !isPermission(folderCreateBy) : false} onClick={() => setUploadVisible(true)}>上传素材</Button>
-                    {(searchParams.keyword && searchParams.searchType) && <Button onClick={() => { setBatchFolderVisible(true); setBatchType('file') }} disabled={folderCreateBy ? !isPermission(folderCreateBy) : false}>批量操作素材</Button>}
                 </>}
                 {handleType === 'folder' ? <>
                     {!!selectedKeys?.[0] && <Button onClick={() => { setBatchFolderVisible(true); setBatchType('folder') }} disabled={folderCreateBy ? !isPermission(folderCreateBy) : false}>批量操作文件夹</Button>}
                 </> : <>
-                    {!!selectedKeys?.[0] && <>
-                        <Button type="primary" disabled={folderCreateBy ? !isPermission(folderCreateBy) : false} onClick={() => setUploadVisible(true)}>上传素材</Button>
-                        <Button onClick={() => { setBatchFolderVisible(true); setBatchType('file') }} disabled={folderCreateBy ? !isPermission(folderCreateBy) : false}>批量操作素材</Button>
-                    </>}
+                    <Button type="primary" disabled={folderCreateBy ? !isPermission(folderCreateBy) : false} onClick={() => setUploadVisible(true)}>上传素材</Button>
+                    <Button onClick={() => { setBatchFolderVisible(true); setBatchType('file') }} disabled={folderCreateBy ? !isPermission(folderCreateBy) : false}>批量操作素材</Button>
                 </>}
             </div>
+            <div className={style.left_bts}>
+                <Button type="link" loading={getMaterialList.loading} onClick={() => {
+                    getMaterialList.refresh()
+                }}>刷新</Button>
+            </div>
         </div>
         {batchFolderVisible ? <div className={style.operates}>
             <div className={style.left_bts}>
@@ -268,7 +302,7 @@ const Material = forwardRef(({ onAddFolder, onUpdateFolder, onDelFolder }: Props
                         setIndeterminateFolder(false)
                         setCheckFolderAll(e.target.checked)
                     }}
-                    disabled={batchType === 'folder' ? false : !getMaterialList?.data?.records?.some((item: { createBy: any }) => isPermission(item.createBy))}
+                    disabled={batchType === 'folder' ? folderList?.length ? false : true : !getMaterialList?.data?.records?.some((item: { createBy: any }) => isPermission(item.createBy))}
                     indeterminate={indeterminateFolder}
                     checked={checkFolderAll}
                 >全选</Checkbox>
@@ -284,8 +318,14 @@ const Material = forwardRef(({ onAddFolder, onUpdateFolder, onDelFolder }: Props
                     setBatchFolderVisible(false)
                 }}>完成</Button>
             </div>
-        </div> : (searchParams.keyword && searchParams.searchType) ? <div className={style.operates}>
-            <span>搜索「{searchParams.searchType === 'file' ? '素材' : '文件夹'}」:{searchParams.keyword}</span>
+        </div> : (materialParams?.materialName || materialParams?.materialType || (materialParams?.designerIds && materialParams?.designerIds?.length > 0) || materialParams?.width || materialParams?.height) ? <div className={style.operates}>
+            <span>搜索「素材」
+                {materialParams?.materialName && <span style={{ fontSize: 12 }}>关键词【{materialParams.materialName}】</span>}
+                {(materialParams?.designerIds && materialParams?.designerIds?.length > 0) && <span style={{ fontSize: 12 }}>设计师ID【{materialParams.designerIds.join(',')}】</span>}
+                {materialParams?.materialType && <span style={{ fontSize: 12 }}>素材类型【{materialParams.materialType === 'video' ? '视频' : '图片'}】</span>}
+                {materialParams?.width && <span style={{ fontSize: 12 }}>素材宽【{materialParams.width}】</span>}
+                {materialParams?.height && <span style={{ fontSize: 12 }}>素材高【{materialParams.height}】</span>}
+            </span>
         </div> : <div className={style.operates}>
             <Breadcrumb>
                 {breadcrumdData.map((item, index) => <Breadcrumb.Item key={item.key}>
@@ -301,10 +341,11 @@ const Material = forwardRef(({ onAddFolder, onUpdateFolder, onDelFolder }: Props
         </div>}
         <div className={`${style.content} content_global`}>
             <Spin spinning={getFolderList.loading || getMaterialList.loading}>
-                <div className={style.content_scroll} ref={ref}>
-                    <Checkbox.Group value={checkedFolderList} style={{ width: '100%' }} onChange={onCheckboxChange}>
+                <div className={style.content_scroll} ref={ref} {...dropAreaProps}>
+                    {isDragOver && <div className={`${style.placeholder} ${isDragOver ? style.dragOver : ''}`}><span>拖动文件到这里</span></div>}
+                    {(isShowFolder() && folderList.length > 0) || (!isShowFolder() && getMaterialList?.data?.records?.length > 0) ? <Checkbox.Group value={checkedFolderList} style={{ width: '100%' }} onChange={onCheckboxChange}>
                         <div className={style.content_scroll_div}>
-                            {isShowFolder() && !(searchParams.keyword && searchParams.searchType) ? folderList.map((item) => <div key={item.id} className={style.content_row} style={{ width: rowNum ? (1 / rowNum * 100) + '%' : 200 }}>
+                            {isShowFolder() ? folderList.map((item) => <div key={item.id} className={style.content_row} style={{ width: rowNum ? (1 / rowNum * 100) + '%' : 200 }}>
                                 <Card
                                     hoverable
                                     bodyStyle={{ padding: 0 }}
@@ -355,29 +396,30 @@ const Material = forwardRef(({ onAddFolder, onUpdateFolder, onDelFolder }: Props
                                         </div>
                                         <img src={item.materialType === 'image' ? item.ossUrl : getVideoImgUrl(item.ossUrl)} className={style.coverImg} alt="" />
                                     </div>}
-                                    onClick={() => {
-
-                                    }}
+                                    onClick={() => { setDetailsData({ visible: true, data: item }) }}
                                 >
                                     <div className={style.body}>
-                                        <Text ellipsis>{item?.materialName}</Text>
+                                        <div className={style.title}><Text ellipsis>{item?.materialName}</Text></div>
+                                        <a className={style.detailBt}>详情</a>
                                     </div>
                                     <div className={style.actions}>
                                         <div style={{ height: 22 }}></div>
-                                        {isPermission(item.createBy) ? <Dropdown menu={{
-                                            items: getItemsFile(item)
-                                        }}>
-                                            <a onClick={e => e.preventDefault()} style={{ fontSize: 11 }}>更多</a>
+                                        {isPermission(item.createBy) ? <Dropdown
+                                            menu={{
+                                                items: getItemsFile(item)
+                                            }}
+                                        >
+                                            <a onClick={e => { e.preventDefault(); e.stopPropagation() }} style={{ fontSize: 11 }}>更多</a>
                                         </Dropdown> : <a style={{ fontSize: 11 }}>无权限操作</a>}
                                     </div>
                                 </Card>
                             </div>)}
                         </div>
-                    </Checkbox.Group>
+                    </Checkbox.Group> : <div style={{ height: '100%', width: '100%', alignContent: 'center' }}><Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /></div>}
                 </div>
             </Spin>
         </div>
-        {((handleType === 'file' && !!selectedKeys?.[0]) || (!selectedKeys?.[0] && searchParams.keyword && searchParams.searchType)) && <div className={style.fotter}>
+        {handleType === 'file' && <div className={style.fotter}>
             <Pagination
                 size="small"
                 total={getMaterialList?.data?.totalRow || 0}
@@ -454,6 +496,35 @@ const Material = forwardRef(({ onAddFolder, onUpdateFolder, onDelFolder }: Props
                 setUpdateFileData({ visible: false, initialValues: {} })
             }}
         />}
+
+        {/* 素材详情 */}
+        {detailsData?.visible && <Details
+            {...detailsData}
+            onClose={() => {
+                setDetailsData({ visible: false, data: {} })
+            }}
+            onChange={() => {
+                getMaterialList.refresh()
+            }}
+        />}
+
+        {/* 拖动上传 */}
+        {uploadsVisible && <UploadsTable
+            folderId={breadcrumdData[breadcrumdData.length - 1].key}
+            userId={(initialState?.currentUser?.userId || 0) as number}
+            visible={uploadsVisible}
+            fileList={fileList}
+            isPermission={!!(folderCreateBy && isPermission(folderCreateBy))}
+            onClose={() => {
+                setUploadsVisible(false)
+                setFileList(undefined)
+            }}
+            onChange={() => {
+                setUploadsVisible(false)
+                setFileList(undefined)
+                getMaterialList.refresh()
+            }}
+        />}
     </div>
 })
 

+ 38 - 0
src/pages/launchSystemV3/material/cloudNew/playVideo.tsx

@@ -0,0 +1,38 @@
+import { Modal } from "antd"
+import React, { useState } from "react"
+import '../../tencentAdPutIn/index.less'
+
+interface Props {
+    children: (onPlay: () => void) => JSX.Element
+    videoUrl: string
+}
+const PlayVideo: React.FC<Props> = ({ children, videoUrl }) => {
+
+    /*******************************/
+    const [visible, setVisible] = useState<boolean>(false)
+    /*******************************/
+
+    return <>
+        {children(() => { setVisible(true) })}
+
+        {visible && <div
+            onClick={(e) => {
+                e.stopPropagation()
+                e.preventDefault()
+            }}
+        >
+            <Modal
+                open={visible}
+                onCancel={() => setVisible(false)}
+                footer={null}
+                width={400}
+                className="modalResetCss"
+                bodyStyle={{ padding: '50px 10px 5px' }}
+            >
+                <video src={videoUrl} controls style={{ width: '100%' }}></video>
+            </Modal>
+        </div>}
+    </>
+}
+
+export default React.memo(PlayVideo)

+ 86 - 20
src/pages/launchSystemV3/material/cloudNew/search.tsx

@@ -1,37 +1,103 @@
-import { Card, Input, Select } from "antd"
-import React, { useContext } from "react"
+import { Button, Card, Form, Input, InputNumber, Select, Space } from "antd"
+import React, { useEffect, useContext } from "react"
+import { getUserAllApi } from "@/services/operating/account";
+import { useAjax } from "@/Hook/useAjax";
+import { SearchOutlined } from "@ant-design/icons";
+import { useUpdateEffect } from "ahooks";
 import { DispatchCloudNew } from ".";
 
 interface Props {
-    onSearch?: (value: CLOUDNEW.SearchProps) => void
+    onSearch?: (value: Partial<CLOUDNEW.GetMaterialListProps>) => void
 }
+
+// 素材管理搜索
 const Search: React.FC<Props> = ({ onSearch }) => {
 
     /**********************************/
-    const { searchParams, setSearchParams } = useContext(DispatchCloudNew)!;
+    const [form] = Form.useForm();
+    const { handleType } = useContext(DispatchCloudNew)!;
+
+    const getUserAll = useAjax(() => getUserAllApi())
     /**********************************/
 
+    useUpdateEffect(() => {
+        form.resetFields()
+    }, [handleType])
+
+    useEffect(() => {
+        getUserAll.run()
+    }, [])
+
+    const handleOk = (values: any) => {
+        onSearch?.(values)
+    }
 
     return <Card
-        bodyStyle={{ padding: '5px 10px', overflow: 'auto hidden', display: 'flex', gap: 6, flexWrap: 'wrap' }}
+        bodyStyle={{ padding: '10px', overflow: 'auto hidden', display: 'flex', gap: 6, flexWrap: 'wrap' }}
         className="cardResetCss buttonResetCss"
         bordered
     >
-        <Input.Group compact>
-            <Select value={searchParams.searchType} onChange={(e) => setSearchParams({ ...searchParams, searchType: e })} style={{ width: 100 }}>
-                {/* <Select.Option value="folder">搜文件夹</Select.Option> */}
-                <Select.Option value="file">搜素材</Select.Option>
-            </Select>
-            <Input.Search
-                enterButton
-                style={{ width: 220 }}
-                onSearch={(value) => {
-                    onSearch?.({ ...searchParams, keyword: value })
-                }}
-                allowClear
-                placeholder="请输入关键词"
-            />
-        </Input.Group>
+        <Form
+            layout="inline"
+            name="basicSearch"
+            colon={false}
+            form={form}
+            onFinish={handleOk}
+        >
+            <Form.Item>
+                <Input.Group compact>
+                    <Select defaultValue={'file'} style={{ width: 100 }}>
+                        <Select.Option value="file">搜素材</Select.Option>
+                    </Select>
+                    <Form.Item name={'materialName'} style={{ marginRight: 0 }}>
+                        <Input style={{ width: 220 }} allowClear placeholder="请输入关键词" />
+                    </Form.Item>
+                </Input.Group>
+            </Form.Item>
+            <Form.Item name={'designerIds'}>
+                <Select
+                    placeholder="设计师"
+                    filterOption={(input, option) =>
+                        ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                    }
+                    style={{ minWidth: 120 }}
+                    maxTagCount={1}
+                    mode="multiple"
+                    allowClear
+                    options={getUserAll?.data?.map((item: { nickname: any; userId: any }) => ({ label: item.nickname, value: item.userId }))}
+                />
+            </Form.Item>
+
+            <Form.Item name="materialType">
+                <Select
+                    placeholder="素材类型"
+                    filterOption={(input, option) =>
+                        ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                    }
+                    allowClear
+                    style={{ width: 120 }}
+                    options={[
+                        { label: '图片', value: 'image' },
+                        { label: '视频', value: 'video' }
+                    ]}
+                />
+            </Form.Item>
+
+            <Form.Item name="width">
+                <InputNumber addonBefore="素材宽" style={{ width: 180 }} placeholder="请输入素材宽" />
+            </Form.Item>
+
+            <Form.Item name="height">
+                <InputNumber addonBefore="素材高" style={{ width: 180 }} placeholder="请输入素材高" />
+            </Form.Item>
+
+            <Form.Item>
+                <Space>
+                    <Button onClick={() => form.resetFields()}>重置</Button>
+                    <Button type="primary" htmlType="submit" icon={<SearchOutlined />} className="modalResetCss">搜索</Button>
+                </Space>
+            </Form.Item>
+        </Form>
     </Card>
 }
 

+ 364 - 0
src/pages/launchSystemV3/material/cloudNew/selectCloudNew.tsx

@@ -0,0 +1,364 @@
+import { Button, Card, Checkbox, Divider, Empty, Form, message, Modal, Pagination, Popover, Radio, Select, Space, Spin, 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 { useLocalStorageState, 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 PlayVideo from "./playVideo"
+import { showFieldList } from "./const"
+
+
+const { Text, Paragraph } = Typography;
+
+/**
+ * 选择素材
+ * @param param0 
+ * @returns 
+ */
+const SelectCloudNew: React.FC<CLOUDNEW.SelectCloudNewProps> = ({ visible, defaultParams, num, sliderImgContent, isGroup, onChange, onClose }) => {
+
+    /************************************/
+    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 [queryParams, setQueryParams] = useState<CLOUDNEW.GetMaterialDataListProps>({ pageNum: 1, pageSize: 20 })
+    const [searchParams, setSearchParams] = useState<Partial<CLOUDNEW.GetMaterialDataListProps>>({})
+    const [uploadVisible, setUploadVisible] = useState<boolean>(false)
+    const [showField, setShowField] = useLocalStorageState<string[]>('show-field', ['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] = useLocalStorageState<{ sortField: string, sortType: boolean }>('sort-data', { sortField: 'material.create_time', sortType: false });
+
+    const getMaterialDataList = useAjax((params) => getMaterialDataListApi(params))
+    /************************************/
+
+    useEffect(() => {
+        if (sliderImgContent && sliderImgContent?.length > 0) {
+            setCheckedFolderList(sliderImgContent as any || [])
+        }
+    }, [sliderImgContent])
+
+    useEffect(() => {
+        getMaterialDataList.run({ ...sortData, ...searchParams, ...defaultParams, ...queryParams })
+    }, [queryParams, defaultParams, searchParams, sortData])
+
+    // 根据内容宽度计算列数
+    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) {
+            message.error(`当前选择了组图${num}图,当前选择了${checkedFolderList.length}个图片`)
+            return
+        }
+        onChange?.(checkedFolderList)
+    }
+
+    // 文件夹选择
+    const onCheckboxChange = (checked: boolean, item: any) => {
+        let newCheckedFolderList: any[] = JSON.parse(JSON.stringify(checkedFolderList))
+        if (checked) { // 选中
+            newCheckedFolderList.push(item)
+        } 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={<Space>
+            <strong>素材库</strong>
+            <a onClick={() => setUploadVisible(true)}>上传素材</a>
+        </Space>}
+        open={visible}
+        onCancel={onClose}
+        width={1200}
+        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 => <div key={item.id} className={style.selectedCloud_col}>
+                            <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 : getVideoImgUrl(item.oss_url)} className={style.coverImg} alt="" />
+                        </div>)}
+                    </div>
+                </div>
+                <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' }}
+    >
+        <div className={style.select_cloudNew_layout}>
+            {/* 搜索 */}
+            <SelectSearch
+                onSearch={(value) => { 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.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(...data)
+                                        } else {
+                                            newCheckedFolderList.push(...remainData.splice(0, remainNum))
+                                        }
+                                        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}
+                                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>}
+                            <Text>「排序-{showFieldList.find(item => item.value === sortData.sortField)?.label}-{sortData.sortType ? '正序' : '倒序'}」</Text>
+                        </div>
+                        <div className={style.left_bts}>
+                            <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
+                                                    }
+                                                    setShowField(value as any)
+                                                }}
+                                            />
+                                        </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}
+                                                    value={sortData.sortField}
+                                                    onChange={(value) => {
+                                                        setSortData({ ...sortData, sortField: value as any })
+                                                    }}
+                                                />
+                                                <Radio.Group value={sortData.sortType} onChange={(e) => setSortData({ ...sortData, sortType: e.target.value })} 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}>
+                            <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?.map(item => item.id)} style={{ width: '100%' }}>
+                                    <div className={style.content_scroll_div}>
+                                        {getMaterialDataList?.data?.records.map((item: any) => {
+                                            const isSelect = checkedFolderList?.map(item => item.id).includes(item.id)
+                                            const disabled = checkedFolderList.length >= num && !isSelect
+                                            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.ctr':
+                                                                    return <Paragraph key={field} style={{ fontSize: 12, marginBottom: 1 }}>点击率:{(item?.ctr === null || item?.ctr === undefined) ? '--' : item?.ctr * 100 + '%'}</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 + '%'}</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
+                                                            }
+                                                        })}
+                                                    </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) => {
+                                setQueryParams({ ...queryParams, pageNum: page, pageSize })
+                            }}
+                        />
+                    </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()
+            }}
+        />}
+    </Modal>
+}
+
+export default React.memo(SelectCloudNew)

+ 174 - 0
src/pages/launchSystemV3/material/cloudNew/selectSearch.tsx

@@ -0,0 +1,174 @@
+import { Button, Card, Col, DatePicker, Form, Input, InputNumber, Row, Select, Space, TreeSelect } from "antd"
+import React, { useEffect, useState } from "react"
+import { getUserAllApi } from "@/services/operating/account";
+import { useAjax } from "@/Hook/useAjax";
+import { SearchOutlined } from "@ant-design/icons";
+import { getFolderListApi } from "@/services/adqV3/cloudNew";
+import { DataNode } from "antd/lib/tree";
+import { updateTreeData } from "./const";
+import moment from "moment";
+
+interface Props {
+    onSearch?: (value: Partial<CLOUDNEW.GetMaterialListProps>) => void
+}
+
+// 选择素材搜索
+const SelectSearch: React.FC<Props> = ({ onSearch }) => {
+
+    /**********************************/
+    const [form] = Form.useForm();
+    const [treeData, setTreeData] = useState<DataNode[]>([]);
+
+    const getUserAll = useAjax(() => getUserAllApi())
+    const getFolderList = useAjax((params) => getFolderListApi(params))
+    /**********************************/
+
+    useEffect(() => {
+        getFolder()
+    }, [])
+
+    const getFolder = () => {
+        getFolderList.run({}).then(res => {
+            setTreeData(() => res?.map((item: { folderName: any; id: any; createBy: number }) => ({
+                title: item.folderName,
+                value: item.id,
+                key: item.id,
+            })) || [])
+        })
+    }
+
+    // 下级目录
+    const handleUpdateFolder = (parentId: number) => {
+        return getFolderListApi({ parentId }).then(res => {
+            setTreeData(origin =>
+                updateTreeData(origin, parentId, res?.data?.map((item: { folderName: any; id: any; createBy: number }) => ({
+                    title: item.folderName,
+                    value: item.id,
+                    key: item.id,
+                })) || []),
+            );
+        })
+    }
+
+    useEffect(() => {
+        getUserAll.run()
+    }, [])
+
+    const handleOk = (values: any) => {
+        console.log(values)
+        let params: any = []
+        Object.keys(values).forEach(key => {
+            let value = values[key]
+            if (['accountIds', 'adgroupIds', 'dynamicCreativeIds', 'tencentMaterialId'].includes(key) && value) {
+                let value1 = value.replace(/[,,\s]/g, ',')
+                params[key] = value1.split(',').filter((a: any) => a)
+            } else if ('uploadTime' === key && value?.length === 2) {
+                params.uploadTimeMin = moment(value?.[0]).format('YYYY-MM-DD')
+                params.uploadTimeMax = 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={'materialName'}>
+                    <Input style={{ width: 190 }} allowClear placeholder="请输入关键词" />
+                </Form.Item></Col>
+                <Col><Form.Item name={'folderId'}>
+                    <TreeSelect
+                        loading={getFolderList.loading}
+                        allowClear
+                        style={{ width: 130 }}
+                        dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
+                        placeholder="请选择文件夹"
+                        loadData={({ value }) => {
+                            return new Promise<void>(async (resolve) => {
+                                await handleUpdateFolder(Number(value))
+                                resolve()
+                            })
+                        }}
+                        dropdownMatchSelectWidth={false}
+                        treeData={treeData}
+                    />
+                </Form.Item></Col>
+                <Col><Form.Item name={'designerIds'}>
+                    <Select
+                        placeholder="设计师"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                        }
+                        style={{ width: 190 }}
+                        maxTagCount={1}
+                        mode="multiple"
+                        allowClear
+                        options={getUserAll?.data?.map((item: { nickname: any; userId: any }) => ({ label: item.nickname, value: item.userId }))}
+                    />
+                </Form.Item></Col>
+                <Col><Form.Item name={'sysUserIds'}>
+                    <Select
+                        placeholder="投手"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                        }
+                        style={{ width: 190 }}
+                        maxTagCount={1}
+                        mode="multiple"
+                        allowClear
+                        options={getUserAll?.data?.map((item: { nickname: any; userId: any }) => ({ label: item.nickname, value: item.userId }))}
+                    />
+                </Form.Item></Col>
+                <Col><Form.Item name={'useStatus'}>
+                    <Select
+                        placeholder="使用情况"
+                        filterOption={(input, option) =>
+                            ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                        }
+                        style={{ minWidth: 120 }}
+                        allowClear
+                        options={[{ label: '未使用', value: 0 }, { label: '无消耗', value: 1 }, { label: '有消耗', value: 2 }]}
+                    />
+                </Form.Item></Col>
+                <Col><Form.Item name={'accountIds'}>
+                    <Input.TextArea rows={1} style={{ width: 190 }} allowClear placeholder="广告账号(多个逗号、换行等隔开)" />
+                </Form.Item></Col>
+                <Col><Form.Item name={'adgroupIds'}>
+                    <Input.TextArea rows={1} style={{ width: 190 }} allowClear placeholder="广告ID(多个逗号、换行等隔开)" />
+                </Form.Item></Col>
+                <Col><Form.Item name={'dynamicCreativeIds'}>
+                    <Input.TextArea rows={1} style={{ width: 190 }} allowClear placeholder="创意ID(多个逗号、换行等隔开)" />
+                </Form.Item></Col>
+                <Col><Form.Item name={'tencentMaterialId'}>
+                    <Input.TextArea rows={1} style={{ width: 190 }} allowClear placeholder="腾讯素材ID(多个逗号、换行等隔开)" />
+                </Form.Item></Col>
+                <Col><Form.Item name={'minCost'}>
+                    <InputNumber style={{ width: 80 }} placeholder="最小消耗" />
+                </Form.Item></Col>
+                <Col><Form.Item name={'uploadTime'}>
+                    <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(SelectSearch)

+ 88 - 0
src/pages/launchSystemV3/material/cloudNew/tableConfig.tsx

@@ -0,0 +1,88 @@
+import { Statistic } from "antd";
+import { ColumnsType } from "antd/lib/table";
+import React from "react";
+
+
+const columns = (): ColumnsType<any> => {
+
+    return [
+        {
+            title: '创意名称',
+            dataIndex: 'dynamic_creative_name',
+            key: 'dynamic_creative_name',
+            width: 100,
+            ellipsis: true
+        },
+        {
+            title: '创意ID',
+            dataIndex: 'dynamic_creative_id',
+            key: 'dynamic_creative_id',
+            width: 80,
+            ellipsis: true,
+            align: 'center'
+        },
+        {
+            title: '广告ID',
+            dataIndex: 'adgroup_id',
+            key: 'adgroup_id',
+            width: 80,
+            ellipsis: true,
+            align: 'center'
+        },
+        {
+            title: '素材ID',
+            dataIndex: 'material_id',
+            key: 'material_id',
+            width: 80,
+            ellipsis: true,
+            align: 'center'
+        },
+        {
+            title: '创建时间',
+            dataIndex: 'created_time',
+            key: 'created_time',
+            width: 130,
+            sorter: true,
+            ellipsis: true,
+        },
+        {
+            title: '消耗',
+            dataIndex: 'cost',
+            key: 'cost',
+            width: 75,
+            sorter: true,
+        },
+        {
+            title: '点击率',
+            dataIndex: 'ctr',
+            key: 'ctr',
+            width: 75,
+            sorter: true,
+            render: (a: number) => <Statistic value={a} precision={2} valueStyle={!a ? {} : a >= 0.5 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+        },
+        {
+            title: '目标转化成本',
+            dataIndex: 'conversions_cost',
+            key: 'conversions_cost',
+            width: 75,
+            sorter: true,
+        },
+        {
+            title: '目标转化量',
+            dataIndex: 'conversions_count',
+            key: 'conversions_count',
+            width: 75,
+            sorter: true,
+        },
+        {
+            title: '目标转化率',
+            dataIndex: 'conversions_rate',
+            key: 'conversions_rate',
+            width: 75,
+            sorter: true,
+            render: (a: number) => <Statistic value={a} precision={2} valueStyle={!a ? {} : a >= 0.5 ? { color: 'red' } : { color: '#0f990f' }} suffix="%" />
+        },
+    ];
+}
+
+export default columns

+ 3 - 2
src/pages/launchSystemV3/material/cloudNew/uploadFile.tsx

@@ -18,8 +18,8 @@ import { DataNode } from "antd/lib/tree"
 import { updateTreeData } from "./const"
 
 interface Props {
-    folderId: number
     userId: number,
+    folderId?: number
     visible?: boolean
     onChange?: () => void
     onClose?: () => void
@@ -219,7 +219,7 @@ const UploadFile: React.FC<Props> = ({ visible, onClose, folderId, userId, onCha
                     <Select
                         placeholder="请选择设计师(不选默认自己)"
                         filterOption={(input, option) =>
-                            ((option?.children ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                            ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
                         }
                         value={queryForm?.designerId}
                         onChange={(e => setQueryForm({ ...queryForm, designerId: e }))}
@@ -230,6 +230,7 @@ const UploadFile: React.FC<Props> = ({ visible, onClose, folderId, userId, onCha
                 <Form.Item label={<strong>文件夹</strong>}>
                     <TreeSelect
                         loading={getFolderList.loading}
+                        allowClear
                         style={{ width: '100%' }}
                         value={queryForm?.folderId}
                         dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}

+ 402 - 0
src/pages/launchSystemV3/material/cloudNew/uploadsTable.tsx

@@ -0,0 +1,402 @@
+import { Badge, Button, Card, Col, Form, message, Modal, Row, Select, Space, Table, TreeSelect } from "antd"
+import React, { useEffect, useRef, useState } from "react"
+import { compressAccurately } from "image-conversion"
+import { blobToBase64, dataURLtoFile, videoMessage } from "@/utils/compress"
+import { getImgSize } from "@/utils/utils"
+import { useAjax } from "@/Hook/useAjax"
+import { bdSysMediaAdd, getFileUrl } from "@/services/launchAdq/material"
+import request from "umi-request"
+import getMD5 from "@/components/MD5"
+import { addMaterialApi, getFolderListApi } from "@/services/adqV3/cloudNew"
+import '../../tencentAdPutIn/index.less'
+import { getUserAllApi } from "@/services/operating/account"
+import { DataNode } from "antd/lib/tree"
+import { updateTreeData } from "./const"
+
+interface Props {
+    isPermission: boolean,
+    userId: number,
+    folderId?: any
+    fileList?: FileList
+    visible?: boolean
+    onClose?: () => void
+    onChange?: () => void
+}
+const UploadsTable: React.FC<Props> = ({ isPermission, fileList, visible, onClose, onChange, folderId, userId }) => {
+
+    /*******************************/
+    const [queryForm, setQueryForm] = useState<CLOUDNEW.AddMaterialProps>({})
+    const [dataSource, setDataSource] = useState<any[]>([])
+    const [loading, setLoading] = useState<boolean>(false)
+    const tableRef = useRef(null);
+    const [treeData, setTreeData] = useState<DataNode[]>([]);
+
+    //请求上传地址
+    const getFileUrlAjx = useAjax((params: { type: string, fileType: 'video' | 'image' }) => getFileUrl(params), { manual: true })
+    const addMaterial = useAjax((params) => addMaterialApi(params))
+    const getUserAll = useAjax(() => getUserAllApi())
+    const getFolderList = useAjax((params) => getFolderListApi(params))
+    /*******************************/
+
+    useEffect(() => {
+        getUserAll.run()
+    }, [])
+
+    useEffect(() => {
+        getFolder()
+    }, [])
+
+    const getFolder = () => {
+        getFolderList.run({}).then(res => {
+            setTreeData(() => res?.map((item: { folderName: any; id: any; createBy: number }) => ({
+                title: item.folderName,
+                value: item.id,
+                key: item.id,
+                disabled: userId.toString() !== item.createBy.toString()
+            })) || [])
+        })
+    }
+
+    // 下级目录
+    const handleUpdateFolder = (parentId: number) => {
+        return getFolderListApi({ parentId }).then(res => {
+            setTreeData(origin =>
+                updateTreeData(origin, parentId, res?.data?.map((item: { folderName: any; id: any; createBy: number }) => ({
+                    title: item.folderName,
+                    value: item.id,
+                    key: item.id,
+                    disabled: userId.toString() !== item.createBy.toString()
+                })) || []),
+            );
+        })
+    }
+
+    const scrollToRow = (key: number) => {
+        if (tableRef.current) {
+            const tableBody = (tableRef.current as any).querySelector('.ant-table-body');
+            const row = tableBody.querySelector(`.ant-table-row[data-row-key="${key}"]`);
+
+            if (row) {
+                row.scrollIntoView({ behavior: 'smooth', block: 'center' });
+            }
+        }
+    };
+
+    useEffect(() => {
+        if (fileList?.length) {
+            setDataSource([...Array.from(fileList)].map((file, index) => {
+                let type = 'default'
+                let mediaTypeName = '其它文件'
+                if (file.type.startsWith('image/')) {
+                    mediaTypeName = '图片'
+                } else if (file.type.startsWith('video/')) {
+                    mediaTypeName = '视频'
+                } else {
+                    type = 'warning'
+                }
+                return { id: index + 1, file, type, mediaTypeName }
+            }))
+        } else {
+            setDataSource([])
+        }
+    }, [fileList])
+
+    // 上传方法
+    const upload = (file: File) => {
+        return new Promise(async (resolve) => {
+            let fileSize = 0
+            if (file.type.startsWith('image/')) {
+                fileSize = 409600
+            } else {
+                fileSize = 524288000
+            }
+
+            if (file.type.startsWith('image/')) {
+                if (file?.size > fileSize) { // 大于400kb进入压缩
+                    let bole = await compressAccurately(file, fileSize / 1024 - 50)
+                    if (bole?.size > fileSize) {
+                        bole = await compressAccurately(file, fileSize / 1024 - 100)
+                    }
+                    if (bole?.size > fileSize) {
+                        bole = await compressAccurately(file, fileSize / 1024 - 150)
+                    }
+                    if (bole?.size > fileSize) {
+                        bole = await compressAccurately(file, fileSize / 1024 - 200)
+                    }
+                    let newFile = await blobToBase64(bole)
+                    file = await dataURLtoFile(newFile, file?.name)
+                }
+            } else if (file.type.startsWith('video/')) {
+                if (file?.size > fileSize) { // 大于500mb
+                    return resolve(`选择的视频大于${fileSize / 1024 / 1024}MB,请重新选择提交`)
+                }
+            }
+            setLoading(() => true)
+            let width = 0
+            let height = 0
+            let videoDuration = 0
+            if (file.type.startsWith('image/')) {
+                let 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
+            }
+            /**修改文件名以用户设置的文件title命名*/
+            let newFile = new File([file], file?.name, { type: file?.type })
+            let formData = new FormData();
+            /**向阿里云请求上传地址*/
+            getFileUrlAjx.run({ type: newFile.type, fileType: file.type.startsWith('video/') ? 'video' : 'image' }).then(res1 => {
+                Object.keys(res1).forEach((key: string) => {
+                    if (key !== 'url') {
+                        formData.append(key, res1[key])
+                    }
+                })
+                formData.append('file', newFile)
+                /**向阿里云返回的上传地址上传文件*/
+                request(res1?.ossUrl, { method: 'post', body: formData }).then(async (res2: { code: number, data: { url: string } }) => {
+                    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 }
+                            delete obj?.file
+                            if (!obj?.folderId) obj.folderId = folderId
+                            if (!obj?.designerId) obj.designerId = userId;
+                            if (file.type.startsWith('video/')) obj.videoDuration = videoDuration;
+                            if (!obj?.materialName) obj.materialName = file.name;
+                            if (obj?.materialName && obj.materialName.match(RegExp(/[<>&\\'"/\x08\x09\x0A\x0D\x7F]/g))) {
+                                obj.materialName = obj.materialName.replace(RegExp(/[<>&\\'"/\x08\x09\x0A\x0D\x7F]/g), '')
+                            }
+                            obj.materialName = (obj as any).materialName.replace(/\.(jpg|jpeg|gif|png|mp4)$/i, '')
+                            addMaterial.run(obj).then((res) => {
+                                if (res) {
+                                    resolve('')
+                                }
+                                resolve(`上传失败`)
+                            }).catch(() => resolve(`上传失败`))
+                        }
+                    } else {
+                        resolve(`上传失败`)
+                    }
+                }).catch(() => resolve(`上传失败`))
+            }).catch(() => resolve(`上传失败`))
+        })
+    }
+
+    // 点击上传
+    const handleOk = () => {
+        if (!folderId && !queryForm?.folderId) {
+            message.error('当前不能上传素材,请选择文件夹')
+            return
+        }
+        if (!isPermission) {
+            message.error('当前文件夹没有操作权限,请选择文件夹')
+            return
+        }
+        setLoading(true)
+        async function uoload(index: number) {
+            let fileData = dataSource[index]
+            scrollToRow(index)
+            if (fileData?.type === 'default') {
+                setDataSource(data => {
+                    const newData = [...data]
+                    newData[index].type = 'processing'
+                    return newData
+                })
+                let error = await upload(fileData.file)
+                setDataSource(data => {
+                    const newData = [...data]
+                    newData[index].type = error ? 'error' : 'success'
+                    newData[index].error = error
+                    return newData
+                })
+            }
+            if (dataSource.length > index) {
+                uoload(index + 1)
+            } else { // 上传完成
+                setLoading(false)
+                onChange?.()
+            }
+        }
+        uoload(0)
+    }
+
+    return <Modal
+        title={<strong>上传素材预览</strong>}
+        open={visible}
+        className='modalResetCss'
+        width={1100}
+        closable={false}
+        maskClosable={false}
+        bodyStyle={{ backgroundColor: '#f1f4fc', maxHeight: 650, overflow: 'hidden', overflowY: 'auto', padding: '10px' }}
+        footer={<Space>
+            {!loading && <Button onClick={onClose}>取消</Button>}
+            <Button type="primary" onClick={handleOk} loading={loading}>上传</Button>
+        </Space>}
+    >
+        <Space direction="vertical" style={{ width: '100%' }}>
+            <Card className="cardResetCss">
+                <Form
+                    labelAlign='left'
+                    labelCol={{ span: 4 }}
+                    layout="horizontal"
+                    colon={false}
+                    style={{}}
+                >
+                    <Row gutter={20}>
+                        <Col span={12}>
+                            <Form.Item label={<strong>设计师</strong>}>
+                                <Select
+                                    placeholder="请选择设计师(不选默认自己)"
+                                    filterOption={(input, option) =>
+                                        ((option?.label ?? '') as any).toLowerCase().includes(input.toLowerCase())
+                                    }
+                                    value={queryForm?.designerId}
+                                    onChange={(e => setQueryForm({ ...queryForm, designerId: e }))}
+                                    options={getUserAll?.data?.map((item: { nickname: any; userId: any }) => ({ label: item.nickname, value: item.userId }))}
+                                />
+                            </Form.Item>
+                        </Col>
+                        <Col span={12}>
+                            <Form.Item label={<strong>文件夹</strong>}>
+                                <TreeSelect
+                                    loading={getFolderList.loading}
+                                    allowClear
+                                    style={{ width: '100%' }}
+                                    value={queryForm?.folderId}
+                                    dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
+                                    placeholder="请选择文件夹(不选择默认当前文件夹)"
+                                    onChange={(e) => {
+                                        setQueryForm({ ...queryForm, folderId: e })
+                                    }}
+                                    loadData={({ value }) => {
+                                        return new Promise<void>(async (resolve) => {
+                                            await handleUpdateFolder(Number(value))
+                                            resolve()
+                                        })
+                                    }}
+                                    treeData={treeData}
+                                />
+                            </Form.Item>
+                        </Col>
+                    </Row>
+                </Form>
+            </Card>
+            <Card className="cardResetCss">
+                <Table
+                    dataSource={dataSource}
+                    size="small"
+                    rowKey={'id'}
+                    bordered
+                    ref={tableRef}
+                    scroll={{ y: 450 }}
+                    pagination={{
+                        defaultPageSize: 100
+                    }}
+                    columns={[
+                        {
+                            title: '序号',
+                            dataIndex: 'id',
+                            key: 'id',
+                            width: 60,
+                            align: 'center',
+                            fixed: 'left',
+                            render: text => <span style={{ fontSize: 12 }}>{text}</span>
+                        },
+                        {
+                            title: '上传状态',
+                            dataIndex: 'type',
+                            key: 'type',
+                            width: 130,
+                            fixed: 'left',
+                            render(value) {
+                                switch (value) {
+                                    case 'success':
+                                        return <Badge status="success" text={<span style={{ fontSize: 12 }}>上传成功</span>} />
+                                    case 'processing':
+                                        return <Badge status="processing" text={<span style={{ fontSize: 12 }}>正在上传...</span>} />
+                                    case 'error':
+                                        return <Badge status="error" text={<span style={{ fontSize: 12 }}>上传失败</span>} />
+                                    case 'error_qx':
+                                        return <Badge status="error" text={<span style={{ fontSize: 12 }}>取消上传</span>} />
+                                    case 'warning':
+                                        return <Badge status="warning" text={<span style={{ fontSize: 12 }}>{`当前只能上传图片和视频`}</span>} />
+                                    default:
+                                        return <Badge status="default" text={<span style={{ fontSize: 12 }}>等待上传</span>} />
+                                }
+                            },
+                        },
+                        {
+                            title: '素材类型',
+                            dataIndex: 'mediaTypeName',
+                            key: 'mediaTypeName',
+                            width: 75,
+                            align: 'center',
+                            render: text => <span style={{ fontSize: 12 }}>{text}</span>
+                        },
+                        {
+                            title: '素材名称',
+                            dataIndex: 'file',
+                            key: 'file',
+                            width: 150,
+                            render(value, record) {
+                                if (record.mediaTypeName === '图片' || record.mediaTypeName === '视频') {
+                                    return <span style={{ fontSize: 12 }}>{value?.name}</span>
+                                }
+                                return <span style={{ fontSize: 12 }}>其它文件</span>
+                            },
+                        },
+                        {
+                            title: '素材',
+                            dataIndex: 'file',
+                            key: 'file',
+                            width: 230,
+                            render(value, record) {
+                                if (record.mediaTypeName === '图片') {
+                                    return <img src={URL.createObjectURL(value)} style={{ height: 100, maxWidth: 200 }} />
+                                } else if (record.mediaTypeName === '视频') {
+                                    return <video src={URL.createObjectURL(value)} style={{ maxWidth: 200, height: 100 }}></video>
+                                }
+                                return <span style={{ fontSize: 12 }}>其它文件</span>
+                            },
+                        },
+                        {
+                            title: '错误信息',
+                            dataIndex: 'error',
+                            key: 'error',
+                            width: 300,
+                            render: text => <span style={{ fontSize: 12 }}>{text || '--'}</span>
+                        },
+                        {
+                            title: '操作',
+                            dataIndex: 'cz',
+                            key: 'cz',
+                            width: 70,
+                            align: 'center',
+                            fixed: 'right',
+                            render(_, record) {
+                                if (record.type === 'default') {
+                                    return <a
+                                        style={{ fontSize: 12, color: 'red' }}
+                                        onClick={() => {
+                                            setDataSource(data => {
+                                                return data.map(item => item.id === record.id ? { ...item, type: 'error_qx' } : item)
+                                            })
+                                        }}
+                                    >取消上传</a>
+                                }
+                                return '--'
+                            },
+                        },
+                    ]}
+                />
+            </Card>
+        </Space>
+    </Modal>
+}
+
+export default React.memo(UploadsTable)

+ 54 - 2
src/pages/launchSystemV3/material/typings.d.ts

@@ -20,8 +20,6 @@ declare namespace CLOUDNEW {
         setBatchFolderVisible: React.Dispatch<React.SetStateAction<boolean>>
         handleType: "folder" | "file"
         setHandleType: React.Dispatch<React.SetStateAction<"folder" | "file">>
-        searchParams: SearchProps
-        setSearchParams: React.Dispatch<React.SetStateAction<SearchProps>>
         materialParams: GetMaterialListProps
         setMaterialParams: React.Dispatch<React.SetStateAction<GetMaterialListProps>>
     }
@@ -55,6 +53,30 @@ declare namespace CLOUDNEW {
         width?: number
         materialName?: string   // 素材名称
         materialType?: string   // 素材类型
+        sizeQueries?: { width?: number, height?: number, relation: string }[]
+    }
+    interface GetMaterialDataListProps {
+        pageNum: number,
+        pageSize: number,
+        columns?: string[]              // 列
+        accountIds?: number[]          // 广告账号
+        adgroupIds?: number[]          // 广告id
+        dynamicCreativeIds?: number[]  // 创意id
+        sysUserIds?: number[]    // 投手id
+        designerIds?: number[]  // 设计师
+        folderId?: number       // 文件夹
+        materialName?: string   // 素材名称
+        materialType?: string   // 素材类型
+        mediaType?: string
+        minCost?: number,       // 最小消耗
+        sizeQueries?: { width?: number, height?: number, relation: string }[]
+        sortField?: string,
+        sortType?: boolean,
+        tencentMaterialId?: number[]  // 腾讯素材id
+        uploadTimeMin?: string        // 图片上传时间
+        uploadTimeMax?: string
+        useStatus?: number            // 使用情况(0未使用 1无消耗 2有消耗)
+        fileSize?: number
     }
     interface AddMaterialProps {
         designerId?: number,    // 设计师
@@ -78,4 +100,34 @@ declare namespace CLOUDNEW {
         materialName: string,  // 素材名称
         description?: string,  // 描述
     }
+    interface GetCreativeReport {
+        pageNum: number,
+        pageSize: number,
+        signature?: string,
+        creativeTimeMin?: string,  // 创意创建时间
+        creativeTimeMax?: string,
+        dataTimeMix?: string,      // 素材数据时间
+        dataTimeMax?: string,
+        mediaType?: string,        // 	媒体类型
+        sortField?: string,
+        sortType?: boolean
+    }
+
+    interface SelectCloudNewProps {
+        defaultParams: {
+            sizeQueries?: {
+                width?: number,
+                height?: number,
+                relation: string
+            }[],
+            materialType: 'image' | 'video'
+            fileSize: number
+        }
+        num: number
+        sliderImgContent?: { oss_url: string, id: number, width?: number, height?: number }[]
+        isGroup?: boolean
+        visible?: boolean
+        onClose?: () => void
+        onChange?: (data: any[]) => void
+    }
 }

+ 37 - 18
src/pages/launchSystemV3/tencenTasset/profiles/index.tsx

@@ -1,14 +1,16 @@
 import { useAjax } from "@/Hook/useAjax";
 import { addProfilesApi, delProfilesApi, getProfilesApi } from "@/services/adqV3/global";
 import { PlusOutlined, SearchOutlined } from "@ant-design/icons";
-import { Button, Card, Form, Input, Modal, Select, Space, Table, message, Image, Divider, Typography } from "antd";
+import { Button, Card, Form, Input, Modal, Select, Space, Table, message, Image, Divider } from "antd";
 import React, { useEffect, useState } from "react"
 import '../../tencentAdPutIn/index.less'
 import profilesColumns from "./tableConfig";
-import SelectCloud from "@/pages/launchSystemNew/components/selectCloud";
-import { useModel } from "umi";
-const { Link } = Typography;
+import SelectCloudNew from "../../material/cloudNew/selectCloudNew";
 
+/**
+ * 头像昵称跳转页
+ * @returns 
+ */
 const Profiles: React.FC = () => {
 
 
@@ -108,27 +110,36 @@ const UploadImageOrMd5: React.FC<ImageProps> = (props) => {
     /*********************/
     const { onChange, value } = props
     const [selectImgVisible, setSelectImgVisible] = useState<boolean>(false)
-    const [sliderImgContent, setSliderImgContent] = useState<{ url: string, width?: number, height?: number }[]>([])  // 保存回填数据
-    const { init } = useModel('useLaunchAdq.useBdMediaPup')
-    /*********************/
-
-    useEffect(() => {
-        if (value && value?.imageUrl) {
-            setSliderImgContent([{ url: value.imageUrl }])
-        } else {
-            setSliderImgContent([])
+    const [selectCloudData, setSelectCloudData] = useState<{
+        defaultParams: {
+            sizeQueries?: {
+                width: number,
+                height: number,
+                relation: string
+            }[],
+            materialType: 'image' | 'video'
+            fileSize: number
         }
-    }, [value])
+        num: number
+    }>()
+    /*********************/
 
     const setImg = (content: any[]) => {
         console.log(content)
-        onChange && onChange({ imageUrl: content[0]?.url, imageMd5: content[0]?.fileMd5 })
+        onChange && onChange({ imageUrl: content[0]?.oss_url, imageMd5: content[0]?.md5 })
         setSelectImgVisible(false)
     }
 
     const selectImg = () => {
-        init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: 512, height: 512 }]], maxSize: 400 * 1024 })
-        setTimeout(() => { setSelectImgVisible(true) }, 50)
+        setSelectCloudData({
+            defaultParams: {
+                sizeQueries: [{ relation: '=', width: 512, height: 512 }],
+                fileSize: 400 * 1024,
+                materialType: 'image'
+            },
+            num: 1
+        })
+        setSelectImgVisible(true)
     }
 
     return <>
@@ -139,7 +150,15 @@ const UploadImageOrMd5: React.FC<ImageProps> = (props) => {
         </div>
 
         {/* 选择素材 */}
-        {selectImgVisible && <SelectCloud visible={selectImgVisible} sliderImgContent={sliderImgContent} onClose={() => setSelectImgVisible(false)} onChange={setImg} />}
+        {(selectImgVisible && selectCloudData) && <SelectCloudNew
+            {...selectCloudData}
+            visible={selectImgVisible}
+            onClose={() => {
+                setSelectImgVisible(false)
+                setSelectCloudData(undefined)
+            }}
+            onChange={setImg}
+        />}
     </>
 }
 

+ 74 - 34
src/pages/launchSystemV3/tencenTasset/wechatCanvasPage/copyPage.tsx

@@ -7,9 +7,9 @@ import { Button, Card, Form, message, Modal, Space, Spin } from "antd"
 import React, { useEffect, useState } from "react"
 import styles from '../../tencentAdPutIn/create/Material/index.less'
 import VideoNews from "@/pages/launchSystemNew/components/newsModal/videoNews"
-import { useModel } from "umi"
 import SelectCloud from "@/pages/launchSystemNew/components/selectCloud"
 import { batchCreateDownPageApi } from "@/services/adqV3"
+import SelectCloudNew from "../../material/cloudNew/selectCloudNew"
 
 interface Props {
     accountId: number
@@ -25,7 +25,6 @@ interface Props {
 const CopyPage: React.FC<Props> = ({ accountId, pageData, visible, onChange, onClose }) => {
 
     /********************************/
-    const { init } = useModel('useLaunchAdq.useBdMediaPup')
     const [materialConfig, setMaterialConfig] = useState<{
         adcreativeTemplateId?: number,
         type: string,
@@ -43,6 +42,18 @@ const CopyPage: React.FC<Props> = ({ accountId, pageData, visible, onChange, onC
         max: 1,//素材数量
         sliderImgContent: undefined
     })//图片素材配置
+    const [selectCloudData, setSelectCloudData] = useState<{
+        defaultParams: {
+            sizeQueries?: {
+                width: number,
+                height: number,
+                relation: string
+            }[],
+            materialType: 'image' | 'video'
+            fileSize: number
+        }
+        num: number
+    }>()
     const [selectVideoVisible, setSelectVideoVisible] = useState(false)
     const [form] = Form.useForm();
     const downPageMaterialDTOS = Form.useWatch('downPageMaterialDTOS', form)
@@ -112,7 +123,14 @@ const CopyPage: React.FC<Props> = ({ accountId, pageData, visible, onChange, onC
         title={<Space>
             <strong>批量替换顶部素材复制落地页</strong>
             {(mType?.type && ['CAROUSEL', 'IMAGE'].includes(mType?.type)) && <Button type="link" onClick={() => {
-                init({ mediaType: 'IMG', num: 100, cloudSize: [[{ relation: '=', width: mType.width, height: mType.height }]], maxSize: 307200 })
+                setSelectCloudData({
+                    defaultParams: {
+                        sizeQueries: [{ relation: '=', width: mType.width, height: mType.height }],
+                        fileSize: 307200,
+                        materialType: 'image'
+                    },
+                    num: 100
+                })
                 setMaterialConfig({
                     ...materialConfig,
                     type: 'imageId',
@@ -120,12 +138,17 @@ const CopyPage: React.FC<Props> = ({ accountId, pageData, visible, onChange, onC
                     index: 99999,
                     adcreativeTemplateId: 721
                 })
-                setTimeout(() => {
-                    setSelectVideoVisible(true)
-                }, 100)
+                setSelectVideoVisible(true)
             }}>批量添加图片素材</Button>}
             {(mType?.type && ['VIDEO'].includes(mType?.type)) && <Button type="link" onClick={() => {
-                init({ mediaType: 'VIDEO', num: 100, cloudSize: [[{ relation: '=', width: mType.width, height: mType.height }]], maxSize: 20971520 })
+                setSelectCloudData({
+                    defaultParams: {
+                        sizeQueries: [{ relation: '>=', width: mType.width, height: mType.height }],
+                        fileSize: 20971520,
+                        materialType: 'video'
+                    },
+                    num: 100
+                })
                 setMaterialConfig({
                     ...materialConfig,
                     type: 'videoId',
@@ -133,9 +156,7 @@ const CopyPage: React.FC<Props> = ({ accountId, pageData, visible, onChange, onC
                     index: 99999,
                     adcreativeTemplateId: 311
                 })
-                setTimeout(() => {
-                    setSelectVideoVisible(true)
-                }, 100)
+                setSelectVideoVisible(true)
             }}>批量添加视频素材</Button>}
         </Space>}
         open={visible}
@@ -211,7 +232,14 @@ const CopyPage: React.FC<Props> = ({ accountId, pageData, visible, onChange, onC
                                     name={[field.name, 'videoId']}
                                 >
                                     <div className={`${styles.box} ${styles.video}`} style={{ width: 300, height: 160 }} onClick={() => {
-                                        init({ mediaType: 'VIDEO', cloudSize: [[{ relation: '=', width: mType.width, height: mType.height }]], maxSize: 20971520 })
+                                        setSelectCloudData({
+                                            defaultParams: {
+                                                sizeQueries: [{ relation: '>=', width: mType.width, height: mType.height }],
+                                                fileSize: 20971520,
+                                                materialType: 'video'
+                                            },
+                                            num: 1
+                                        })
                                         setMaterialConfig({
                                             ...materialConfig,
                                             type: 'videoId',
@@ -219,9 +247,7 @@ const CopyPage: React.FC<Props> = ({ accountId, pageData, visible, onChange, onC
                                             index: num,
                                             adcreativeTemplateId: 311
                                         })
-                                        setTimeout(() => {
-                                            setSelectVideoVisible(true)
-                                        }, 100)
+                                        setSelectVideoVisible(true)
                                     }}>
                                         <div className={styles.p}>
                                             {downPageMaterialDTOS?.length > 0 && downPageMaterialDTOS[num] && Object.keys(downPageMaterialDTOS[num])?.includes('videoId') ? <VideoNews src={downPageMaterialDTOS[num]['videoId']['url']} style={{ display: 'block', width: 'auto', margin: 0, height: '100%' }} maskImgStyle={{ position: 'absolute', top: '50%', left: '50%', width: 40, height: 40, transform: 'translate(-50%, -50%)', zIndex: 10 }} /> : <>
@@ -238,7 +264,14 @@ const CopyPage: React.FC<Props> = ({ accountId, pageData, visible, onChange, onC
                                 >
                                     <Space align="end">
                                         <div className={`${styles.box} ${styles.image}`} style={{ width: 300, height: 160 }} onClick={() => {
-                                            init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: mType.width, height: mType.height }]], maxSize: 307200 })
+                                            setSelectCloudData({
+                                                defaultParams: {
+                                                    sizeQueries: [{ relation: '=', width: mType.width, height: mType.height }],
+                                                    fileSize: 307200,
+                                                    materialType: 'image'
+                                                },
+                                                num: 1
+                                            })
                                             setMaterialConfig({
                                                 ...materialConfig,
                                                 type: 'imageId',
@@ -246,9 +279,7 @@ const CopyPage: React.FC<Props> = ({ accountId, pageData, visible, onChange, onC
                                                 index: num,
                                                 adcreativeTemplateId: 721
                                             })
-                                            setTimeout(() => {
-                                                setSelectVideoVisible(true)
-                                            }, 100)
+                                            setSelectVideoVisible(true)
                                         }}>
                                             <p>
                                                 {downPageMaterialDTOS?.length > 0 && downPageMaterialDTOS[num]?.imageId?.length ? <img src={downPageMaterialDTOS[num]['imageId'][0]['url']} /> : <>
@@ -268,7 +299,14 @@ const CopyPage: React.FC<Props> = ({ accountId, pageData, visible, onChange, onC
                                     name={[field.name, 'imageId']}
                                 >
                                     <div className={`${styles.box} ${mType.number >= 3 ? styles.image_list : styles.imageMater}`} style={mType.number >= 3 ? { flexFlow: 'row', width: '100%', gap: 2 } : {}} onClick={() => {
-                                        init({ mediaType: 'IMG', num: mType.number, cloudSize: [[{ relation: '=', width: mType.width, height: mType.height }]], maxSize: 307200 })
+                                        setSelectCloudData({
+                                            defaultParams: {
+                                                sizeQueries: [{ relation: '=', width: mType.width, height: mType.height }],
+                                                fileSize: 307200,
+                                                materialType: 'image'
+                                            },
+                                            num: mType.number
+                                        })
                                         setMaterialConfig({
                                             ...materialConfig,
                                             type: 'imageId',
@@ -276,9 +314,7 @@ const CopyPage: React.FC<Props> = ({ accountId, pageData, visible, onChange, onC
                                             index: num,
                                             adcreativeTemplateId: 721
                                         })
-                                        setTimeout(() => {
-                                            setSelectVideoVisible(true)
-                                        }, 100)
+                                        setSelectVideoVisible(true)
                                     }}>
                                         {Array(mType.number).fill('').map((arr, index1) => {
                                             return <p key={index1} style={mType.number >= 3 ? { width: 130, height: 130 } : { justifyContent: 'center' }}>
@@ -311,21 +347,25 @@ const CopyPage: React.FC<Props> = ({ accountId, pageData, visible, onChange, onC
             </Form.Item>
         </Form>
 
-
-        {selectVideoVisible && <SelectCloud
-            isGroup={materialConfig?.isGroup}
+        {/* 选择素材 */}
+        {(selectVideoVisible && selectCloudData) && <SelectCloudNew
+            {...selectCloudData}
             visible={selectVideoVisible}
-            onClose={() => setSelectVideoVisible(false)}
+            isGroup={materialConfig?.isGroup}
             sliderImgContent={materialConfig.index === 99999 ? undefined :
-                materialConfig.type === 'imageId' ? downPageMaterialDTOS[materialConfig.index]?.imageId?.length ? downPageMaterialDTOS[materialConfig.index]['imageId']?.map((item: any) => item) : undefined :
-                    (downPageMaterialDTOS[materialConfig.index] && Object.keys(downPageMaterialDTOS[materialConfig.index])?.includes(materialConfig.type)) ? [{ url: downPageMaterialDTOS[materialConfig.index][materialConfig.type] }] : undefined
+                materialConfig.type === 'imageId' ? downPageMaterialDTOS[materialConfig.index]?.imageId?.length ? downPageMaterialDTOS[materialConfig.index]['imageId']?.map((item: any) => ({ oss_url: item.url, id: item.id })) : undefined :
+                    (downPageMaterialDTOS[materialConfig.index] && Object.keys(downPageMaterialDTOS[materialConfig.index])?.includes(materialConfig.type)) ? [{ oss_url: downPageMaterialDTOS[materialConfig.index][materialConfig.type]['url'], id: downPageMaterialDTOS[materialConfig.index][materialConfig.type]['id'] }] : undefined
             }
+            onClose={() => {
+                setSelectVideoVisible(false)
+                setSelectCloudData(undefined)
+            }}
             onChange={(content: any) => {
                 if (content.length > 0) {
                     console.log(content)
                     if (materialConfig.index === 99999) {
                         if (materialConfig.type === 'imageId') {
-                            let urls = content?.map((item: any) => ({ id: item?.id, url: item?.url, materialType: 0 }))
+                            let urls = content?.map((item: any) => ({ id: item?.id, url: item?.oss_url, materialType: 0 }))
                             let max = materialConfig.max
                             let materialsNew = downPageMaterialDTOS.map((item: any) => {
                                 let newItem = item || {}
@@ -372,7 +412,7 @@ const CopyPage: React.FC<Props> = ({ accountId, pageData, visible, onChange, onC
                             form.setFieldsValue({ downPageMaterialDTOS: materialsNew })
                         } else {
                             let newMaterials = content?.map((item: any) => {
-                                return { [materialConfig.type]: { url: item?.url, id: item?.id, materialType: 0 } }
+                                return { [materialConfig.type]: { url: item?.oss_url, id: item?.id, materialType: 0 } }
                             })
                             if (newMaterials.length > 0) {
                                 if (downPageMaterialDTOS?.every((item: any) => !item)) { // 没设置过
@@ -403,17 +443,17 @@ const CopyPage: React.FC<Props> = ({ accountId, pageData, visible, onChange, onC
                             if (materialConfig.index === index) {
                                 if (materialConfig.type === 'imageId') {
                                     if (item) {
-                                        item[materialConfig.type] = content?.map((item: any) => ({ id: item?.id, url: item?.url, materialType: 0 }))
+                                        item[materialConfig.type] = content?.map((item: any) => ({ id: item?.id, url: item?.oss_url, materialType: 0 }))
                                         return { ...item }
                                     } else {
-                                        return { [materialConfig.type]: content?.map((item: any) => ({ id: item?.id, url: item?.url, materialType: 0 })) }
+                                        return { [materialConfig.type]: content?.map((item: any) => ({ id: item?.id, url: item?.oss_url, materialType: 0 })) }
                                     }
                                 } else {
                                     if (item) {
-                                        item[materialConfig.type] = { id: content[0]?.id, url: content[0]?.url, materialType: 0 }
+                                        item[materialConfig.type] = { id: content[0]?.id, url: content[0]?.oss_url, materialType: 0 }
                                         return { ...item }
                                     } else {
-                                        return { [materialConfig.type]: { id: content[0]?.id, url: content[0]?.url, materialType: 0 } }
+                                        return { [materialConfig.type]: { id: content[0]?.id, url: content[0]?.oss_url, materialType: 0 } }
                                     }
                                 }
                             }

+ 110 - 63
src/pages/launchSystemV3/tencentAdPutIn/create/Material/addMaterial.tsx

@@ -1,13 +1,12 @@
 import { CloseCircleOutlined, DeleteOutlined, PlusOutlined } from "@ant-design/icons"
-import { Button, Card, Dropdown, Empty, Form, InputNumber, Menu, Modal, Popconfirm, Space, message } from "antd"
+import { Button, Card, Dropdown, Empty, Form, InputNumber, Modal, Popconfirm, Space, message } from "antd"
 import React, { useEffect, useState } from "react"
 import styles from './index.less'
 import VideoNews from "@/pages/launchSystemNew/components/newsModal/videoNews"
-import { useModel } from "umi"
-import SelectCloud from "@/pages/launchSystemNew/components/selectCloud"
 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"
 
 interface Props {
     adLength: number,
@@ -23,8 +22,6 @@ interface Props {
 const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, deliveryMode, visible, value, onChange, onClose, adLength }) => {
 
     /*********************************/
-    const { init } = useModel('useLaunchAdq.useBdMediaPup')
-
     const [form] = Form.useForm();
     const dynamicGroup = Form.useWatch('dynamicGroup', form)
     const mediaType = Form.useWatch('mediaType', form)
@@ -52,6 +49,18 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
     const [imgUploads, setImgUploads] = useState<any>()
     const [minNumber, setMinNumber] = useState<number>(3)
     const [mCount, setMCount] = useState<number>(1)
+    const [selectCloudData, setSelectCloudData] = useState<{
+        defaultParams: {
+            sizeQueries?: {
+                width: number,
+                height: number,
+                relation: string
+            }[],
+            materialType: 'image' | 'video'
+            fileSize: number
+        }
+        num: number
+    }>()
     /*********************************/
 
     useEffect(() => {
@@ -103,13 +112,6 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
 
     const handleOk = (values: any) => {
         const { mediaType, dynamicGroup } = values
-        // if (mediaType === 1 && dynamicGroup.length < adLength) {
-        //     message.error({
-        //         content: `创意组分配规则选择“平均分配到广告”时,创意组总数必须大于等于广告总数。当前广告总数:${adLength},创意组总数:${dynamicGroup.length}`,
-        //         duration: 8
-        //     })
-        //     return
-        // }
         if (mediaType === 2 && dynamicGroup.length > adLength) {
             message.error({
                 content: `创意组分配规则选择“顺序分配到广告”时,创意组总数必须小于等于广告总数。当前广告总数:${adLength},创意组总数:${dynamicGroup.length}`,
@@ -140,7 +142,7 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
     }
 
     const selectGroupImg = (index: number, num: number) => {
-        init({ mediaType: 'IMG', num, cloudSize: [[{ relation: '=', width: 800, height: 800 }]], maxSize: 400 * 1024 })
+        setSelectCloudData({ defaultParams: { sizeQueries: [{ width: 800, height: 800, relation: '=' }], fileSize: 400 * 1024, materialType: 'image' }, num })
         setMaterialConfig({
             ...materialConfig,
             type: 'image',
@@ -174,7 +176,7 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
             <strong style={{ fontSize: 20 }}>创意素材</strong>
             {deliveryMode === 'DELIVERY_MODE_CUSTOMIZE' ? <>
                 {videoUploads && Object.keys(videoUploads)?.length > 0 && <Button type="link" onClick={() => {
-                    init({ mediaType: 'VIDEO', num: 100, cloudSize: creativeTemplateId === 1708 ? [[{ relation: '>=', width: 1280, height: 720 }]] : [[{ relation: '>=', width: videoUploads.restriction.videoRestriction.minWidth, height: videoUploads.restriction.videoRestriction.minHeight }]], maxSize: videoUploads.restriction.videoRestriction.fileSize * 1024 })
+                    setSelectCloudData({ defaultParams: { sizeQueries: creativeTemplateId === 1708 ? [{ relation: '>=', width: 1280, height: 720 }] : [{ relation: '>=', width: videoUploads.restriction.videoRestriction.minWidth, height: videoUploads.restriction.videoRestriction.minHeight }], fileSize: videoUploads.restriction.videoRestriction.fileSize * 1024, materialType: 'video' }, num: 100 })
                     setMaterialConfig({
                         ...materialConfig,
                         type: videoUploads.name,
@@ -187,7 +189,7 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                     }, 100)
                 }}>批量添加视频素材</Button>}
                 {imgUploads && Object.keys(imgUploads)?.length > 0 && <Button type="link" onClick={() => {
-                    init({ mediaType: 'IMG', num: 100, cloudSize: [[{ relation: '=', width: imgUploads.restriction.imageRestriction.width, height: imgUploads.restriction.imageRestriction.height }]], maxSize: imgUploads.restriction.imageRestriction.fileSize * 1024 })
+                    setSelectCloudData({ defaultParams: { sizeQueries: [{ relation: '=', width: imgUploads.restriction.imageRestriction.width, height: imgUploads.restriction.imageRestriction.height }], fileSize: imgUploads.restriction.imageRestriction.fileSize * 1024, materialType: 'image' }, num: 100 })
                     setMaterialConfig({
                         ...materialConfig,
                         type: imgUploads.name,
@@ -202,14 +204,19 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
             </> : <>
                 <InputNumber size="small" min={1} style={{ width: 50 }} value={mCount} max={15} onChange={(e) => setMCount(e || 0)} />
                 <Button type="link" onClick={() => {
-                    init({
-                        mediaType: 'IMG', num: 100, cloudSize: [[
-                            { relation: '=', width: 800, height: 800 },
-                            { relation: '=', width: 1280, height: 720 },
-                            { relation: '=', width: 720, height: 1280 },
-                            { relation: '=', width: 960, height: 334 },
-                            { relation: '=', width: 480, height: 320 },
-                        ]], maxSize: 400 * 1024
+                    setSelectCloudData({
+                        num: 100,
+                        defaultParams: {
+                            materialType: 'image',
+                            sizeQueries: [
+                                { relation: '=', width: 800, height: 800 },
+                                { relation: '=', width: 1280, height: 720 },
+                                { relation: '=', width: 720, height: 1280 },
+                                { relation: '=', width: 960, height: 334 },
+                                { relation: '=', width: 480, height: 320 },
+                            ],
+                            fileSize: 400 * 1024
+                        }
                     })
                     setMaterialConfig({
                         ...materialConfig,
@@ -224,11 +231,16 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                     }, 100)
                 }}>批量添加图片素材</Button>
                 <Button type="link" onClick={() => {
-                    init({
-                        mediaType: 'VIDEO', num: 100, cloudSize: [[
-                            { relation: '>=', width: 1280, height: 720 },
-                            { relation: '>=', width: 720, height: 1280 }
-                        ]], maxSize: 512000 * 1024
+                    setSelectCloudData({
+                        num: 100,
+                        defaultParams: {
+                            materialType: 'video',
+                            sizeQueries: [
+                                { relation: '>=', width: 1280, height: 720 },
+                                { relation: '>=', width: 720, height: 1280 }
+                            ],
+                            fileSize: 512000 * 1024
+                        }
                     })
                     setMaterialConfig({
                         ...materialConfig,
@@ -305,11 +317,16 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                                                     label: '添加视频',
                                                     key: 'addVideo',
                                                     onClick: () => {
-                                                        init({
-                                                            mediaType: 'VIDEO', num: 15, cloudSize: [[
-                                                                { relation: '>=', width: 1280, height: 720 },
-                                                                { relation: '>=', width: 720, height: 1280 }
-                                                            ]], maxSize: 512000 * 1024
+                                                        setSelectCloudData({
+                                                            num: 15 - (dynamicGroup?.[num]?.['list']?.length || 0),
+                                                            defaultParams: {
+                                                                materialType: 'video',
+                                                                sizeQueries: [
+                                                                    { relation: '>=', width: 1280, height: 720 },
+                                                                    { relation: '>=', width: 720, height: 1280 }
+                                                                ],
+                                                                fileSize: 512000 * 1024
+                                                            }
                                                         })
                                                         setMaterialConfig({
                                                             ...materialConfig,
@@ -328,14 +345,19 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                                                     label: '添加图片',
                                                     key: 'addImg',
                                                     onClick: () => {
-                                                        init({
-                                                            mediaType: 'IMG', num: 15, cloudSize: [[
-                                                                { relation: '=', width: 800, height: 800 },
-                                                                { relation: '=', width: 1280, height: 720 },
-                                                                { relation: '=', width: 720, height: 1280 },
-                                                                { relation: '=', width: 960, height: 334 },
-                                                                { relation: '=', width: 480, height: 320 },
-                                                            ]], maxSize: 400 * 1024
+                                                        setSelectCloudData({
+                                                            num: 15 - (dynamicGroup?.[num]?.['list']?.length || 0),
+                                                            defaultParams: {
+                                                                materialType: 'image',
+                                                                sizeQueries: [
+                                                                    { relation: '=', width: 800, height: 800 },
+                                                                    { relation: '=', width: 1280, height: 720 },
+                                                                    { relation: '=', width: 720, height: 1280 },
+                                                                    { relation: '=', width: 960, height: 334 },
+                                                                    { relation: '=', width: 480, height: 320 },
+                                                                ],
+                                                                fileSize: 400 * 1024
+                                                            }
                                                         })
                                                         setMaterialConfig({
                                                             ...materialConfig,
@@ -393,7 +415,14 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                                                 key={k}
                                             >
                                                 <div className={`${styles.box} ${styles.video}`} style={{ width: 300, height: 160 }} onClick={() => {
-                                                    init({ mediaType: 'VIDEO', cloudSize: creativeTemplateId === 1708 ? [[{ relation: '>=', width: 1280, height: 720 }]] : [[{ relation: '>=', width: item.restriction.videoRestriction.minWidth, height: item.restriction.videoRestriction.minHeight }]], maxSize: item.restriction.videoRestriction.fileSize * 1024 })
+                                                    setSelectCloudData({
+                                                        num: 1,
+                                                        defaultParams: {
+                                                            materialType: 'video',
+                                                            sizeQueries: creativeTemplateId === 1708 ? [{ relation: '>=', width: 1280, height: 720 }] : [{ relation: '>=', width: item.restriction.videoRestriction.minWidth, height: item.restriction.videoRestriction.minHeight }],
+                                                            fileSize: item.restriction.videoRestriction.fileSize * 1024
+                                                        }
+                                                    })
                                                     setMaterialConfig({
                                                         ...materialConfig,
                                                         type: item.name,
@@ -424,7 +453,14 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                                             >
                                                 <Space align="end">
                                                     <div className={`${styles.box} ${styles.image}`} style={{ width: 300, height: 160 }} onClick={() => {
-                                                        init({ mediaType: 'IMG', cloudSize: [[{ relation: '=', width: item.restriction.imageRestriction.width, height: item.restriction.imageRestriction.height }]], maxSize: item.restriction.imageRestriction.fileSize * 1024 })
+                                                        setSelectCloudData({
+                                                            num: 1,
+                                                            defaultParams: {
+                                                                materialType: 'image',
+                                                                sizeQueries: [{ relation: '=', width: item.restriction.imageRestriction.width, height: item.restriction.imageRestriction.height }],
+                                                                fileSize: item.restriction.imageRestriction.fileSize * 1024
+                                                            }
+                                                        })
                                                         setMaterialConfig({
                                                             ...materialConfig,
                                                             type: item.name,
@@ -471,7 +507,14 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                                                 key={key}
                                             >
                                                 <div className={`${styles.box} ${item.arrayProperty.maxNumber >= 3 ? styles.image_list : styles.imageMater}`} style={item.arrayProperty.maxNumber >= 3 ? { flexFlow: 'row', width: '100%', gap: 2 } : {}} onClick={() => {
-                                                    init({ mediaType: 'IMG', num: item.arrayProperty.maxNumber, cloudSize: [[{ relation: '=', width: imageData.restriction.imageRestriction.width, height: imageData.restriction.imageRestriction.height }]], maxSize: imageData.restriction.imageRestriction.fileSize * 1024 })
+                                                    setSelectCloudData({
+                                                        num: item.arrayProperty.maxNumber,
+                                                        defaultParams: {
+                                                            materialType: 'image',
+                                                            sizeQueries: [{ relation: '=', width: imageData.restriction.imageRestriction.width, height: imageData.restriction.imageRestriction.height }],
+                                                            fileSize: imageData.restriction.imageRestriction.fileSize * 1024
+                                                        }
+                                                    })
                                                     setMaterialConfig({
                                                         ...materialConfig,
                                                         type: name,
@@ -566,17 +609,20 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
             </Form.Item>
         </Form>
 
-
-        {/* 选择视频素材 */}
-        {selectVideoVisible && <SelectCloud
-            isGroup={materialConfig?.isGroup}
+        {/* 选择素材 */}
+        {(selectVideoVisible && selectCloudData) && <SelectCloudNew
+            {...selectCloudData}
             visible={selectVideoVisible}
-            onClose={() => setSelectVideoVisible(false)}
+            isGroup={materialConfig?.isGroup}
             sliderImgContent={materialConfig.index === 99999 ? undefined :
-                materialConfig.type === 'element_story' ? (dynamicGroup[materialConfig.index] && Object.keys(dynamicGroup[materialConfig.index])?.includes('element_story')) ? dynamicGroup[materialConfig.index]['element_story']?.map((item: any) => item) : undefined :
-                    materialConfig.type === 'image_list' ? (dynamicGroup[materialConfig.index] && Object.keys(dynamicGroup[materialConfig.index])?.includes('image_list')) ? dynamicGroup[materialConfig.index]['image_list']?.map((item: any) => item) : undefined :
-                        (dynamicGroup[materialConfig.index] && Object.keys(dynamicGroup[materialConfig.index])?.includes(materialConfig.type)) ? [{ url: dynamicGroup[materialConfig.index][materialConfig.type] }] : undefined
+                materialConfig.type === 'element_story' ? (dynamicGroup[materialConfig.index] && Object.keys(dynamicGroup[materialConfig.index])?.includes('element_story')) ? dynamicGroup[materialConfig.index]['element_story']?.map((item: any) => ({ oss_url: item.url, id: item.id })) : undefined :
+                    materialConfig.type === 'image_list' ? (dynamicGroup[materialConfig.index] && Object.keys(dynamicGroup[materialConfig.index])?.includes('image_list')) ? dynamicGroup[materialConfig.index]['image_list']?.map((item: any) => ({ oss_url: item.url, id: item.id })) : undefined :
+                        (dynamicGroup[materialConfig.index] && Object.keys(dynamicGroup[materialConfig.index])?.includes(materialConfig.type)) ? [{ oss_url: dynamicGroup[materialConfig.index][materialConfig.type]['url'], id: dynamicGroup[materialConfig.index][materialConfig.type]['id'] }] : undefined
             }
+            onClose={() => {
+                setSelectVideoVisible(false)
+                setSelectCloudData(undefined)
+            }}
             onChange={(content: any) => {
                 if (content.length > 0) {
                     if (deliveryMode === 'DELIVERY_MODE_COMPONENT') { // 组件化创意
@@ -584,7 +630,7 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                             if (materialConfig.isGroup) {
 
                             } else {
-                                const aContent = content.map((m: any) => ({ id: m?.id, url: m?.url, materialType: 0 }))
+                                const aContent = content.map((m: any) => ({ id: m?.id, url: m?.oss_url, materialType: 0 }))
                                 let newDynamicGroup = dynamicGroup?.map((item: any) => {
                                     let oldList = item?.list || []
                                     if (oldList.length < mCount) {
@@ -608,10 +654,10 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                                 if (materialConfig.index === index) {
                                     let oldList = item?.list || []
                                     if (materialConfig.isGroup) {
-                                        oldList = oldList.concat([content.map((m: any) => ({ id: m?.id, url: m?.url, materialType: 0 }))])
+                                        oldList = oldList.concat([content.map((m: any) => ({ id: m?.id, url: m?.oss_url, materialType: 0 }))])
                                         return { list: oldList }
                                     } else {
-                                        oldList = oldList.concat(content.map((m: any) => ({ id: m?.id, url: m?.url, materialType: 0 })))
+                                        oldList = oldList.concat(content.map((m: any) => ({ id: m?.id, url: m?.oss_url, materialType: 0 })))
                                         return { list: oldList }
                                     }
                                 }
@@ -622,7 +668,7 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                     } else { // 自定义创意
                         if (materialConfig.index === 99999) {
                             if (materialConfig.type === 'image_list' || materialConfig.type === 'element_story') {
-                                let urls = content?.map((item: any) => ({ id: item?.id, url: item?.url, materialType: 0 }))
+                                let urls = content?.map((item: any) => ({ id: item?.id, url: item?.oss_url, materialType: 0 }))
                                 let max = materialConfig.max
                                 let materialsNew = dynamicGroup.map((item: any) => {
                                     let newItem = item || {}
@@ -670,9 +716,9 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                             } else {
                                 let newMaterials = content?.map((item: any) => {
                                     if (["short_video1", 'video_id'].includes(materialConfig.type) && mLength === 2) {
-                                        return { [materialConfig.type]: { url: item?.url, id: item?.id, materialType: 0 }, cover_id: { url: getVideoImgUrl(item?.url), id: null, materialType: 0 } }
+                                        return { [materialConfig.type]: { url: item?.oss_url, id: item?.id, materialType: 0 }, cover_id: { url: getVideoImgUrl(item?.oss_url), id: null, materialType: 0 } }
                                     }
-                                    return { [materialConfig.type]: { url: item?.url, id: item?.id, materialType: 0 } }
+                                    return { [materialConfig.type]: { url: item?.oss_url, id: item?.id, materialType: 0 } }
                                 })
                                 if (newMaterials.length > 0) {
                                     if (dynamicGroup?.every((item: any) => !item)) { // 没设置过
@@ -703,20 +749,20 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                                 if (materialConfig.index === index) {
                                     if (materialConfig.type === 'image_list' || materialConfig.type === 'element_story') {
                                         if (item) {
-                                            item[materialConfig.type] = content?.map((item: any) => ({ id: item?.id, url: item?.url, materialType: 0 }))
+                                            item[materialConfig.type] = content?.map((item: any) => ({ id: item?.id, url: item?.oss_url, materialType: 0 }))
                                             return { ...item }
                                         } else {
-                                            return { [materialConfig.type]: content?.map((item: any) => ({ id: item?.id, url: item?.url, materialType: 0 })) }
+                                            return { [materialConfig.type]: content?.map((item: any) => ({ id: item?.id, url: item?.oss_url, materialType: 0 })) }
                                         }
                                     } else {
                                         if (item) {
-                                            item[materialConfig.type] = { id: content[0]?.id, url: content[0]?.url, materialType: 0 }
+                                            item[materialConfig.type] = { id: content[0]?.id, url: content[0]?.oss_url, materialType: 0 }
                                             return { ...item }
                                         } else {
                                             if (["short_video1", 'video_id'].includes(materialConfig.type) && mLength === 2) {
-                                                return { [materialConfig.type]: { id: content[0]?.id, url: content[0]?.url, materialType: 0 }, cover_id: { id: null, url: getVideoImgUrl(content[0]?.url), materialType: 0 } }
+                                                return { [materialConfig.type]: { id: content[0]?.id, url: content[0]?.oss_url, materialType: 0 }, cover_id: { id: null, url: getVideoImgUrl(content[0]?.oss_url), materialType: 0 } }
                                             }
-                                            return { [materialConfig.type]: { id: content[0]?.id, url: content[0]?.url, materialType: 0 } }
+                                            return { [materialConfig.type]: { id: content[0]?.id, url: content[0]?.oss_url, materialType: 0 } }
                                         }
                                     }
                                 }
@@ -727,6 +773,7 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                     }
                 }
                 setSelectVideoVisible(false)
+                setSelectCloudData(undefined)
             }}
         />}
     </Modal>

+ 6 - 0
src/pages/launchSystemV3/tencentAdPutIn/index.less

@@ -77,6 +77,12 @@
     }
 }
 
+.selectModal {
+    .ant-modal-footer {
+        padding: 0 8px;
+    }
+}
+
 .cardResetCss {
     --boder: 8px;
     border-radius: var(--boder);

+ 60 - 0
src/services/adqV3/cloudNew.ts

@@ -100,6 +100,18 @@ export async function getMaterialListApi(data: CLOUDNEW.GetMaterialListProps) {
     })
 }
 
+/**
+ * 分页查询素材 有数据
+ * @param data 
+ * @returns 
+ */
+export async function getMaterialDataListApi(data: CLOUDNEW.GetMaterialDataListProps) {
+    return request(api + `/material/material/pageDataList`, {
+        method: 'POST',
+        data
+    })
+}
+
 
 /**
  * 新增素材
@@ -149,4 +161,52 @@ export async function moveMaterialApi({ folderId, materialIds }: { folderId: num
         data: materialIds,
         params: { folderId }
     })
+}
+
+/**
+ * 素材详情
+ * @param params 
+ * @returns 
+ */
+export async function getMaterialDetailApi(params: { id: number }) {
+    return request(api + `/material/material/materialDetail/get`, {
+        method: 'POST',
+        params
+    })
+}
+
+/**
+ * 获取素材数据概览
+ * @param data 
+ * @returns 
+ */
+export async function getReportMaterialDetailApi(data: { signature: string, mediaType: string, dataTimeMix: string, dataTimeMax: string }) {
+    return request(api + `/material/material/materialDetail/getReport`, {
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 素材创意列表
+ * @param data 
+ * @returns 
+ */
+export async function getCreativeReportApi(data: CLOUDNEW.GetCreativeReport) {
+    return request(api + `/material/material/materialDetail/getCreativeReport`, {
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 迁移素材
+ * @param data 
+ * @returns 
+ */
+export async function moveOldMaterialApi(data: { materialIds: number[], materialType: string, targetFolderId: number }) {
+    return request(api + `/material/material/moveOldMaterial`, {
+        method: 'POST',
+        data
+    })
 }

+ 16 - 1
src/utils/utils.ts

@@ -516,4 +516,19 @@ export function formatSecondsToTime(seconds: number): string {
 
     // 使用 padStart 确保每个部分都是两位数  
     return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
-} 
+}
+
+/**
+ * 文件大小转换
+ * @param bytes 
+ * @returns 
+ */
+export function formatBytes(bytes: number) {
+    if (bytes < 1048576) { // 1024 * 1024  
+        return (bytes / 1024).toFixed(2) + ' KB';
+    } else if (bytes < 1073741824) { // 1024 * 1024 * 1024  
+        return (bytes / 1048576).toFixed(2) + ' MB';
+    } else {
+        return (bytes / 1073741824).toFixed(2) + ' GB';
+    }
+}