Переглянути джерело

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

wjx 8 місяців тому
батько
коміт
bd7cfa1cc5

+ 1 - 1
config/config.ts

@@ -24,7 +24,7 @@ export default defineConfig({
   layout: {
     name: '趣程投放工具',
     locale: false,
-    siderWidth: 208,
+    siderWidth: 208
   },
   locale: {
     // default zh-CN

+ 6 - 0
config/routerConfig.ts

@@ -195,6 +195,12 @@ const launchSystemV3 = {
                     access: 'cloud',
                     component: './launchSystemNew/material/cloud',
                 },
+                {
+                    name: '素材库',
+                    path: '/launchSystemV3/material/cloudNew',
+                    access: 'cloud',
+                    component: './launchSystemV3/material/cloudNew',
+                },
             ],
         },
     ]

+ 30 - 7
src/components/FileBoxAD/components/moveTo/index.tsx

@@ -1,8 +1,10 @@
 import { editMediaFolder } from "@/services/launchAdq/material"
-import { Form, message, Modal, Select } from "antd"
-import React, { useState } from "react"
+import { Form, message, Modal, TreeSelect } from "antd"
+import React, { useEffect, useState } from "react"
 import { useModel } from "umi"
-
+import '../../../../pages/launchSystemV3/tencentAdPutIn/index.less'
+import { DataNode } from "antd/lib/tree"
+import { DownOutlined } from "@ant-design/icons"
 
 interface Props {
     catalogueData: any[]
@@ -21,6 +23,7 @@ const MoveTo: React.FC<Props> = ({ isAll, visible, onClose, catalogueData = [] }
     const { state, list } = useModel(isAll ? 'useLaunchAdq.useBdMedia' : 'useLaunchAdq.useBdMediaPup')
     const { selectFile, mediaType, belongUser } = state
     const [form] = Form.useForm();
+    const [treeData, setTreeData] = useState<any[]>([]);
     const [loading, setLoading] = useState<boolean>(false)
     /*****************************/
 
@@ -44,24 +47,44 @@ const MoveTo: React.FC<Props> = ({ isAll, visible, onClose, catalogueData = [] }
         })
     }
 
+    useEffect(() => {
+        setTreeData(resettingTreedata(catalogueData))
+    }, [catalogueData])
+
+    const resettingTreedata = (tree: { title: string, id: number, childMedias?: any[] }[]): any[] => {
+        return tree.map(item => {
+            let children: DataNode[] = []
+            if (item?.childMedias?.length) {
+                children = resettingTreedata(item.childMedias)
+            }
+            return { title: item.title, value: item.id, children }
+        });
+    };
+
     return <Modal
-        title="移动至"
+        title={<strong>移动至</strong>}
         open={visible}
         onCancel={() => onClose?.()}
         onOk={handleOk}
         confirmLoading={loading}
+        className='modalResetCss'
     >
         <Form
             form={form}
             labelCol={{ span: 4 }}
+            labelAlign="left"
             className='ad_form_style'
             colon={false}
             initialValues={{}}
         >
             <Form.Item label={<strong>文件夹</strong>} name='folderId' rules={[{ required: true, message: '请选择文件夹' }]}>
-                <Select style={{ width: 380 }} placeholder='请选择'>
-                    {[{ title: belongUser == 1 ? '个人本地' : '公共本地', id: 0 }, ...catalogueData]?.map((item) => <Select.Option value={item.id} key={item.id}>{item.title}</Select.Option>)}
-                </Select>
+                <TreeSelect
+                    treeData={[
+                        { title: belongUser == 1 ? '个人本地' : '公共本地', value: 0 },
+                        ...treeData
+                    ]}
+                    switcherIcon={<DownOutlined />}
+                />
             </Form.Item>
         </Form>
     </Modal>

+ 0 - 64
src/models/useOperating/useOperate.ts

