Procházet zdrojové kódy

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

wjx před 9 měsíci
rodič
revize
ff3b2a75df

+ 6 - 0
config/routerConfig.ts

@@ -162,6 +162,12 @@ const launchSystemV3 = {
                     path: '/launchSystemV3/tencenTasset/profiles',
                     access: 'profiles',
                     component: './launchSystemV3/tencenTasset/profiles',
+                },
+                {
+                    name: '原生推广页',
+                    path: '/launchSystemV3/tencenTasset/wechatCanvasPage',
+                    access: 'wechatCanvasPage',
+                    component: './launchSystemV3/tencenTasset/wechatCanvasPage',
                 }
             ],
         },

+ 237 - 237
src/app.tsx

@@ -20,275 +20,275 @@ import versions from './utils/versions';
 
 
 interface CurrentUser {
-  avatar?: string;
-  name?: string;
-  title?: string;
-  group?: string;
-  signature?: string;
-  tags?: {
-    key: string;
-    label: string;
-  }[];
-  userId?: string;
-  powerLevel?: number;
-  access?: 'user' | 'guest' | 'admin' | any;
-  unreadCount?: number;
-  companyList?: any[],
-  onlineCompanyId?: number
+    avatar?: string;
+    name?: string;
+    title?: string;
+    group?: string;
+    signature?: string;
+    tags?: {
+        key: string;
+        label: string;
+    }[];
+    userId?: string;
+    powerLevel?: number;
+    access?: 'user' | 'guest' | 'admin' | any;
+    unreadCount?: number;
+    companyList?: any[],
+    onlineCompanyId?: number
 }
 
 export async function getInitialState(): Promise<{
-  currentUser?: CurrentUser;
-  settings?: LayoutSettings;
-  menu?: any,
-  loading?: boolean,
-  collapsed?: string,
-  onCollapse?: (onCollapse: boolean) => void
+    currentUser?: CurrentUser;
+    settings?: LayoutSettings;
+    menu?: any,
+    loading?: boolean,
+    collapsed?: string,
+    onCollapse?: (onCollapse: boolean) => void
 }> {
-  // 如果是登录页面,不执行
-  if (sessionStorage.getItem('Admin-Token')) {
-    //开始版本号对比
-    versions()
-    try {
-      sessionStorage.removeItem('msg')
-      let currentUser = {}
-      let userInfo: any = await queryCurrent();//用户信息
-      let companyInfo = userInfo?.data?.companyRelationInfo?.filter((item: { companyId: number }) => item.companyId !== 4 && item.companyId !== 3)
-      currentUser = { access: 'admin', powerLevel: userInfo?.data?.userInfo?.powerLevel, name: userInfo?.data?.userInfo?.nickname || '', userId: userInfo?.data?.userInfo?.userId, phone: userInfo?.data?.userInfo?.phone || '', companyList: companyInfo, onlineCompanyId: userInfo?.data?.onlineCompanyId }//处理个人信息 
-      let menu: any = await getMenu().then(async (res: any) => {//获取菜单并处理
-        let { code, data } = res
-        let path = getMyMenu(code, data)
-        if (userInfo?.data?.userInfo?.account === 'admin' && path.length > 0) {//假如是ADMIN加入api测试
-          path[0].routes.push({ key: 0, path: '/operatePage/apitest', name: 'api测试', icon: '', component: './operatePage/apitest', roles: 'admin' })
-        }
-        return { data: path.reverse() }
-      });
-      localStorage.setItem('sex', userInfo?.data?.userInfo?.sex)
-      localStorage.setItem('userId', userInfo?.data?.userInfo?.userId)
-      localStorage.setItem('name', userInfo?.data?.userInfo?.nickname)
-      return {
-        currentUser,
-        settings: { ...defaultSettings },
-        loading: false,
-        menu,
-        collapsed: '0',
-        onCollapse: (collapsed: boolean) => {
-          let v = collapsed ? '1' : '0'
-          localStorage.setItem('collapsed', v)
+    // 如果是登录页面,不执行
+    if (sessionStorage.getItem('Admin-Token')) {
+        //开始版本号对比
+        versions()
+        try {
+            sessionStorage.removeItem('msg')
+            let currentUser = {}
+            let userInfo: any = await queryCurrent();//用户信息
+            let companyInfo = userInfo?.data?.companyRelationInfo?.filter((item: { companyId: number }) => item.companyId !== 4 && item.companyId !== 3)
+            currentUser = { access: 'admin', powerLevel: userInfo?.data?.userInfo?.powerLevel, name: userInfo?.data?.userInfo?.nickname || '', userId: userInfo?.data?.userInfo?.userId, phone: userInfo?.data?.userInfo?.phone || '', companyList: companyInfo, onlineCompanyId: userInfo?.data?.onlineCompanyId }//处理个人信息 
+            let menu: any = await getMenu().then(async (res: any) => {//获取菜单并处理
+                let { code, data } = res
+                let path = getMyMenu(code, data)
+                if (userInfo?.data?.userInfo?.account === 'admin' && path.length > 0) {//假如是ADMIN加入api测试
+                    path[0].routes.push({ key: 0, path: '/operatePage/apitest', name: 'api测试', icon: '', component: './operatePage/apitest', roles: 'admin' })
+                }
+                return { data: path.reverse() }
+            });
+            localStorage.setItem('sex', userInfo?.data?.userInfo?.sex)
+            localStorage.setItem('userId', userInfo?.data?.userInfo?.userId)
+            localStorage.setItem('name', userInfo?.data?.userInfo?.nickname)
+            return {
+                currentUser,
+                settings: { ...defaultSettings },
+                loading: false,
+                menu,
+                collapsed: '0',
+                onCollapse: (collapsed: boolean) => {
+                    let v = collapsed ? '1' : '0'
+                    localStorage.setItem('collapsed', v)
 
+                }
+            };
+        } catch (error) {
+            console.log(111111, error)
+            history.push('/user/login');
         }
-      };
-    } catch (error) {
-      console.log(111111, error)
-      history.push('/user/login');
     }
-  }
-  return {
-    settings: defaultSettings,
-  };
+    return {
+        settings: defaultSettings,
+    };
 }
 // 枚举转译服务端菜单icon
 const IconMap = {
-  desktop: <DesktopOutlined />,
-  message: <MessageOutlined />,
-  send: <SendOutlined />,
-  team: <TeamOutlined />,
-  database: <DatabaseOutlined />,
-  qrcode: <QrcodeOutlined />,
-  read: <ReadOutlined />,
-  mobile: <MobileOutlined />,
-  fundView: <FundViewOutlined />,
-  radarChart: <RadarChartOutlined />,
-  barChart: <BarChartOutlined />,
-  wechat: <WechatOutlined />,
-  book: <BookOutlined />,
-  peoples: <UserOutlined />,
-  'file-image': <FileImageOutlined />,
-  launch: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><LaunchSvg /></span>,
-  adLaunch: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><AdLaunchSvg /></span>,
-  material: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><MaterialSvg /></span>,
-  gdt: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><GdtSvg /></span>,
-  monitor: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><MonitorSvg /></span>,
-  eye: <EyeOutlined />,
-  user: <UserSwitchOutlined />,
-  asset: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><AssetSvg /></span>,
-  assetLibrary: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><AssetLibrarySvg /></span>,
+    desktop: <DesktopOutlined />,
+    message: <MessageOutlined />,
+    send: <SendOutlined />,
+    team: <TeamOutlined />,
+    database: <DatabaseOutlined />,
+    qrcode: <QrcodeOutlined />,
+    read: <ReadOutlined />,
+    mobile: <MobileOutlined />,
+    fundView: <FundViewOutlined />,
+    radarChart: <RadarChartOutlined />,
+    barChart: <BarChartOutlined />,
+    wechat: <WechatOutlined />,
+    book: <BookOutlined />,
+    peoples: <UserOutlined />,
+    'file-image': <FileImageOutlined />,
+    launch: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><LaunchSvg /></span>,
+    adLaunch: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><AdLaunchSvg /></span>,
+    material: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><MaterialSvg /></span>,
+    gdt: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><GdtSvg /></span>,
+    monitor: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><MonitorSvg /></span>,
+    eye: <EyeOutlined />,
+    user: <UserSwitchOutlined />,
+    asset: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><AssetSvg /></span>,
+    assetLibrary: <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><AssetLibrarySvg /></span>,
 };
 //处理菜单
 const loopMenuItem = (menus: MenuDataItem[],): MenuDataItem[] => {
-  let menu = menus?.map(({ icon, routes, roles, ...item }) => {
-    let newItem = JSON.parse(JSON.stringify(item))
-    newItem.key = item?.path || item?.key
-    return {
-      ...newItem,
-      hideInMenu: false,//校验隐藏菜单
-      icon: icon && IconMap[icon as string],
-      children: routes && loopMenuItem(routes),
-    }
-  })
-  return menu
+    let menu = menus?.map(({ icon, routes, roles, ...item }) => {
+        let newItem = JSON.parse(JSON.stringify(item))
+        newItem.key = item?.path || item?.key
+        return {
+            ...newItem,
+            hideInMenu: false,//校验隐藏菜单
+            icon: icon && IconMap[icon as keyof typeof IconMap],
+            children: routes && loopMenuItem(routes),
+        }
+    })
+    return menu
 }
 export const layout = ({
-  initialState
+    initialState
 }: {
-  initialState: { settings?: LayoutSettings; currentUser?: CurrentUser; menu?: any; loading: boolean, collapsed: string, onCollapse: any };
+    initialState: { settings?: LayoutSettings; currentUser?: CurrentUser; menu?: any; loading: boolean, collapsed: string, onCollapse: any };
 }): BasicLayoutProps => {
-  return {
-    menuDataRender: () => {
-      return loopMenuItem(initialState?.menu?.data || [])
-    },//解析服务端菜单
-    rightContentRender: () => <RightContent />,
-    disableContentMargin: false,
-    collapsed: localStorage.collapsed === '1',
-    onCollapse: initialState.onCollapse,
-    breakpoint: false,
-    // footerRender: () => <Footer />,
-    onPageChange: () => {
-      headrRouter(initialState, history)
-      let { pathname, query } = history.location
-      if (query?.t) {//带token直接进入对应页面
-        sessionStorage.setItem('Admin-Token', decodeURIComponent(query.t as any))
-        location.href = window.location.origin + '/#' + pathname
-        location.reload()
-      } else if (!initialState?.currentUser?.name && history.location.pathname !== '/user/login') {
-        history.push('/user/login');
-      }
-    },
-    splitMenus: true,//配合mix分割菜单
-    menuHeaderRender: undefined,
-    ...initialState?.settings,
-  };
+    return {
+        menuDataRender: () => {
+            return loopMenuItem(initialState?.menu?.data || [])
+        },//解析服务端菜单
+        rightContentRender: () => <RightContent />,
+        disableContentMargin: false,
+        collapsed: localStorage.collapsed === '1',
+        onCollapse: initialState.onCollapse,
+        breakpoint: false,
+        // footerRender: () => <Footer />,
+        onPageChange: () => {
+            headrRouter(initialState, history)
+            let { pathname, query } = history.location
+            if (query?.t) {//带token直接进入对应页面
+                sessionStorage.setItem('Admin-Token', decodeURIComponent(query.t as any))
+                location.href = window.location.origin + '/#' + pathname
+                location.reload()
+            } else if (!initialState?.currentUser?.name && history.location.pathname !== '/user/login') {
+                history.push('/user/login');
+            }
+        },
+        splitMenus: true,//配合mix分割菜单
+        menuHeaderRender: undefined,
+        ...initialState?.settings,
+    };
 };
 const codeMessage = {
-  200: '服务器成功返回请求的数据。',
-  201: '新建或修改数据成功。',
-  202: '一个请求已经进入后台排队(异步任务)。',
-  204: '删除数据成功。',
-  400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
-  401: '用户没有权限(令牌、用户名、密码错误)。',
-  403: '用户得到授权,但是访问是被禁止的。',
-  404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
-  405: '请求方法不被允许。',
-  406: '请求的格式不可得。',
-  410: '请求的资源被永久删除,且不会再得到的。',
-  422: '当创建一个对象时,发生一个验证错误。',
-  500: '服务器发生错误,请检查服务器。',
-  502: '网关错误。',
-  503: '服务不可用,服务器暂时过载或维护。',
-  504: '网关超时。',
+    200: '服务器成功返回请求的数据。',
+    201: '新建或修改数据成功。',
+    202: '一个请求已经进入后台排队(异步任务)。',
+    204: '删除数据成功。',
+    400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
+    401: '用户没有权限(令牌、用户名、密码错误)。',
+    403: '用户得到授权,但是访问是被禁止的。',
+    404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
+    405: '请求方法不被允许。',
+    406: '请求的格式不可得。',
+    410: '请求的资源被永久删除,且不会再得到的。',
+    422: '当创建一个对象时,发生一个验证错误。',
+    500: '服务器发生错误,请检查服务器。',
+    502: '网关错误。',
+    503: '服务不可用,服务器暂时过载或维护。',
+    504: '网关超时。',
 };
 
 /**
  * 异常处理程序
  */
 const errorHandler = (error: ResponseError) => {
-  const { response } = error;
-  if (response && response.status) {
-    const errorText = codeMessage[response.status] || response.statusText;
-    const { status, url } = response;
-    notification.error({
-      message: `请求错误 ${status}: ${url}`,
-      description: errorText,
-    });
-  }
+    const { response } = error;
+    if (response && response.status) {
+        const errorText = codeMessage[response.status as keyof typeof codeMessage] || response.statusText;
+        const { status, url } = response;
+        notification.error({
+            message: `请求错误 ${status}: ${url}`,
+            description: errorText,
+        });
+    }
 
-  if (!response) {
-    notification.error({
-      description: '您的网络发生异常,无法连接服务器',
-      message: '网络异常',
-    });
-  }
-  throw error;
+    if (!response) {
+        notification.error({
+            description: '您的网络发生异常,无法连接服务器',
+            message: '网络异常',
+        });
+    }
+    throw error;
 };
 //请求处理
 export const request: RequestConfig = {
-  errorHandler,
-  timeout: 300000,
-  headers: { ['Authorization']: 'Bearer ' + sessionStorage.getItem('Admin-Token') },
-  errorConfig: {
-    adaptor: (resData: any) => {
-      let div: any
-      // if (!document.getElementById('notificationPop')) {
-      //   div = document.createElement('div')
-      //   div.id = 'notificationPop'
-      // }
-      if (resData.code === 500) {
-        let msg = sessionStorage.getItem('msg')
-        if (resData.msg === '令牌验证失败') {//
-          if (history.location.pathname !== '/user/login') {
-            if (!msg) {
-              // document.body.appendChild(div)
-              const key = `open${Date.now()}`;
-              const btn = (
-                <Button type="primary" onClick={() => {
-                  // setCookie('Admin-Token', '1', -300)
-                  sessionStorage.removeItem('Admin-Token')
-                  history.push('/user/login')
-                  notification.close(key)
-                  sessionStorage.removeItem('msg')
-                  // document.body.removeChild(div)
-                }}>
-                  重新登录
-                </Button>
-              );
-              const description = (
-                <Result
-                  status="500"
-                  title='令牌验证失败!'
-                  subTitle="长时间没有操作,令牌失效请重新登录!"
-                  extra={btn}
-                />
-              )
-              sessionStorage.setItem('msg', 'true')
-              notification.open({
-                message: '',
-                description,
-                duration: null,
-                getContainer: () => document.getElementById('notificationPop') || document.getElementById('root') as HTMLElement,
-                key,
-                style: { zIndex: 999999, right: '50%', top: '50%', transform: 'translate(50%, -50%)', position: 'fixed', transition: 'all 0s' },
-                onClose: () => {
-                  // setCookie('Admin-Token', '1', -300)
-                  sessionStorage.removeItem('Admin-Token')
-                  history.push('/user/login')
-                  sessionStorage.removeItem('msg')
-                  // document.body.removeChild(div)
+    errorHandler,
+    timeout: 300000,
+    headers: { ['Authorization']: 'Bearer ' + sessionStorage.getItem('Admin-Token') },
+    errorConfig: {
+        adaptor: (resData: any) => {
+            let div: any
+            // if (!document.getElementById('notificationPop')) {
+            //   div = document.createElement('div')
+            //   div.id = 'notificationPop'
+            // }
+            if (resData.code === 500) {
+                let msg = sessionStorage.getItem('msg')
+                if (resData.msg === '令牌验证失败') {//
+                    if (history.location.pathname !== '/user/login') {
+                        if (!msg) {
+                            // document.body.appendChild(div)
+                            const key = `open${Date.now()}`;
+                            const btn = (
+                                <Button type="primary" onClick={() => {
+                                    // setCookie('Admin-Token', '1', -300)
+                                    sessionStorage.removeItem('Admin-Token')
+                                    history.push('/user/login')
+                                    notification.close(key)
+                                    sessionStorage.removeItem('msg')
+                                    // document.body.removeChild(div)
+                                }}>
+                                    重新登录
+                                </Button>
+                            );
+                            const description = (
+                                <Result
+                                    status="500"
+                                    title='令牌验证失败!'
+                                    subTitle="长时间没有操作,令牌失效请重新登录!"
+                                    extra={btn}
+                                />
+                            )
+                            sessionStorage.setItem('msg', 'true')
+                            notification.open({
+                                message: '',
+                                description,
+                                duration: null,
+                                getContainer: () => document.getElementById('notificationPop') || document.getElementById('root') as HTMLElement,
+                                key,
+                                style: { zIndex: 999999, right: '50%', top: '50%', transform: 'translate(50%, -50%)', position: 'fixed', transition: 'all 0s' },
+                                onClose: () => {
+                                    // setCookie('Admin-Token', '1', -300)
+                                    sessionStorage.removeItem('Admin-Token')
+                                    history.push('/user/login')
+                                    sessionStorage.removeItem('msg')
+                                    // document.body.removeChild(div)
+                                }
+                            });
+                        }
+                        return { ...resData }
+                    } else {
+                        sessionStorage.removeItem('Admin-Token')
+                    }
+                }
+                if (!msg) {
+                    sessionStorage.setItem('msg', 'true')
+                    notification.error({
+                        message: resData.msg,
+                        onClose: () => {
+                            sessionStorage.removeItem('msg')
+                        }
+                    });
                 }
-              });
+
             }
-            return { ...resData }
-          } else {
-            sessionStorage.removeItem('Admin-Token')
-          }
-        }
-        if (!msg) {
-          sessionStorage.setItem('msg', 'true')
-          notification.error({
-            message: resData.msg,
-            onClose: () => {
-              sessionStorage.removeItem('msg')
+            if (resData.code === 310) {//权限错误
+                let msg = sessionStorage.getItem('msg')
+                sessionStorage.removeItem('Admin-Token')
+                history.push('/user/login')
+                if (!msg) {
+                    sessionStorage.setItem('msg', 'true')
+                    notification.error({
+                        message: resData.msg,
+                        onClose: () => {
+                            sessionStorage.removeItem('msg')
+                        }
+                    });
+                }
             }
-          });
-        }
-
-      }
-      if (resData.code === 310) {//权限错误
-        let msg = sessionStorage.getItem('msg')
-        sessionStorage.removeItem('Admin-Token')
-        history.push('/user/login')
-        if (!msg) {
-          sessionStorage.setItem('msg', 'true')
-          notification.error({
-            message: resData.msg,
-            onClose: () => {
-              sessionStorage.removeItem('msg')
+            return {
+                ...resData,
             }
-          });
-        }
-      }
-      return {
-        ...resData,
-      }
+        },
     },
-  },
 };

+ 11 - 11
src/components/FileBoxAD/components/imgModal/index.tsx

@@ -1,10 +1,11 @@
-import React, { useCallback, useMemo, useState } from 'react'
+import React, { useMemo, useState } from 'react'
 import { Button, Form, Input, InputNumber, message, Modal, Space, Upload } from 'antd'
 import { useModel } from 'umi'
 import { RcFile } from 'antd/lib/upload'
 import CropperImg from '../Cropper'
 import { UploadOutlined } from '@ant-design/icons'
 import style from './index.less'
+import '../../../../pages/launchSystemV3/tencentAdPutIn/index.less'
 
 /**新建非图文素材 */
 let ImgModal = React.memo((props: { isAll?: boolean }) => {
@@ -33,7 +34,7 @@ let ImgModal = React.memo((props: { isAll?: boolean }) => {
     const getVideo = useMemo(() => {
         if (mediaType === 'VIDEO') {
             if (queryForm?.file) {
-                return <video src={URL.createObjectURL(queryForm?.file)} style={{ width: '100%' }} controls />
+                return <video src={URL.createObjectURL(queryForm?.file)} style={{ width: 200 }} controls />
             } else {
                 return null
             }
@@ -44,37 +45,36 @@ let ImgModal = React.memo((props: { isAll?: boolean }) => {
 
     return <div>
         <Modal
-            title={(actionItem ? '编辑' : '新建') + typeEnum[mediaType as string]}
+            title={<strong>{(actionItem ? '编辑' : '新建') + typeEnum[mediaType as keyof typeof typeEnum]}</strong>}
             visible={imgVisrible}
             onOk={(e) => { handleOk(e) }}
             confirmLoading={upLoadLoading}
             onCancel={() => {
                 offEditFile();
             }}
-            width={450}
+            width={700}
             destroyOnClose
             maskClosable={false}
+            className='modalResetCss'
         >
             <Form
                 name="basic"
                 labelCol={{ span: 4 }}
                 wrapperCol={{ span: 20 }}
                 autoComplete="off"
+                colon={false}
+                labelAlign="left"
             >
-                <Form.Item
-                    label="图片名称"
-                >
+                <Form.Item label={<strong>图片名称</strong>}>
                     <Input value={queryForm.title} onChange={(e) => setQueryForm({ ...queryForm, title: e.target.value })} placeholder="请输入图片名称" />
                 </Form.Item>
                 <Form.Item
-                    label="排序"
+                    label={<strong>排序</strong>}
                     tooltip="数值越大越靠前"
                 >
                     <InputNumber value={queryForm.sort} onChange={(e) => setQueryForm({ ...queryForm, sort: e })} placeholder="请输入排序" />
                 </Form.Item>
-                <Form.Item
-                    label={'上传' + typeEnum[mediaType as string]}
-                >
+                <Form.Item label={<strong>{'上传' + typeEnum[mediaType as keyof typeof typeEnum]}</strong>}>
                     {mediaType === 'IMG' ? <Space>
                         <Upload
                             listType="picture-card"

+ 1 - 1
src/components/FileBoxAD/index.tsx

@@ -76,7 +76,7 @@ function FlieBox(props: Props) {
 
     // 处理数据
     useEffect(() => {
-        let newList = {}
+        let newList: any = {}
         listData?.records?.forEach((item: { id: number }) => {
             newList[item.id] = JSON.parse(JSON.stringify(item))
         })

+ 6 - 6
src/models/useLaunchAdq/useBdMedia.ts

@@ -55,7 +55,7 @@ function reducer(state: State, action: Action) {
     switch (type) {
         case 'set':
             Object.keys(params as State).forEach((key: string) => {
-                newState[key] = (params as State)[key]
+                newState[key] = (params as any)[key]
             })
             return newState
         case 'init':
@@ -136,14 +136,14 @@ function useBdMediaPup() {
                 let fileSize = size || 0
                 if (!size) {
                     if (mediaType === 'IMG') {
-                        fileSize = 307200
+                        fileSize = 409600
                     } else {
-                        fileSize = 104857600
+                        fileSize = 524288000
                     }
                 }
 
                 if (mediaType === 'IMG') {
-                    if (file?.size > fileSize) { // 大于300kb进入压缩
+                    if (file?.size > fileSize) { // 大于400kb进入压缩
                         let bole = await compressAccurately(file, fileSize / 1024 - 50)
                         if (bole?.size > fileSize) {
                             bole = await compressAccurately(file, fileSize / 1024 - 100)
@@ -201,7 +201,7 @@ function useBdMediaPup() {
                                 /**取到返回的文件地址向后端发送具体数据*/
                                 if (res2?.data?.url) {
                                     let fileMd5 = await getMD5(newFile)
-                                    let obj = { title: data.title, mediaType, folder: false, parentId, width, height, fileMd5, belongUser: belongUser === '0' ? false : true, url: res2?.data?.url, sort: data?.sort, fileSize: newFile?.size, fileMime: newFile.type }
+                                    let obj: any = { title: data.title, mediaType, folder: false, parentId, width, height, fileMd5, belongUser: belongUser === '0' ? false : true, url: res2?.data?.url, sort: data?.sort, fileSize: newFile?.size, fileMime: newFile.type }
                                     if (mediaType === 'VIDEO') {
                                         obj['videoTitle'] = data?.videoTitle || data?.title
                                         obj['videoDescription'] = data?.videoDescription
@@ -311,7 +311,7 @@ function useBdMediaPup() {
                 let data1: { code: number, data: { url: string } } = await request(res1?.ossUrl, { method: 'post', body: formData }).catch(() => set({ upLoadLoading: false }))
                 message.success('上传成功')
                 let fileMd5 = await getMD5(newFile)
-                let obj = { title: data.title ? data.title + '_' + (index + 1).toString() : file?.name + '_' + (index + 1).toString(), folder: false, parentId, width, height, fileMd5, url: data1?.data?.url, sort: data?.sort, fileSize: newFile?.size, fileMime: newFile.type }
+                let obj: any = { title: data.title ? data.title + '_' + (index + 1).toString() : file?.name + '_' + (index + 1).toString(), folder: false, parentId, width, height, fileMd5, url: data1?.data?.url, sort: data?.sort, fileSize: newFile?.size, fileMime: newFile.type }
                 if (mediaType === 'VIDEO') {
                     obj['videoTitle'] = data?.videoTitle || data?.title
                     obj['videoDescription'] = data?.videoDescription

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

@@ -74,7 +74,7 @@ function reducer(state: State, action: Action) {
   switch (type) {
     case 'set':
       Object.keys(params as State).forEach((key: string) => {
-        newState[key] = (params as State)[key];
+        newState[key] = (params as any)[key];
       });
       return newState;
     case 'init':
@@ -193,15 +193,15 @@ function useBdMediaPup() {
         let fileSize = size || 0;
         if (!size) {
           if (mediaType === 'IMG') {
-            fileSize = 307200;
+            fileSize = 409600
           } else {
-            fileSize = 104857600;
+            fileSize = 524288000
           }
         }
 
         if (mediaType === 'IMG') {
           if (file?.size > fileSize) {
-            // 大于300kb进入压缩
+            // 大于400kb进入压缩
             let bole = await compressAccurately(file, 250);
             if (bole?.size > 300000) {
               bole = await compressAccurately(file, 200);
@@ -268,7 +268,7 @@ function useBdMediaPup() {
                     /**取到返回的文件地址向后端发送具体数据*/
                     if (res2?.data?.url) {
                       let fileMd5 = await getMD5(newFile);
-                      let obj = {
+                      let obj: any = {
                         title: data.title,
                         mediaType,
                         folder: false,
@@ -393,7 +393,7 @@ function useBdMediaPup() {
         let data1: { code: number, data: { url: string } } = await request(res1?.ossUrl, { method: 'post', body: formData }).catch(() => set({ upLoadLoading: false }))
         message.success('上传成功')
         let fileMd5 = await getMD5(newFile)
-        let obj = { title: data.title + (index + 1).toString(), folder: false, parentId, width, height, fileMd5, url: data1?.data?.url, sort: data?.sort, fileSize: newFile?.size, fileMime: newFile.type }
+        let obj: any = { title: data.title + (index + 1).toString(), folder: false, parentId, width, height, fileMd5, url: data1?.data?.url, sort: data?.sort, fileSize: newFile?.size, fileMime: newFile.type }
         if (mediaType === 'VIDEO') {
           obj['videoTitle'] = data?.videoTitle || data?.title
           obj['videoDescription'] = data?.videoDescription

+ 1 - 1
src/pages/launchSystemNew/material/cloud/index.tsx

@@ -42,7 +42,7 @@ function Cloud() {
 
             {
                 ['IMG', 'VIDEO', 'PAGE'].map((key: any) => {
-                    return <TabPane tab={typeEnum[key]} key={key} style={{ backgroundColor: '#fff', padding: '0 15px' }} >
+                    return <TabPane tab={typeEnum[key as keyof typeof typeEnum]} key={key} style={{ backgroundColor: '#fff', padding: '0 15px' }} >
                         <Tabs tabBarStyle={{ marginBottom: 0 }} onChange={(activeKey: any) => { set({ belongUser: activeKey }) }} activeKey={belongUser} tabBarExtraContent={<Space>
                             {
                                 mediaType === 'PAGE' ? <Button type="primary" size='small' onClick={() => { setVisible(true) }}>新建素材</Button> : <Button size='small' type="primary" onClick={() => { set({ imgVisrible: true }) }}>新建素材</Button>

+ 431 - 0
src/pages/launchSystemV3/tencenTasset/wechatCanvasPage/copyPage.tsx

@@ -0,0 +1,431 @@
+import InputName from "@/components/InputName"
+import { useAjax } from "@/Hook/useAjax"
+import { getImagesInfoApi, getVideosInfoApi } from "@/services/adqV3/global"
+import { txtLength } from "@/utils/utils"
+import { DeleteOutlined, PlusOutlined } from "@ant-design/icons"
+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"
+
+interface Props {
+    accountId: number
+    pageData: any
+    visible?: boolean
+    onClose?: () => void
+    onChange?: () => void
+}
+/**
+ * 批量复制落地页
+ * @returns 
+ */
+const CopyPage: React.FC<Props> = ({ accountId, pageData, visible, onChange, onClose }) => {
+
+    /********************************/
+    const { init } = useModel('useLaunchAdq.useBdMediaPup')
+    const [materialConfig, setMaterialConfig] = useState<{
+        adcreativeTemplateId?: number,
+        type: string,
+        cloudSize: { relation: string, width: number, height: number }[],
+        list: any[],
+        index: number,
+        max: number,
+        sliderImgContent: any,
+        isGroup?: boolean
+    }>({
+        type: '',//类型
+        cloudSize: [],//素材搜索条件
+        list: [],//素材
+        index: 0, // 素材组下标
+        max: 1,//素材数量
+        sliderImgContent: undefined
+    })//图片素材配置
+    const [selectVideoVisible, setSelectVideoVisible] = useState(false)
+    const [form] = Form.useForm();
+    const downPageMaterialDTOS = Form.useWatch('downPageMaterialDTOS', form)
+    const [mType, setMtype] = useState<{ width: number, height: number, type: "IMAGE" | 'VIDEO' | 'CAROUSEL', number: number }>()
+
+    const getImagesInfo = useAjax((params) => getImagesInfoApi(params))
+    const getVideosInfo = useAjax((params) => getVideosInfoApi(params))
+    const batchCreateDownPage = useAjax((params) => batchCreateDownPageApi(params))
+    /********************************/
+
+    useEffect(() => {
+        console.log(pageData)
+        if (pageData?.pageElementsSpecList) {
+            form.setFieldsValue({ pageName: pageData?.pageName })
+            let { elementType, ...spec } = pageData?.pageElementsSpecList[0]
+            switch (elementType) {
+                case "IMAGE": // 图片
+                case "CAROUSEL": // 轮播
+                    let imageIdList = spec.imageSpec.imageIdList
+                    getImagesInfo.run({ adAccountId: accountId, imageIds: [imageIdList[0]], pageNum: 1, pageSize: 1 }).then(res => {
+                        if (res?.records?.length) {
+                            const { imageWidth, imageHeight } = res.records[0]
+                            setMtype({ width: imageWidth, height: imageHeight, type: elementType, number: imageIdList.length })
+                        }
+                    })
+                    break
+                case "VIDEO": // 视频
+                    let videoId = spec.videoSpec.videoId
+                    getVideosInfo.run({ adAccountId: accountId, videoIds: [videoId] }).then(res => {
+                        console.log(res)
+                        if (res?.length) {
+                            const { width, height } = res[0]
+                            setMtype({ width, height, type: elementType, number: 1 })
+                        }
+                    })
+                    break
+            }
+        }
+    }, [pageData])
+
+    const handleOk = (values: any) => {
+        console.log(values)
+        const { pageName, downPageMaterialDTOS: data } = values
+        let downPageMaterialDTOS: { materialType: number, videoId?: number, imageId?: number[] }[] = []
+        if (mType?.type === 'VIDEO') {
+            downPageMaterialDTOS = data.map((item: { videoId: { id: any; materialType: any } }) => {
+                const { id, materialType } = item.videoId
+                return { videoId: id, materialType }
+            })
+        } else {
+            downPageMaterialDTOS = data.map((item: { imageId: { id: any; materialType: any }[] }) => {
+                const { materialType } = item.imageId[0]
+                return { imageId: item.imageId.map(item => item.id), materialType }
+            })
+        }
+        batchCreateDownPage.run({ pageName, downPageMaterialDTOS, accountId, templateId: pageData.pageId }).then(res => {
+            if (res?.data?.length) {
+                message.error(res.data.toString())
+            } else {
+                message.success('新增成功')
+                onChange?.()
+            }
+        })
+    }
+
+    return <Modal
+        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 })
+                setMaterialConfig({
+                    ...materialConfig,
+                    type: 'imageId',
+                    max: mType.number || 1,
+                    index: 99999,
+                    adcreativeTemplateId: 721
+                })
+                setTimeout(() => {
+                    setSelectVideoVisible(true)
+                }, 100)
+            }}>批量添加图片素材</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 })
+                setMaterialConfig({
+                    ...materialConfig,
+                    type: 'videoId',
+                    max: 1,
+                    index: 99999,
+                    adcreativeTemplateId: 311
+                })
+                setTimeout(() => {
+                    setSelectVideoVisible(true)
+                }, 100)
+            }}>批量添加视频素材</Button>}
+        </Space>}
+        visible={visible}
+        onCancel={onClose}
+        className="modalResetCss"
+        width={900}
+        bodyStyle={{ padding: '0 0 40px', position: 'relative', borderRadius: '0 0 8px 8px' }}
+        footer={null}
+    >
+        <Form
+            form={form}
+            name="copyPage"
+            labelAlign='left'
+            layout="vertical"
+            colon={false}
+            style={{ backgroundColor: '#f1f4fc', maxHeight: 650, overflow: 'hidden', overflowY: 'auto', padding: 10, borderRadius: '0 0 8px 8px' }}
+            scrollToFirstError={{
+                behavior: 'smooth',
+                block: 'center'
+            }}
+            onFinishFailed={({ errorFields }) => {
+                message.error(errorFields?.[0]?.errors?.[0])
+            }}
+            onFinish={handleOk}
+            initialValues={{
+                downPageMaterialDTOS: [undefined]
+            }}
+        >
+            <Card className="cardResetCss" style={{ marginBottom: 10 }}>
+                <Form.Item
+                    label={<strong>落地页名称</strong>}
+                    name='pageName'
+                    rules={[
+                        { required: true, message: '请输入落地页名称!' },
+                        {
+                            required: true, message: '广告名称不能包含特殊字符:< > & ‘ ” / 以及TAB、换行、回车键,请修改', validator(_, value) {
+                                let reg = /[&‘’“”/\n\t\f]/ig
+                                if (value && reg.test(value)) {
+                                    return Promise.reject()
+                                }
+                                return Promise.resolve()
+                            }
+                        },
+                        {
+                            required: true, message: '请确保落地页名称长度不超过40个字', validator(_, value) {
+                                if (value && txtLength(value) > 40) {
+                                    return Promise.reject()
+                                }
+                                return Promise.resolve()
+                            }
+                        }
+                    ]}
+                >
+                    <InputName placeholder='落地页名称' style={{ width: 680 }} length={40} />
+                </Form.Item>
+            </Card>
+
+            {mType ? <Form.List name="downPageMaterialDTOS">
+                {(fields, { add, remove }) => (<>
+                    <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10 }}>
+                        {fields.map((field, num) => (<Card
+                            title={<strong style={{ fontSize: 18 }}>素材组{num + 1}</strong>}
+                            className="cardResetCss"
+                            key={field.key}
+                            style={{ width: mType?.type === 'CAROUSEL' ? '100%' : downPageMaterialDTOS?.length > 1 ? 'calc(50% - 5px)' : '100%' }}
+                            extra={fields?.length > 1 && <DeleteOutlined className={styles.clear} onClick={() => remove(field.name)} style={{ color: 'red' }} />}
+                        >
+                            <Space size={30} style={{ width: '100%' }} className={styles.space}>
+                                {mType?.type === "VIDEO" ? <Form.Item
+                                    {...field}
+                                    label={<strong>视频</strong>}
+                                    rules={[{ required: true, message: '请选择素材!' }]}
+                                    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 })
+                                        setMaterialConfig({
+                                            ...materialConfig,
+                                            type: 'videoId',
+                                            max: 1,
+                                            index: num,
+                                            adcreativeTemplateId: 311
+                                        })
+                                        setTimeout(() => {
+                                            setSelectVideoVisible(true)
+                                        }, 100)
+                                    }}>
+                                        <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 }} /> : <>
+                                                <span>{`推荐尺寸(${mType?.width} x ${mType?.height})`}</span>
+                                                <span>{`${['MEDIA_TYPE_MP4', 'MEDIA_TYPE_MOV', 'MEDIA_TYPE_AVI'].map((str: any) => str?.replace('MEDIA_TYPE_', ''))};< ${20}M;时长 ≥ ${5}s,≤ ${300}s,必须带有声音`}</span>
+                                            </>}
+                                        </div>
+                                    </div>
+                                </Form.Item> : mType?.type === 'IMAGE' ? <Form.Item
+                                    {...field}
+                                    label={<strong>图片</strong>}
+                                    rules={[{ required: true, message: '请选择素材!' }]}
+                                    name={[field.name, 'imageId']}
+                                >
+                                    <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 })
+                                            setMaterialConfig({
+                                                ...materialConfig,
+                                                type: 'imageId',
+                                                max: 1,
+                                                index: num,
+                                                adcreativeTemplateId: 721
+                                            })
+                                            setTimeout(() => {
+                                                setSelectVideoVisible(true)
+                                            }, 100)
+                                        }}>
+                                            <p>
+                                                {downPageMaterialDTOS?.length > 0 && downPageMaterialDTOS[num]?.imageId?.length ? <img src={downPageMaterialDTOS[num]['imageId'][0]['url']} /> : <>
+                                                    <span>{`推荐尺寸(${mType?.width} x ${mType?.height})`}</span>
+                                                    <span>{`${['IMAGE_TYPE_JPG', 'IMAGE_TYPE_PNG'].map((str: any) => str?.replace('IMAGE_TYPE_', ''))};小于 300KB`}</span>
+                                                </>}
+                                            </p>
+                                        </div>
+                                    </Space>
+                                </Form.Item> : mType?.type === 'CAROUSEL' ? <Form.Item
+                                    {...field}
+                                    label={<strong>轮播图</strong>}
+                                    rules={[
+                                        { required: true, type: 'array', len: mType.number, message: '素材数量不对!' },
+                                        { required: true, message: '请选择素材!' },
+                                    ]}
+                                    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 })
+                                        setMaterialConfig({
+                                            ...materialConfig,
+                                            type: 'imageId',
+                                            max: mType.number,
+                                            index: num,
+                                            adcreativeTemplateId: 721
+                                        })
+                                        setTimeout(() => {
+                                            setSelectVideoVisible(true)
+                                        }, 100)
+                                    }}>
+                                        {Array(mType.number).fill('').map((arr, index1) => {
+                                            return <p key={index1} style={mType.number >= 3 ? { width: 130, height: 130 } : { justifyContent: 'center' }}>
+                                                {downPageMaterialDTOS?.length > 0 && downPageMaterialDTOS[num] && Object.keys(downPageMaterialDTOS[num])?.includes('imageId') && downPageMaterialDTOS[num]['imageId'][index1] ? <img src={downPageMaterialDTOS[num]['imageId'][index1]['url']} /> : <>
+                                                    <span>{`推荐尺寸(${mType?.width} x ${mType?.height})`}</span>
+                                                    <span>{`${['IMAGE_TYPE_JPG', 'IMAGE_TYPE_PNG']?.map((str: any) => str?.replace('IMAGE_TYPE_', ''))};小于 ${300}KB`}</span>
+                                                </>}
+                                            </p>
+                                        })}
+                                    </div>
+                                </Form.Item> : null}
+                            </Space>
+                        </Card>))}
+                    </div>
+
+                    <Form.Item style={{ marginBottom: 0, marginTop: 10 }}>
+                        <Button type="dashed" style={{ color: '#1890ff' }} onClick={() => add()} block icon={<PlusOutlined />}>添加素材组</Button>
+                    </Form.Item>
+                </>)}
+            </Form.List> : <Spin spinning={getImagesInfo.loading}><div style={{ width: '100%', height: 100 }}></div></Spin>}
+
+
+            <Form.Item className="submit_pull">
+                <Space>
+                    <Button onClick={onClose}>取消</Button>
+                    <Button type="primary" htmlType="submit" className="modalResetCss" loading={batchCreateDownPage.loading}>
+                        确定
+                    </Button>
+                </Space>
+            </Form.Item>
+        </Form>
+
+
+        {selectVideoVisible && <SelectCloud
+            isGroup={materialConfig?.isGroup}
+            visible={selectVideoVisible}
+            onClose={() => setSelectVideoVisible(false)}
+            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
+            }
+            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 max = materialConfig.max
+                            let materialsNew = downPageMaterialDTOS.map((item: any) => {
+                                let newItem = item || {}
+                                // 判断是否有字段,是否设置了值
+                                if (Object.keys(newItem).includes(materialConfig.type) && newItem[materialConfig.type]) {
+                                    if (max > newItem[materialConfig.type].length && urls.length > 0) {
+                                        let difference = max - newItem[materialConfig.type].length
+                                        let material: any[] = []
+                                        if (urls.length >= difference) {
+                                            material = urls.splice(0, difference)
+                                        } else {
+                                            material = urls.splice(0, urls.length)
+                                        }
+                                        newItem[materialConfig.type] = [...newItem[materialConfig.type], ...material]
+                                        return newItem
+                                    } else {
+                                        return newItem
+                                    }
+                                } else {
+                                    if (urls.length >= max) {
+                                        let material = urls.splice(0, max)
+                                        return { ...newItem, [materialConfig.type]: material }
+                                    } else if (urls.length > 0) {
+                                        let material = urls.splice(0, urls.length)
+                                        return { ...newItem, [materialConfig.type]: material }
+                                    } else {
+                                        return newItem
+                                    }
+                                }
+                            })
+                            if (urls.length > 0) {
+                                let data = Array(Math.ceil(urls.length / max)).fill(undefined).map(item => {
+                                    if (urls.length >= max) {
+                                        let material = urls.splice(0, max)
+                                        return { [materialConfig.type]: material }
+                                    } else {
+                                        let material = urls.splice(0, urls.length)
+                                        return { [materialConfig.type]: material }
+                                    }
+                                })
+                                materialsNew = [...materialsNew, ...data]
+                            }
+                            console.log('materialsNew-->', materialsNew)
+                            form.setFieldsValue({ downPageMaterialDTOS: materialsNew })
+                        } else {
+                            let newMaterials = content?.map((item: any) => {
+                                return { [materialConfig.type]: { url: item?.url, id: item?.id, materialType: 0 } }
+                            })
+                            if (newMaterials.length > 0) {
+                                if (downPageMaterialDTOS?.every((item: any) => !item)) { // 没设置过
+                                    form.setFieldsValue({ downPageMaterialDTOS: newMaterials })
+                                } else { // 设置过
+                                    let materialsNew = downPageMaterialDTOS.map((item: any) => {
+                                        let newItem = item || {}
+                                        if (Object.keys(newItem).includes(materialConfig.type) && newItem[materialConfig.type]) {
+                                            return item
+                                        } else {
+                                            if (newMaterials.length > 0) {
+                                                let material = newMaterials.splice(0, 1)
+                                                return { ...newItem, ...material[0] }
+                                            } else {
+                                                return item
+                                            }
+                                        }
+                                    })
+                                    if (newMaterials.length > 0) {
+                                        materialsNew = [...materialsNew, ...newMaterials]
+                                    }
+                                    form.setFieldsValue({ downPageMaterialDTOS: materialsNew })
+                                }
+                            }
+                        }
+                    } else {
+                        let newDynamicGroup = downPageMaterialDTOS?.map((item: any, index: number) => {
+                            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 }))
+                                        return { ...item }
+                                    } else {
+                                        return { [materialConfig.type]: content?.map((item: any) => ({ id: item?.id, url: item?.url, materialType: 0 })) }
+                                    }
+                                } else {
+                                    if (item) {
+                                        item[materialConfig.type] = { id: content[0]?.id, url: content[0]?.url, materialType: 0 }
+                                        return { ...item }
+                                    } else {
+                                        return { [materialConfig.type]: { id: content[0]?.id, url: content[0]?.url, materialType: 0 } }
+                                    }
+                                }
+                            }
+                            return item
+                        })
+                        form.setFieldsValue({ downPageMaterialDTOS: newDynamicGroup })
+                    }
+                }
+                setSelectVideoVisible(false)
+            }}
+        />}
+    </Modal>
+}
+
+export default React.memo(CopyPage)

+ 146 - 0
src/pages/launchSystemV3/tencenTasset/wechatCanvasPage/index.tsx

@@ -0,0 +1,146 @@
+import { Button, Card, Input, Select, Table } from "antd"
+import React, { useEffect, useState } from "react"
+import '../../tencentAdPutIn/index.less'
+import { getAdqLandingPageListApi, getWXDownPageAuthInfoListApi } from "@/services/adqV3/global";
+import { useAjax } from "@/Hook/useAjax";
+import { useModel } from "umi";
+import { SearchOutlined } from "@ant-design/icons";
+import columns from "./tableConfig";
+import CopyPage from "./copyPage";
+
+interface AjaxProps {
+    pageNum: number;
+    pageSize: number;
+    accountId?: number;
+    pageName?: string;
+    pageType?: string;
+    pageStatus?: string;
+    ownerUid?: number;
+}
+/**
+ * 原生推广页
+ * @returns 
+ */
+const WechatCanvasPage: React.FC = () => {
+
+    /********************************/
+    const { getAllUserAccount } = useModel('useLaunchAdq.useAdAuthorize')
+    const [queryParamsNew, setQueryParamsNew] = useState<AjaxProps>({ pageNum: 1, pageSize: 20 })
+    const [visible, setVisible] = useState<boolean>(false)
+    const [pageData, setPageData] = useState<any>()
+
+    const getAdqLandingPageList = useAjax((params) => getAdqLandingPageListApi(params))
+    const getWXDownPageAuthInfoList = useAjax((params) => getWXDownPageAuthInfoListApi(params))
+    /********************************/
+
+    useEffect(() => {
+        // 获取账户列表
+        getAllUserAccount.run().then(res => {
+            setQueryParamsNew({ ...queryParamsNew, accountId: res?.data?.[0]?.accountId })
+        })
+    }, [])
+
+    // 落地页列表
+    useEffect(() => {
+        if (queryParamsNew?.accountId) {
+            getAdqLandingPageList.run(queryParamsNew)
+        }
+    }, [queryParamsNew])
+
+    // 授权落地页
+    useEffect(() => {
+        if (queryParamsNew?.accountId) {
+            getWXDownPageAuthInfoList.run(queryParamsNew.accountId)
+        }
+    }, [queryParamsNew.accountId])
+
+    // 批量复制
+    const handleCopy = (data: any) => {
+        setVisible(true)
+        setPageData(data)
+    }
+
+    return <Card
+        className="cardResetCss"
+        title={<div className="flexStart" style={{ gap: 8 }}>
+            <Select
+                placeholder="请先选择媒体账户"
+                maxTagCount={1}
+                style={{ width: 250 }}
+                autoClearSearchValue={false}
+                filterOption={(input: any, option: any) => {
+                    let newInput: string[] = input ? input?.split(/[,,\n\s]+/ig).filter((item: any) => item) : []
+                    return newInput?.some(val => option!.children?.toString().toLowerCase()?.includes(val))
+                }}
+                value={queryParamsNew.accountId}
+                onChange={(e) => {
+                    setQueryParamsNew({ ...queryParamsNew, accountId: e, ownerUid: undefined })
+                }}
+                showSearch
+            >
+                {getAllUserAccount?.data?.data?.map((item: any) => <Select.Option value={item.accountId} key={item.id}>{item.remark ? item.accountId + '_' + item.remark : item.accountId}</Select.Option>)}
+            </Select>
+            {queryParamsNew?.accountId && <>
+                <Select
+                    placeholder='选择原生页授权方信息'
+                    style={{ width: 210 }}
+                    showSearch
+                    filterOption={(input: any, option: any) =>
+                        (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
+                    }
+                    allowClear
+                    onChange={(value) => {
+                        setQueryParamsNew({ ...queryParamsNew, pageNum: 1, ownerUid: value })
+                    }}
+                    loading={getWXDownPageAuthInfoList.loading}
+                    value={queryParamsNew?.ownerUid}
+                    dropdownMatchSelectWidth={false}
+                >
+                    {getWXDownPageAuthInfoList.data?.map((item: { accountId: number; accountName: string }) => {
+                        return <Select.Option value={item.accountId} key={item.accountId}>{item.accountName}({item.accountId})</Select.Option>
+                    })}
+                </Select>
+                <Input value={queryParamsNew?.pageName} style={{ width: 180 }} allowClear placeholder='请输入落地页名称' onChange={(e) => setQueryParamsNew({ ...queryParamsNew, pageNum: 1, pageName: e.target.value })} />
+            </>}
+
+            <Button type="primary" icon={<SearchOutlined />} loading={getAdqLandingPageList.loading} onClick={() => getAdqLandingPageList.refresh()}>刷新</Button>
+        </div>}
+    >
+        <Table
+            columns={columns(handleCopy)}
+            dataSource={getAdqLandingPageList.data?.records}
+            size="small"
+            loading={getAdqLandingPageList?.loading || getAllUserAccount.loading}
+            scroll={{ y: 600 }}
+            bordered
+            rowKey={'pageId'}
+            pagination={{
+                total: getAdqLandingPageList.data?.total,
+                defaultPageSize: 20,
+                current: getAdqLandingPageList.data?.current,
+                pageSize: getAdqLandingPageList.data?.size
+            }}
+            onChange={(pagination) => {
+                const { current, pageSize } = pagination
+                setQueryParamsNew({ ...queryParamsNew, pageNum: current as number, pageSize: pageSize as number || 20 })
+            }}
+        />
+
+        {visible && <CopyPage
+            accountId={queryParamsNew.accountId as number}
+            pageData={pageData}
+            visible={visible}
+            onClose={() => {
+                setVisible(false)
+                setPageData(undefined)
+            }}
+            onChange={() => {
+                setVisible(false)
+                setPageData(undefined)
+                getAdqLandingPageList.refresh()
+            }}
+        />}
+    </Card>
+}
+
+export default WechatCanvasPage

+ 53 - 0
src/pages/launchSystemV3/tencenTasset/wechatCanvasPage/tableConfig.tsx

@@ -0,0 +1,53 @@
+import { PageStatusEnum } from "@/services/launchAdq/enum"
+import { Badge, TableProps } from "antd"
+import React from "react"
+
+let columns = (handleCopy: (data: any) => void): TableProps<any>['columns'] => {
+
+    return [
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            align: 'center',
+            width: 80,
+            render(_, record) {
+                let { canvasType } = record
+                if (canvasType === 'COMMON_PAGE') {
+                    return '--'
+                }
+                return <a style={{ fontSize: 12 }} onClick={() => handleCopy(record)}>批量复制</a>
+            }
+        },
+        {
+            title: '落地页ID',
+            dataIndex: 'pageId',
+            key: 'pageId',
+            align: 'center',
+            width: 100,
+            render(value) {
+                return <span style={{ fontSize: 12 }}>{value}</span>
+            }
+        },
+        {
+            title: '落地页名称',
+            dataIndex: 'pageName',
+            key: 'pageName',
+            ellipsis: true,
+            render(value) {
+                return <span style={{ fontSize: 12 }}>{value}</span>
+            }
+        },
+        {
+            title: '落地页状态',
+            dataIndex: 'pageStatus',
+            key: 'pageStatus',
+            align: 'center',
+            width: 90,
+            render: (a: string | number) => {
+                return <Badge status={a === 'NORMAL' ? "success" : a === 'DELETED' ? "error" : 'processing'} text={<span style={{ fontSize: 12 }}>{PageStatusEnum[a as keyof typeof PageStatusEnum]}</span>} />
+            }
+        }
+    ]
+}
+export default columns

+ 2 - 2
src/pages/launchSystemV3/tencentAdPutIn/create/Ad/adgroupsAdSetting.tsx

@@ -28,7 +28,7 @@ const AdgroupsAdSetting: React.FC<{ value?: any }> = ({ value }) => {
     useEffect(() => {
         if (!(value && Object.keys(value).length)) {
             form.setFieldsValue({
-                adgroupName: marketingGoalList.find(item => item.value === marketingGoal)?.label + '_' + MARKETING_TARGET_TYPE_ENUM[marketingTargetType] + '_' + localStorage.getItem('userId')// + '_' + moment().format('MM_DD_HH:mm:ss')
+                adgroupName: marketingGoalList.find(item => item.value === marketingGoal)?.label + '_' + MARKETING_TARGET_TYPE_ENUM[marketingTargetType as keyof typeof MARKETING_TARGET_TYPE_ENUM] + '_' + localStorage.getItem('userId')// + '_' + moment().format('MM_DD_HH:mm:ss')
             })
         }
     }, [marketingGoal, marketingTargetType])
@@ -92,7 +92,7 @@ const AdgroupsAdSetting: React.FC<{ value?: any }> = ({ value }) => {
             </Card>
         </Form.Item>
         <Form.Item label={<strong>广告状态</strong>} name="configuredStatus" rules={[{ required: true, message: '请选择广告状态' }]}>
-            <New1Radio data={Object.keys(AD_STATUS_ENUM).map(key => ({ label: AD_STATUS_ENUM[key], value: key }))} />
+            <New1Radio data={Object.keys(AD_STATUS_ENUM).map(key => ({ label: AD_STATUS_ENUM[key as keyof typeof AD_STATUS_ENUM], value: key }))} />
         </Form.Item>
         <Form.Item
             label={<strong>广告名称</strong>}

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

@@ -5,7 +5,7 @@ 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 { chunkArray, chunkArray1, getVideoImgUrl } from "@/utils/utils"
+import { chunkArray1, getVideoImgUrl } from "@/utils/utils"
 import VideoFrameSelect from "@/pages/launchSystemV3/components/VideoFrameSelect"
 import New1Radio from "@/pages/launchSystemV3/components/New1Radio"
 
@@ -285,7 +285,6 @@ const AddMaterial: React.FC<Props> = ({ creativeTemplateId, materialData, delive
                         data={[{ label: '全账号复用', value: 0 }, { label: '平均分配到广告', value: 1 }, { label: '顺序分配到广告', value: 2 }]}
                         onChange={(e) => {
                             if (e === 2 && dynamicGroup?.length > adLength) {
-                                console.log('11111111-->', dynamicGroup?.length, adLength)
                                 form.setFieldsValue({ dynamicGroup: dynamicGroup.slice(0, adLength) })
                             }
                         }}

+ 24 - 0
src/services/adqV3/global.ts

@@ -365,4 +365,28 @@ export async function syncAdInfoApi(data: { accountAdgroupMaps: string[], adgrou
         method: 'POST',
         data
     })
+}
+
+/**
+ * 获取图片信息
+ * @param data 
+ * @returns 
+ */
+export async function getImagesInfoApi(data: { adAccountId: number, imageIds: number[], pageNum: number, pageSize: number }) {
+    return request(api + `/adq/v3/marketingAssets/getImages`, {
+        method: 'POST',
+        data
+    })
+}
+
+/**
+ * 获取视频信息
+ * @param data 
+ * @returns 
+ */
+export async function getVideosInfoApi(data: { adAccountId: number, videoIds: number[] }) {
+    return request(api + `/adq/v3/marketingAssets/getVideos`, {
+        method: 'POST',
+        data
+    })
 }

+ 12 - 0
src/services/adqV3/index.ts

@@ -194,4 +194,16 @@ export async function delV3StrategyApi(strategyId: number) {
     return request(api + `/adq/v3/strategy/delete/${strategyId}`, {
         method: 'POST'
     })
+}
+
+/**
+ * 批量复制落地页
+ * @param data 
+ * @returns 
+ */
+export async function batchCreateDownPageApi(data: { accountId: number, pageName: string, templateId: number, downPageMaterialDTOS: { imageId?: number[], materialType: number, videoId: number }[] }) {
+    return request(api + `/adq/v3/marketingAssets/batchCreateDownPage`, {
+        method: 'POST',
+        data
+    })
 }