@@ -1,64 +0,0 @@
-import { useReducer } from 'react'
-import {
-    getStatistics, getArticleSummary, getFanGrowth, getFansAttribute, getFansActive, getFansInteractive, getFansKeep, Fans, getStatisticsMpId
-} from '@/services/operating/operate'
-import { useAjax } from '@/Hook/useAjax'
-type State = {
-    fansData?: any,//对应公众号头部粉丝数据
-}
-export type Action = {
-    type: 'setFansData',
-    params?: any
-}
-export function reducer(state: State, action: Action) {
-    let { type, params } = action
-    switch (type) {
-        case 'setFansData':
-            return { ...state, fansData: params.fansData }
-        default:
-            return state;
-    }
-}
-
-export default function useWxGroupList() {
-    const [state, dispatch] = useReducer(reducer,{})
-    /**总览统计 */
-    const statistics = useAjax((params: {userId: number}) => getStatistics(params))
-    /**指定公众号的统计数据 */
-    const statisticsMpId = useAjax((mpId) => getStatisticsMpId(mpId))
-    /**图文分析 */
-    const articleSummary = useAjax((params: Fans) => getArticleSummary(params))
-    /**粉丝增长 */
-    const fanGrowth = useAjax((params: Fans) => getFanGrowth(params))
-    /**粉丝属性 */
-    const fansAttribute = useAjax((params: Fans) => getFansAttribute(params))
-    /**统计指定公众号粉丝的活跃数据 */
-    const fansActive = useAjax((params: {
-        mpId: string;
-        timeDto: any;
-    }) => getFansActive(params))
-    /**统计指定公众号粉丝的互动数据 */
-    const fansInteractive = useAjax((params: {
-        mpId: string;
-        timeDto: any;
-    }) => getFansInteractive(params))
-    /**统计指定公众号粉丝的忠诚度 */
-    const fansKeep = useAjax((params: {
-        mpId: string;
-        statisticsKeepDto: any;
-    }) => getFansKeep(params))
-
-
-    return {
-        state,
-        dispatch,
-        statistics,
-        statisticsMpId,
-        articleSummary,
-        fanGrowth,
-        fansAttribute,
-        fansActive,
-        fansInteractive,
-        fansKeep
-    }
-}

+ 72 - 0
src/pages/launchSystemV3/material/cloudNew/folder.tsx

@@ -0,0 +1,72 @@
+import React, { forwardRef, Ref, useContext, useImperativeHandle } from "react"
+import style from './index.less'
+import { Button, Empty, Spin, Tree, Typography } from "antd"
+import { DataNode, DirectoryTreeProps, EventDataNode } from "antd/lib/tree";
+import { DispatchCloudNew } from ".";
+
+interface FolderRef {
+    folderRefresh: () => void
+}
+interface Props {
+    loading: boolean
+    onLoadData: (treeNode: EventDataNode<DataNode>) => Promise<void>
+}
+/**
+ * 文件夹
+ * @returns 
+ */
+const Folder = forwardRef(({ loading, onLoadData }: Props, ref: Ref<FolderRef>) => {
+
+    /******************************/
+    const { treeData, setExpandedKeys, expandedKeys, setBreadcrumdData, findParentKeys, loadedKeys } = useContext(DispatchCloudNew)!;
+
+    /******************************/
+
+    useImperativeHandle(ref, () => ({
+        folderRefresh() {
+
+        }
+    }));
+
+    const onSelect: DirectoryTreeProps['onSelect'] = (keys, info) => {
+        console.log('Trigger Expand', keys, info);
+        if (keys?.length) {
+            findParentKeys(keys?.[0] as string, treeData)
+        } else {
+            setBreadcrumdData([{ label: '全部素材', key: 0, currentKey: '0' }])
+        }
+        setExpandedKeys(keys);
+    };
+
+
+
+    return <div className={style.folder}>
+        <div className={style.top}>
+            <Button
+                style={{ padding: 0, height: 'auto', width: 71, fontSize: 14 }}
+                onClick={() => {
+                    setExpandedKeys([])
+                    setBreadcrumdData([{ label: '全部素材', key: 0, currentKey: '0' }])
+                }}
+                type="text"
+            >全部素材</Button>
+            {/* <Input.Search enterButton style={{marginTop: 6,}} allowClear placeholder="文件夹名称" onSearch={(value) => { setQueryFormFolder({ folderName: value }) }} /> */}
+        </div>
+        <div className={style.bottom}>
+            <Spin spinning={loading}>
+                <Tree
+                    showIcon
+                    blockNode={true}
+                    selectedKeys={expandedKeys}
+                    loadData={onLoadData}
+                    onSelect={onSelect}
+                    treeData={treeData}
+                    loadedKeys={loadedKeys}
+                />
+                {!(treeData?.length > 0) && (loading ? <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: 200 }} ></div> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无文件夹" />)}
+            </Spin>
+        </div>
+    </div>
+})
+
+export default React.memo(Folder)

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

@@ -0,0 +1,100 @@
+.cloudNew {
+    display: flex;
+    min-width: 1200px;
+    height: calc(100vh - 98px);
+
+    .folder {
+        width: 238px;
+
+        border-right: 1px solid #e8eaec;
+        overflow: hidden;
+        display: flex;
+        flex-direction: column;
+
+        .top {
+            border-bottom: 1px solid #e8eaec;
+            padding: 6px;
+            display: flex;
+            flex-direction: column;
+            flex: 0 1;
+        }
+
+        .bottom {
+            padding: 4px 6px 4px 2px;
+            flex: 1 0;
+            overflow: hidden;
+            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;
+        }
+
+        .left_bts {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 10px;
+        }
+
+        .content {
+            width: 100%;
+            flex: 1 0;
+            overflow: hidden;
+            min-height: 100px;
+
+            >div {
+                height: 100%;
+
+                >div {
+                    height: 100%;
+                }
+            }
+        }
+
+        .content_scroll {
+            height: 100%;
+            overflow-y: auto;
+            padding: 12px;
+
+            >div {
+                display: flex;
+                // gap: 10px;
+                flex-wrap: wrap;
+            }
+        }
+        .content_row {
+            padding: 4px;
+            >div {
+                border-radius: 6px;
+            }
+        }
+
+        .content_col {
+            // min-width: 240px;
+            // max-width: 300px;
+            // width: 20%;
+        }
+
+        .content_cover {
+            padding: 10px;
+            background-color: #ececec;
+            border-top-left-radius: 6px;
+            border-top-right-radius: 6px;
+        }
+
+        .fotter {
+            flex: 0 1;
+            border-top: 1px solid #e8eaec;
+            padding: 8px 16px;
+        }
+    }
+}

+ 166 - 0
src/pages/launchSystemV3/material/cloudNew/index.tsx

@@ -0,0 +1,166 @@
+import React, { useEffect, useRef, useState } from "react"
+import style from './index.less'
+import Folder from "./folder";
+import Material from "./material";
+import { Card } from "antd";
+import '../../tencentAdPutIn/index.less'
+import { DataNode, EventDataNode } from "antd/lib/tree";
+import { getFolderListApi } from "@/services/adqV3/cloudNew";
+import { useAjax } from "@/Hook/useAjax";
+import ManageFolder from "./manageFolder";
+import { FolderOpenOutlined, FolderOutlined } from "@ant-design/icons";
+
+export const DispatchCloudNew = React.createContext<CLOUDNEW.CloudNewReactContent | null>(null);
+
+/**
+ * 新版素材库
+ * @returns 
+ */
+const CloudNew: React.FC = () => {
+
+    /**********************************/
+    const refMaterial = useRef(null);
+    const [treeData, setTreeData] = useState<DataNode[]>([]);
+    const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
+    const [queryFormFolder, setQueryFormFolder] = useState<CLOUDNEW.GetFolderListProps>({})
+    const [folderVisible, setFolderVisible] = useState<boolean>(false)
+    const [folderLoading, setFolderLoading] = useState<boolean>(false)
+    const [breadcrumdData, setBreadcrumdData] = useState<CLOUDNEW.BreadcrumdData[]>([{ label: '全部素材', key: 0, currentKey: '0' }])
+    const [loadedKeys, setLoadedKeys] = useState<string[]>([])  // 已经加载了的节点
+
+    const getFolderList = useAjax((params) => getFolderListApi(params))
+    /**********************************/
+
+    useEffect(() => {
+        handleGetFolder()
+    }, [queryFormFolder?.folderName])
+
+    // 获取顶级文件夹列表
+    const handleGetFolder = () => {
+        setFolderLoading(() => true)
+        setLoadedKeys(() => [])
+        getFolderList.run({ folderName: queryFormFolder?.folderName }).then(res => {
+            setFolderLoading(() => false)
+            setTreeData(() => res?.map((item: { folderName: any; id: any; }) => ({
+                title: item.folderName,
+                key: '0-' + item.id,
+                icon: ({ selected }: any) => (selected ? <FolderOpenOutlined style={{ color: 'rgb(255, 202, 40)' }} /> : <FolderOutlined style={{ color: 'rgb(255, 202, 40)' }} />),
+            })) || [])
+        }).catch(() => setFolderLoading(false))
+    }
+
+    // 下级目录
+    const handleUpdateFolder = (parentIdStr: string) => {
+        const parentIdArr = parentIdStr.split('-')
+        const parentIdArrLength = parentIdArr.length
+        const parentId = Number(parentIdArr[parentIdArrLength - 1])
+        return getFolderListApi({ parentId }).then(res => {
+            setLoadedKeys(loadedkeys => [...loadedkeys, parentIdStr])
+            setTreeData(origin =>
+                updateTreeData(origin, parentIdStr, res?.data?.map((item: { folderName: any; id: any; }) => ({
+                    title: item.folderName,
+                    key: parentIdStr + '-' + item.id,
+                    icon: ({ selected }: any) => (selected ? <FolderOpenOutlined /> : <FolderOutlined />)
+                })) || []),
+            );
+        })
+    }
+
+    // 更新目录
+    const updateTreeData = (list: DataNode[], key: React.Key, children: DataNode[]): DataNode[] => {
+        return list.map(node => {
+            if (node.key === key) {
+                return {
+                    ...node,
+                    children,
+                };
+            }
+            if (node.children) {
+                return {
+                    ...node,
+                    children: updateTreeData(node.children, key, children),
+                };
+            }
+            return node;
+        });
+    }
+
+    // 查找所有菜单列表(m面包屑)
+    const findParentKeys = (key: string, tree: DataNode[]) => {
+        let newBreadcrumdData: CLOUDNEW.BreadcrumdData[] = [{ label: '全部素材', key: 0, currentKey: '0' }]
+        let keys = key.split('-')
+        let treeData: DataNode[] = JSON.parse(JSON.stringify(tree))
+        let currentKey: string = '0'
+        keys.forEach((item, index) => {
+            if (index !== 0) {
+                currentKey = currentKey + '-' + item
+                let treeD = treeData.find(item => item.key === currentKey)
+                if (treeD) {
+                    if (treeD?.children) {
+                        treeData = treeD.children
+                    }
+                    newBreadcrumdData.push({ label: treeD?.title as string, key: Number(item), currentKey })
+                }
+            }
+        })
+        setBreadcrumdData(newBreadcrumdData)
+    };
+
+    return <Card
+        style={{ height: '100%' }}
+        bodyStyle={{ padding: 0, overflow: 'auto hidden', height: '100%' }}
+        className="cardResetCss buttonResetCss"
+        bordered
+    >
+        <div className={style.cloudNew}>
+            <DispatchCloudNew.Provider value={{
+                treeData, setTreeData,
+                expandedKeys, setExpandedKeys,
+                queryFormFolder, setQueryFormFolder,
+                breadcrumdData, setBreadcrumdData,
+                loadedKeys, setLoadedKeys,
+                findParentKeys
+            }}>
+                {/* 文件夹 */}
+                <Folder
+                    loading={folderLoading}
+                    onLoadData={(treeNode: EventDataNode<DataNode>) => {
+                        return new Promise<void>(async (resolve) => {
+                            console.log('Trigger Select', treeNode);
+                            await handleUpdateFolder(treeNode.key as string)
+                            resolve()
+                        })
+                    }}
+                />
+                {/* 素材列表 */}
+                <Material
+                    onAddFolder={() => {
+                        setFolderVisible(true)
+                    }}
+                    ref={refMaterial}
+                />
+            </DispatchCloudNew.Provider>
+        </div>
+
+        {/* 文件夹新增修改 */}
+        {folderVisible && <ManageFolder
+            parentIdStr={expandedKeys?.[0] as string || '0'}
+            visible={folderVisible}
+            onClose={() => {
+                setFolderVisible(false)
+            }}
+            onChange={(newData) => {
+                setFolderVisible(false)
+                if (expandedKeys?.[0]) {
+                    (refMaterial.current as any).folderRefresh()
+                    handleUpdateFolder(expandedKeys[0] as string)
+                } else {
+                    // handleGetFolder()
+                    setTreeData(data => [...data, { title: newData.folderName, key: '0-' + newData.id, icon: ({ selected }: any) => (selected ? <FolderOpenOutlined style={{ color: 'rgb(255, 202, 40)' }} /> : <FolderOutlined style={{ color: 'rgb(255, 202, 40)' }} />) }])
+                }
+            }}
+        />}
+    </Card>
+}
+
+export default CloudNew

+ 118 - 0
src/pages/launchSystemV3/material/cloudNew/manageFolder.tsx

@@ -0,0 +1,118 @@
+import { Button, Card, Form, Input, message, Modal, Radio, Space, Transfer } from "antd"
+import React, { useEffect } from "react"
+import '../../tencentAdPutIn/index.less'
+import { useAjax } from "@/Hook/useAjax"
+import { getUserAllApi } from "@/services/operating/account"
+import { addFolderApi, updateFolderApi } from "@/services/adqV3/cloudNew"
+
+interface Props {
+    parentIdStr: string
+    visible?: boolean
+    onChange?: (res: any) => void
+    onClose?: () => void
+}
+
+/**
+ * 新增文件夹
+ * @param param0 
+ * @returns 
+ */
+const ManageFolder: React.FC<Props> = ({ parentIdStr = '0', visible, onChange, onClose }) => {
+
+    /************************************/
+    const [form] = Form.useForm();
+    const isPublic = Form.useWatch('isPublic', form)
+
+    const getUserAll = useAjax(() => getUserAllApi())
+    const addFolder = useAjax((params) => addFolderApi(params))
+    const updateFolder = useAjax((params) => updateFolderApi(params))
+    /************************************/
+
+    useEffect(() => {
+        getUserAll.run()
+    }, [])
+
+    const handleOk = (values: any) => {
+        console.log(values)
+        const parentIdArr = parentIdStr.split('-')
+        const parentIdArrLength = parentIdArr.length
+        const parentId = Number(parentIdArr[parentIdArrLength - 1])
+        addFolder.run({ ...values, parentId }).then(res => {
+            if (res) {
+                message.success('新增成功')
+                onChange?.(res)
+            }
+        })
+    }
+
+
+    return <Modal
+        title={<strong>新增文件夹</strong>}
+        open={visible}
+        onCancel={onClose}
+        footer={null}
+        className="modalResetCss"
+        bodyStyle={{ padding: '0 0 40px', position: 'relative', borderRadius: '0 0 8px 8px' }}
+        maskClosable={false}
+        width={600}
+    >
+        <Form
+            form={form}
+            name="newFolder"
+            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'
+            }}
+            onFinishFailed={({ errorFields }) => {
+                message.error(errorFields?.[0]?.errors?.[0])
+            }}
+            onFinish={handleOk}
+            initialValues={{
+                isPublic: true
+            }}
+        >
+            <Card className="cardResetCss">
+                <Form.Item name="folderName" label={<strong>名称</strong>} required={false} rules={[{ required: true, message: '请输入文件夹名称!' }]}>
+                    <Input placeholder="文件夹名称" />
+                </Form.Item>
+                {parentIdStr === '0' && <>
+                    <Form.Item name="isPublic" label={<strong>权限</strong>}>
+                        <Radio.Group buttonStyle="solid">
+                            <Radio.Button value={true}>所有人可见</Radio.Button>
+                            <Radio.Button value={false}>指定人员可见</Radio.Button>
+                        </Radio.Group>
+                    </Form.Item>
+                    {isPublic === false && <Form.Item name="permissionUserId" label={<strong>人员</strong>} valuePropName="targetKeys">
+                        <Transfer
+                            dataSource={getUserAll?.data || []}
+                            rowKey={record => record.userId}
+                            render={item => item.nickname}
+                            titles={['未选', '已选']}
+                            oneWay
+                            showSearch
+                            listStyle={({ direction }) => (direction === 'left' ? { height: 350, width: 240 } : { height: 350 })}
+                        />
+                    </Form.Item>}
+                </>}
+                <Form.Item name="description" label={<strong>备注</strong>}>
+                    <Input.TextArea placeholder="备注" />
+                </Form.Item>
+            </Card>
+            <Form.Item className="submit_pull">
+                <Space>
+                    <Button onClick={onClose}>取消</Button>
+                    <Button type="primary" htmlType="submit" loading={addFolder.loading} className="modalResetCss">
+                        确定
+                    </Button>
+                </Space>
+            </Form.Item>
+        </Form>
+    </Modal>
+}
+
+export default React.memo(ManageFolder)

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

@@ -0,0 +1,114 @@
+import React, { forwardRef, Ref, useContext, useEffect, useImperativeHandle, useRef, useState } from "react"
+import style from './index.less'
+import { Breadcrumb, Button, Card, Pagination, Spin } from "antd"
+import { DispatchCloudNew } from "."
+import { getFolderListApi } from "@/services/adqV3/cloudNew"
+import { useAjax } from "@/Hook/useAjax"
+import { useSize } from "ahooks"
+
+interface Props {
+    /** 新增文件夹 */
+    onAddFolder?: () => void
+}
+
+interface MaterialRef {
+    folderRefresh: () => void
+}
+/**
+ * 素材列表
+ * @returns 
+ */
+const Material = forwardRef(({ onAddFolder }: Props, ref1: Ref<MaterialRef>) => {
+
+    /********************************/
+    const { breadcrumdData, setExpandedKeys, setBreadcrumdData, expandedKeys } = useContext(DispatchCloudNew)!;
+    const ref = useRef<HTMLDivElement>(null);
+    const size = useSize(ref);
+    const [folderList, setFolderList] = useState<{ id: number, folderName: string, description?: string }[]>([])
+    const [rowNum, setRowNum] = useState<number>(0)
+
+    const getFolderList = useAjax((params) => getFolderListApi(params))
+    /********************************/
+
+    useImperativeHandle(ref1, () => ({
+        // 刷新文件夹
+        folderRefresh() {
+            getFolder()
+        }
+    }));
+
+    // 根据内容宽度计算列数
+    useEffect(() => {
+        if (size?.width) {
+            let rowNum = Math.floor((size?.width - 26) / 240)
+            setRowNum(rowNum || 1)
+        }
+    }, [size?.width])
+
+    useEffect(() => {
+        getFolder()
+    }, [expandedKeys])
+
+    /** 获取下级文件夹 */
+    const getFolder = () => {
+        if (expandedKeys?.length) {
+            const parentIdArr = (expandedKeys[0] as string).split('-')
+            const parentIdArrLength = parentIdArr.length
+            const parentId = Number(parentIdArr[parentIdArrLength - 1])
+            getFolderList.run({ parentId }).then(res => {
+                setFolderList(res || [])
+            })
+        } else {
+            setFolderList([])
+        }
+    }
+
+    return <div className={style.material}>
+        <div className={style.operates}>
+            <div className={style.left_bts}>
+                <Button type="primary">上传素材</Button>
+                <Button onClick={() => onAddFolder?.()}>新建文件夹</Button>
+            </div>
+        </div>
+        <div className={style.operates}>
+            <Breadcrumb>
+                {breadcrumdData.map((item, index) => <Breadcrumb.Item key={item.key}>
+                    {breadcrumdData.length !== index + 1 ? <a
+                        style={{ color: '#1890ff' }}
+                        onClick={() => {
+                            setBreadcrumdData(data => data.splice(0, index + 1))
+                            setExpandedKeys([item.currentKey])
+                        }}
+                    >{item.label}</a> : item.label}
+                </Breadcrumb.Item>)}
+            </Breadcrumb>
+        </div>
+        <div className={style.content}>
+            <Spin spinning={getFolderList.loading}>
+                <div className={style.content_scroll} ref={ref}>
+                    <div>
+                        {folderList.map((item, index) => <div key={index} className={style.content_row} style={{ width: rowNum ? (1 / rowNum * 100) + '%' : 240 }}>
+                            <Card
+                                hoverable
+                                className={style.content_col}
+                                cover={<div style={{ height: 150 }} className={style.content_cover}>
+
+                                </div>}
+                            >
+                                {/* <Meta title="Europe Street beat" description="www.instagram.com" /> */}
+                                6666
+                            </Card>
+                        </div>)}
+
+
+                    </div>
+                </div>
+            </Spin>
+        </div>
+        <div className={style.fotter}>
+            <Pagination size="small" total={50} showSizeChanger showQuickJumper />
+        </div>
+    </div>
+})
+
+export default React.memo(Material)

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

@@ -0,0 +1,63 @@
+declare namespace CLOUDNEW {
+    interface CloudNewReactContent {
+        treeData: DataNode[]
+        setTreeData: React.Dispatch<React.SetStateAction<DataNode[]>>
+        expandedKeys: React.Key[]
+        setExpandedKeys: React.Dispatch<React.SetStateAction<React.Key[]>>
+        queryFormFolder: CLOUDNEW.GetFolderListProps
+        setQueryFormFolder: React.Dispatch<React.SetStateAction<CLOUDNEW.GetFolderListProps>>
+        breadcrumdData: CLOUDNEW.BreadcrumdData[]
+        setBreadcrumdData: React.Dispatch<React.SetStateAction<CLOUDNEW.BreadcrumdData[]>>
+        loadedKeys: string[]
+        setLoadedKeys: React.Dispatch<React.SetStateAction<string[]>>
+        findParentKeys: (key: string, tree: DataNode[]) => void
+    }
+    interface BreadcrumdData {
+        label: string
+        key: number,
+        currentKey: string
+    }
+    interface GetFolderListProps {
+        folderName?: string
+        parentId?: number
+    }
+    interface AddFolderProps {
+        folderName: string    // 文件夹名称
+        isPublic: boolean,    // 是否公开
+        description?: string  // 备注
+        id?: number,
+        parentId?: number,     // 上层文件夹ID
+        permissionUserId?: number[]  // 指定人员可见
+    }
+    interface GetMaterialListProps {
+        pageNum: number,
+        pageSize: number,
+        designerIds?: number[]  // 设计师
+        folderId?: number       // 文件夹
+        height?: number         
+        width?: number
+        materialName?: string   // 素材名称
+        materialType?: string   // 素材类型
+    }
+    interface AddMaterialProps {
+        designerId: number,    // 设计师
+        fileMime: string,  
+        fileSize: number,
+        height: number,
+        width: number,
+        materialName: string,  // 素材名称
+        materialType: string,  // 素材类型
+        md5: string,           
+        ossUrl: string,        // 链接地址
+        folderId?: number,     // 文件夹ID  
+        aspectRatio?: string,  // 素材比率
+        description?: string,  // 描述
+        videoDuration?: number // 视频时长
+    }
+    interface UpdateMaterialProps {
+        folderId?: number,     // 文件夹ID  
+        id: number,
+        materialName: string,  // 素材名称
+        description?: string,  // 描述
+    }
+}

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

@@ -34,10 +34,16 @@
         padding: 3px 11px;
     }
 
+    .ant-transfer .ant-btn {
+        padding: 0px 0;
+        height: 24px;
+    }
+
     .ant-space .ant-form-item {
         margin-bottom: 16px;
     }
 
+    .ant-transfer-list,
     .ant-picker,
     .ant-input,
     .ant-select-selector,
@@ -45,6 +51,10 @@
         border-radius: var(--boder) !important;
     }
 
+    .ant-transfer-list-header {
+        border-radius: var(--boder) var(--boder) 0 0 !important;
+    }
+
     .ant-input-group {
         .ant-input-affix-wrapper:not(:last-child) {
             border-top-right-radius: 0 !important;
@@ -144,4 +154,15 @@
     display: flex;
     flex-direction: column;
     gap: var(--g, 0);
+}
+
+
+.buttonResetCss {
+    .ant-btn {
+        font-size: 12px;
+        height: 28px;
+        padding: 3px 11px;
+        border-radius: 6px;
+    }
+    
 }

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

@@ -0,0 +1,127 @@
+import { request } from 'umi';
+import { api } from '../api';
+
+
+/**
+ * 获取文件夹列表
+ * @param data 
+ * @returns 
+ */
+export async function getFolderListApi(data: CLOUDNEW.GetFolderListProps) {
+    return request(api + `/material/folder/listByPage`, {
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 新建文件夹
+ * @param data 
+ * @returns 
+ */
+export async function addFolderApi(data: CLOUDNEW.AddFolderProps) {
+    return request(api + `/material/folder/create`, {
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 修改文件夹
+ * @param data 
+ * @returns 
+ */
+export async function updateFolderApi(data: CLOUDNEW.AddFolderProps) {
+    return request(api + `/material/folder/update`, {
+        method: 'POST',
+        data
+    })
+}
+
+
+/**
+ * 删除文件夹
+ * @param id 
+ * @returns 
+ */
+export async function delFolderApi(id: number) {
+    return request(api + `/material/folder/delete/${id}`, {
+        method: 'DELETE'
+    })
+}
+
+
+/**
+ * 移动文件夹
+ * @param param0 
+ * @returns 
+ */
+export async function moveFolderApi({ targetId, sourceId }: { targetId: number, sourceId: number[] }) {
+    return request(api + `/material/folder/move`, {
+        method: 'POST',
+        data: { sourceId },
+        params: { targetId }
+    })
+}
+
+/**
+ * 文件夹详情
+ * @param id 
+ * @returns 
+ */
+export async function getFolderDetailsApi(id: number) {
+    return request(api + `/material/folder/detail/${id}`, {
+        method: 'GET'
+    })
+}
+
+
+/**
+ * 分页查询素材(无数据)
+ * @param data 
+ * @returns 
+ */
+export async function getMaterialListApi(data: CLOUDNEW.GetMaterialListProps) {
+    return request(api + `/material/material/pageList`, {
+        method: 'POST',
+        data
+    })
+}
+
+
+/**
+ * 新增素材
+ * @param data 
+ * @returns 
+ */
+export async function addMaterialApi(data: CLOUDNEW.AddMaterialProps) {
+    return request(api + `/material/material/add`, {
+        method: 'POST',
+        data
+    })
+}
+
+
+/**
+ * 修改素材
+ * @param data 
+ * @returns 
+ */
+export async function updateMaterialApi(data: CLOUDNEW.UpdateMaterialProps) {
+    return request(api + `/material/material/update`, {
+        method: 'POST',
+        data
+    })
+}
+
+
+/**
+ * 删除素材
+ * @param id 
+ * @returns 
+ */
+export async function delMaterialApi(id: number) {
+    return request(api + `/material/material/delete/${id}`, {
+        method: 'DELETE'
+    })
+}

+ 8 - 0
src/services/operating/account.ts

@@ -140,4 +140,12 @@ export async function verify(params:{resourceId:number,beginTime:string}) {
   return request(`${api}/erp/resourceChangeRecord/checkTime/${resourceId}/${beginTime}`,{
     method:'PUT',
   })
+}
+
+/**
+ * 获取所有人员
+ * @returns 
+ */
+export async function getUserAllApi() {
+  return request(`${api}/erp/user/all`);
 }

+ 0 - 53
src/services/operating/operate.ts

@@ -1,53 +0,0 @@
-import { queryStr } from '@/utils/query';
-import { request } from 'umi';
-import {api} from '../api'
-export type Fans = {
-  beginDate?: string,
-  endDate?: string,
-  mpId?: string,
-  type?: 1 | 2 | 3 | 4,//时间类型 1 按小时;2 按天 3 按周 4 按月
-}
-/**总览统计 */
-export async function getStatistics(params:{ userId: number }) {
-  return request(api+`/system/statistics${queryStr(params)}`);
-}
-/**指定公众号的统计数据 */
-export async function getStatisticsMpId(mpId:string) {
-  return request(`${api}/system/statistics/${mpId}`);
-}
-/**图文分析 */
-export async function getArticleSummary(params: Fans) {
-  return request(`${api}/system/statistics/articleSummary${queryStr(params)}`);
-}
-/**粉丝增长 */
-export async function getFanGrowth(params: Fans) {
-  return request(`${api}/system/statistics/fanGrowth${queryStr(params)}`);
-}
-/**粉丝属性 */
-export async function getFansAttribute(params: Fans) {
-  return request(`${api}/system/statistics/fansAttribute${queryStr(params)}`);
-}
-/**统计指定公众号粉丝的活跃数据 */
-export async function getFansActive(params: { mpId: string, timeDto: any }) {
-  let { mpId, timeDto } = params
-  return request(`${api}/system/statistics/fansActive/${mpId}`, {
-    method: 'POST',
-    data: timeDto
-  });
-}
-/**统计指定公众号粉丝的互动数据 */
-export async function getFansInteractive(params: { mpId: string, timeDto: any }) {
-  let { mpId, timeDto } = params
-  return request(`${api}/system/statistics/fansInteractive/${mpId}`, {
-    method: 'POST',
-    data: timeDto
-  });
-}
-/**统计指定公众号粉丝的忠诚度 */
-export async function getFansKeep(params: { mpId: string, statisticsKeepDto: any }) {
-  let { mpId, statisticsKeepDto } = params
-  return request(`${api}/system/statistics/fansKeep/${mpId}`, {
-    method: 'POST',
-    data: statisticsKeepDto
-  });
-}