Prechádzať zdrojové kódy

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

wjx 1 rok pred
rodič
commit
6dbb20ddfe
38 zmenil súbory, kde vykonal 5376 pridanie a 498 odobranie
  1. 12 0
      config/routerConfig.ts
  2. 17 2
      src/models/useAdMonitor/useMonitor.ts
  3. 1 1
      src/pages/adMonitor/adMonitorList/components/Details.tsx
  4. 1 1
      src/pages/adMonitor/adMonitorList/components/box.tsx
  5. 1 1
      src/pages/adMonitor/adMonitorList/config1.ts
  6. 0 474
      src/pages/adMonitor/adMonitorList/monitor.tsx
  7. 1 1
      src/pages/adMonitor/adMonitorList/monitor1.tsx
  8. 3 3
      src/pages/adMonitor/adMonitorList/tableMonitorConfig1.tsx
  9. 226 0
      src/pages/launchSystemNew/adMonitorListV3/Details.tsx
  10. 548 0
      src/pages/launchSystemNew/adMonitorListV3/FilterQuery.tsx
  11. 219 0
      src/pages/launchSystemNew/adMonitorListV3/TabAd.tsx
  12. 127 0
      src/pages/launchSystemNew/adMonitorListV3/adExpandedRowRender.tsx
  13. 403 0
      src/pages/launchSystemNew/adMonitorListV3/adPlanList.tsx
  14. 173 0
      src/pages/launchSystemNew/adMonitorListV3/config.ts
  15. 36 0
      src/pages/launchSystemNew/adMonitorListV3/index.tsx
  16. 469 0
      src/pages/launchSystemNew/adMonitorListV3/monitor.tsx
  17. 1035 0
      src/pages/launchSystemNew/adMonitorListV3/tableMonitorConfig.tsx
  18. 768 0
      src/pages/launchSystemNew/adMonitorListV3/tablePlanListConfig.tsx
  19. 1 1
      src/pages/launchSystemNew/adq/ad/FilterQuery.tsx
  20. 4 4
      src/pages/launchSystemNew/adq/ad/adPlanList.tsx
  21. 5 5
      src/pages/launchSystemNew/adq/ad/index.tsx
  22. 3 3
      src/pages/launchSystemNew/adq/ad/tablePlanListConfig.tsx
  23. 1 1
      src/pages/launchSystemNew/adq/config.ts
  24. 292 0
      src/pages/launchSystemNew/adqv3/ad/index.tsx
  25. 35 0
      src/pages/launchSystemNew/adqv3/ad/switchStatus.tsx
  26. 243 0
      src/pages/launchSystemNew/adqv3/ad/tableConfig.tsx
  27. 59 0
      src/pages/launchSystemNew/adqv3/ad/updateAd.tsx
  28. 52 0
      src/pages/launchSystemNew/adqv3/config.ts
  29. 47 0
      src/pages/launchSystemNew/adqv3/const.tsx
  30. 61 0
      src/pages/launchSystemNew/adqv3/creative/box.tsx
  31. 100 0
      src/pages/launchSystemNew/adqv3/creative/index.tsx
  32. 108 0
      src/pages/launchSystemNew/adqv3/creative/tableConfig.tsx
  33. 0 0
      src/pages/launchSystemNew/adqv3/index.less
  34. 117 0
      src/pages/launchSystemNew/adqv3/index.tsx
  35. 42 0
      src/pages/launchSystemNew/adqv3/typings.d.ts
  36. 1 1
      src/pages/launchSystemNew/components/TableData/index.tsx
  37. 103 0
      src/services/adMonitor/adMonitor.ts
  38. 62 0
      src/services/launchAdq/adqv3.ts

+ 12 - 0
config/routerConfig.ts

@@ -94,6 +94,12 @@ const launchSystem = {
             component: './adMonitor/adMonitorList',
             access: 'adMonitorList',
         },
+        {
+            path: '/launchSystemNew/adMonitorListV3',
+            name: '广告监控3.0',
+            component: './launchSystemNew/adMonitorListV3',
+            access: 'adMonitorListV3',
+        },
         {
             path: '/launchSystemNew/account',
             name: '广告账户管理',
@@ -119,6 +125,12 @@ const launchSystem = {
             component: './launchSystemNew/adq',
             access: 'adq',
         },
+        {
+            path: '/launchSystemNew/adqv3',
+            name: '腾讯广告3.0',
+            component: './launchSystemNew/adqv3',
+            access: 'adqv3',
+        },
         {
             path: '/launchSystemNew/sysWarningRule',
             name: '监控告警',

+ 17 - 2
src/models/useAdMonitor/useMonitor.ts

@@ -3,7 +3,7 @@ import {
     ListType, getPlanListApi, getTotalCostApi, getPlanCostApi, getCostSpeedApi, getUserGroupApi, getAllPlanListApi,
     allPlanProps, getDetailListApi, getMinuteListApi, downLoadUpAdApi, downLoadDetailApi,
     downLoadDetailMinuteApi, downLoadSpeedApi, downLoadAllAdListApi, addEditGroupApi, getAdGroupListApi, deleteAdGroupApi, 
-    getAccountListApi, AccountListProps, addDelAccountApi, getBookListAllApi, getCostTopListApi, getCostTrendListApi, getListForHourApi, getColumnTrendApi, getAdTotalDataApi, getListForAdApi
+    getAccountListApi, AccountListProps, addDelAccountApi, getBookListAllApi, getCostTopListApi, getCostTrendListApi, getListForHourApi, getColumnTrendApi, getAdTotalDataApi, getListForAdApi, getCostTrendV3ListApi, getCostTopV3ListApi, getListForHourV3Api, getColumnTrendV3Api, getAdTotalDataV3Api, getListForAdV3Api
 } from '@/services/adMonitor/adMonitor'
 import { getAdAccountApi } from '@/services/launchAdq/adAuthorize'
 
@@ -50,6 +50,15 @@ export default function useMonitor() {
     const getAdTotalData = useAjax((params) => getAdTotalDataApi(params), { formatResult: true })
     const getListForAd = useAjax((params) => getListForAdApi(params), { formatResult: true })
 
+    // 3.0
+    const getListForHourV3 = useAjax((params) => getListForHourV3Api(params), { formatResult: true, debounceInterval: 100 })
+    const getCostTrendV3List = useAjax((params: ListType) => getCostTrendV3ListApi(params), { formatResult: true })
+    const getCostTopV3List = useAjax((params: ListType) => getCostTopV3ListApi(params), { formatResult: true })
+    const getColumnTrendV3 = useAjax((params) => getColumnTrendV3Api(params), { formatResult: true })
+    const getAdTotalDataV3 = useAjax((params) => getAdTotalDataV3Api(params), { formatResult: true })
+    const getListForAdV3 = useAjax((params) => getListForAdV3Api(params), { formatResult: true })
+
+
     return {
         getPlanList,
         getTotalCost,
@@ -76,6 +85,12 @@ export default function useMonitor() {
         getAdqAccountList,
         getColumnTrend,
         getAdTotalData,
-        getListForAd
+        getListForAd,
+        getListForHourV3,
+        getCostTrendV3List,
+        getCostTopV3List,
+        getColumnTrendV3,
+        getAdTotalDataV3,
+        getListForAdV3
     }
 }

+ 1 - 1
src/pages/adMonitor/adMonitorList/components/Details.tsx

@@ -58,7 +58,7 @@ const Details: React.FC<Props> = ({ data, onClose, visible }) => {
 
     const getListList = () => {
         let columns: string[] = []
-        let message = localStorage.getItem(`myAdMonitorConfig1.0.0_${configName}`)
+        let message = localStorage.getItem(`myAdMonitorConfig1.0.1_${configName}`)
         if (message) {
             message = JSON.parse(message)
         }

+ 1 - 1
src/pages/adMonitor/adMonitorList/components/box.tsx

@@ -4,7 +4,7 @@ import React, { useMemo } from 'react'
 
 function Box(props: { b: any }) {
     const { b } = props
-    const creativePreview = b?.creativePreview || {}
+    const creativePreview = b?.[0]?.creativePreview
     
     let el = useMemo(() => {
         let image_list = creativePreview?.image_list || []

+ 1 - 1
src/pages/adMonitor/adMonitorList/config1.ts

@@ -9,7 +9,7 @@ const qiliangpaihanghour = [
             { title: '计划ID', dataIndex: 'campaign_id', label: '设置信息' },
             { title: '广告账户', dataIndex: 'account_id', label: '设置信息', default: 10, width: 70 },
             { title: '投手', dataIndex: 'put_user_name', serverIndex: 'sys_user.put_user_name', label: '设置信息', default: 11, width: 65 },
-            { title: '创意预览', dataIndex: 'creative_preivew', serverIndex: 'adgroup_data.creative_id', label: '设置信息', width: 80 },
+            { title: '创意预览', dataIndex: 'creative_preview', serverIndex: 'adgroup_data.creative_ids', label: '设置信息', width: 80 },
             { title: '投放时间', dataIndex: 'begin_date', serverIndex: 'adgroups.begin_date, adgroups.end_date', label: '设置信息', default: 12, width: 135 },
             { title: '广告状态', dataIndex: 'status', serverIndex: 'adgroups.status', label: '设置信息', width: 85 },
             { title: '推广目标', dataIndex: 'promoted_object_type', serverIndex: 'adgroups.promoted_object_type', label: '设置信息' },

+ 0 - 474
src/pages/adMonitor/adMonitorList/monitor.tsx

@@ -1,474 +0,0 @@
-import { Button, Card, Input, Radio, RadioChangeEvent, Select, Space, Spin, Tag, TimePicker, Tooltip } from "antd";
-import React, { useCallback, useEffect, useState } from "react";
-import { CloudDownloadOutlined, ColumnHeightOutlined, ColumnWidthOutlined, EyeInvisibleOutlined, EyeOutlined, RedoOutlined } from "@ant-design/icons";
-import useEcharts from '@/Hook/useEcharts'
-import columnsMonitor from './tableMonitorConfig'
-import { useModel } from 'umi'
-import { ListType, downLoadUpAdApi, downLoadDetailApi, downLoadDetailMinuteApi } from '@/services/adMonitor/adMonitor'
-import { qiliangpaihang, qiliangpaihanghour, qiliangpaihangminute } from './config'
-import { compare } from '@/utils/utils'
-import PlanDetail from './components/planDetail'
-import { formatDate, downloadFile1 } from '@/utils/downloadFile'
-import './table.less'
-import moment from "moment";
-import TableData from "@/pages/launchSystemNew/components/TableData";
-interface newListType extends ListType {
-    totalTimeUnit: 'minute' | 'hour' | 'day',
-    planTimeUnit: 'minute' | 'hour' | 'day'
-}
-
-/**
- * 今日起量监控
- * @param props 
- * @returns 
- */
-function Monitor(props: { onChange: () => void }) {
-    const { onChange } = props
-    const { getPlanList, getTotalCost, getPlanCost, getPlanDetailList, getMinuteList, getAdqAccountList } = useModel('useAdMonitor.useMonitor')
-    const { getPicherList } = useModel('useOperating.useWxGroupList')
-    // 变量开始
-    const [queryForm, setQueryForm] = useState<newListType>({ totalTimeUnit: 'day', planTimeUnit: 'hour', sysUserId: JSON.parse(sessionStorage.getItem('SYSUSERID') || '[]'), pageNum: 1, pageSize: 20 }) // 搜索变量//startTime: moment().format('YYYY-MM-DD'), endTime: moment().format('YYYY-MM-DD'),
-    const { BarMonitor, LineMonitor } = useEcharts()
-    const [barDis, setBarDis] = useState<any[]>([])
-    const [lineDis, setLineDis] = useState<any[]>([])
-    const [lineTitle, setLineTitle] = useState<string>('广告总消耗趋势')
-    const [mode, setMode] = useState<string>('total') // 总/明细
-    const [px, setPx] = useState<boolean>(false)//设置顶部图形的排列方式
-    const [planDetailList, setPlanDetailList] = useState<any[]>([])
-    const [minuteList, setMinuteList] = useState<any[]>([])
-    const [visible, setVisible] = useState<boolean>(false) // 详情弹窗控制
-    const [aId, setAId] = useState<any>()
-    const [downLoadLoading, setDownLoadLoading] = useState<boolean>(false)
-    const [hour, setHour] = useState<any>()
-    const [clickAccountId, setClickAccountId] = useState<number[]>([])
-    const [showEacharts, setShowEacharts] = useState<boolean>(true)
-    const { totalTimeUnit, planTimeUnit, adgroup, accountId, sysUserId, pageNum, pageSize, campaign, sortField, sort } = queryForm
-    // 变量结束
-
-    // 获取投手
-    useEffect(() => {
-        !getPicherList.data && getPicherList.run()
-    }, [])
-    // 获取广告账号
-    useEffect(() => {
-        !getAdqAccountList.data && getAdqAccountList.run()
-    }, [])
-    // // 获取排行数据,柱图
-    useEffect(() => {
-        getPlanCostList()
-    }, [totalTimeUnit, accountId, sysUserId])
-    // 获取今日计划总消耗图谱,折线
-    useEffect(() => {
-        getTootalCostList()
-    }, [planTimeUnit, adgroup, accountId, sysUserId])
-    // 获取起量计划列表 底部table
-    useEffect(() => {
-        if (mode === 'total') {
-            getList()
-        }
-    }, [accountId, sysUserId, pageNum, pageSize, campaign, adgroup, sortField, sort, mode, hour])
-
-    // 获取起量明细表 点击
-    useEffect(() => {
-        if (!adgroup) {
-            setMode('total')//切到总表
-        }
-    }, [adgroup])
-
-    /** 获取折线图 */
-    const getTootalCostList = useCallback(async () => {
-        let { totalTimeUnit, planTimeUnit, pageNum, pageSize, adgroup, sysUserId, accountId, ...newQueryForm } = queryForm
-        let params = adgroup ? { ...newQueryForm, adgroupId: adgroup } : newQueryForm
-        let newPitcherIds = sysUserId?.join()
-        let res = await getTotalCost.run({ ...params, timeUnit: planTimeUnit, sysUserId: newPitcherIds, accountId: accountId?.join() })
-        let data: any[] = [{ legendName: '消耗' }]
-        res?.data?.totalCostDtoList?.forEach((item: any) => {
-            data[0][item.currTime] = item.totalCost
-        })
-        setLineDis(() => data)
-        setLineTitle(() => res?.data?.adName ? res?.data?.adName + '_消耗趋势' : '广告总消耗趋势')
-    }, [queryForm, lineDis])
-
-    /** 获取柱状图 */
-    const getPlanCostList = useCallback(async () => {
-        let { totalTimeUnit, planTimeUnit, pageNum, pageSize, sysUserId, accountId, ...newQueryForm } = queryForm
-        let { adgroup, ...planQueryFrom } = newQueryForm
-        let newPitcherIds = sysUserId?.join()
-        let res = await getPlanCost.run({ ...planQueryFrom, timeUnit: totalTimeUnit, sysUserId: newPitcherIds, accountId: accountId?.join() })
-        let data = res?.data?.planCostDtoList?.map((item: { adId: number, cost: number, adName: string, accountId: number }) => {
-            return { name: item.adId.toString(), value: item.cost, adName: item.adName, accountId: item.accountId }
-        })
-        data = data?.sort((a: any, b: any) => {
-            var value1 = a['value'];
-            var value2 = b['value'];
-            return value2 - value1;
-        })
-        setBarDis(() => data)
-    }, [queryForm, barDis])
-
-    // 气量Table总表
-    const getList = useCallback(() => {
-        let { totalTimeUnit, planTimeUnit, timeUnit, sysUserId, accountId, ...newQueryForm } = queryForm
-        getPlanList.run({ ...newQueryForm, sysUserId, accountId, hour })
-    }, [queryForm, hour])
-
-
-    // 起量明细表 点击
-    const getDetailList = useCallback((adgroup: any, clickAccountId: any[]) => {
-        setClickAccountId(clickAccountId)
-        let { totalTimeUnit, planTimeUnit, timeUnit, pageNum, pageSize, adgroup: aa, sysUserId, accountId, ...newQueryForm } = queryForm
-        if (adgroup) {
-            setMode('detail')//切到明细
-            getPlanDetailList.run({ ...newQueryForm, adgroupId: adgroup, sysUserId, accountId: accountId ? accountId : clickAccountId }).then((res: any) => {
-                setPlanDetailList(res?.data ? [...res?.data] : [])
-                if (adgroup != aa) {
-                    setQueryForm({ ...queryForm, adgroup })
-                }
-            })
-        }
-    }, [queryForm, getPlanList, planDetailList])
-
-    // 详情
-    const details = (data: any) => {
-        setAId(data)
-        setVisible(true)
-    }
-
-    // 起量5min表
-    const getMinuList = useCallback((adgroup: any, clickAccountId: any[]) => {
-        setClickAccountId(clickAccountId)
-        let { totalTimeUnit, planTimeUnit, timeUnit, pageNum, pageSize, adgroup: aa, sysUserId, accountId, ...newQueryForm } = queryForm
-        if (adgroup) {
-            setMode('minute')
-            getMinuteList.run({ ...newQueryForm, adgroupId: adgroup, sysUserId, accountId: accountId ? accountId : clickAccountId }).then((res: any) => {
-                setMinuteList(res?.data ? [...res?.data] : [])
-                if (adgroup != aa) {
-                    setQueryForm({ ...queryForm, adgroup })
-                }
-            })
-        }
-    }, [queryForm, getPlanList, minuteList, adgroup])
-
-    // 计划详情
-    const planDetail = (data: any) => {
-        sessionStorage.setItem('ADIDORNAME', JSON.stringify(data))
-        onChange && onChange()
-    }
-
-    //全部接口刷新
-    const refresh = () => {
-        getPlanList.refresh()
-        getTotalCost.refresh()
-        getPlanCost.refresh()
-        getPlanDetailList.refresh()
-        getMinuteList.refresh()
-    }
-
-    // 接口自动刷新10分钟一次
-    useEffect(() => {
-        let time = setInterval(() => {
-            refresh()
-        }, 1000 * 60 * 10)
-        return () => {
-            clearInterval(time)
-        }
-    }, [])
-
-    //图形排列样式改变重新获取数据刷新图形
-    const set = useCallback((b) => {
-        setPx(b)
-        getTotalCost.refresh()
-        getPlanCost.refresh()
-    }, [getPlanCost, getTotalCost])
-
-    // 下载
-    const downLoadExcel = useCallback(() => {
-        let ajax: any = null
-        let params: any = {}
-        let { totalTimeUnit, planTimeUnit, timeUnit, sysUserId, accountId, adgroup, pageNum, pageSize, ...newQueryForm } = queryForm
-        switch (mode) {
-            case 'total':
-                params = { ...newQueryForm, sysUserId, accountId, adgroupId: adgroup }
-                ajax = downLoadUpAdApi
-                break;
-            case 'detail':
-                params = { ...newQueryForm, adgroupId: adgroup, accountId }
-                ajax = downLoadDetailApi
-                break;
-            case 'minute':
-                params = { ...newQueryForm, adgroupId: adgroup, accountId }
-                ajax = downLoadDetailMinuteApi
-                break;
-        }
-        if (ajax) {
-            setDownLoadLoading(true)
-            ajax(params).then((res: any) => {
-                setDownLoadLoading(false)
-                downloadFile1(res, 'octet-stream', formatDate(new Date()) + ".xlsx")
-            }).catch(() => setDownLoadLoading(false))
-        }
-    }, [queryForm, mode, downLoadLoading])
-
-    // 处理折线图数据
-    const timePickerHandle = async (values: any, formatString: [string, string]) => {
-        let res = await getTotalCost.data
-        let data: any[] = [{ legendName: '消耗' }]
-        res?.data?.totalCostDtoList?.forEach((item: any) => {
-            data[0][item.currTime] = item.totalCost
-        })
-        if (values) {
-            let date = moment().format('YYYY-MM-DD')
-            let strTime = formatString[0]
-            let endTime = formatString[1]
-            let dateTimeStr = `${date} ${strTime}:00`
-            let dateTimeEnd = `${date} ${endTime}:00`
-            let { legendName, ...otherData } = data[0]
-            let newData: any = { legendName }
-            for (const key in otherData) {
-                if (Object.prototype.hasOwnProperty.call(otherData, key)) {
-                    const value = otherData[key];
-                    if (moment(dateTimeStr) <= moment(key) && moment(key) <= moment(dateTimeEnd)) {
-                        newData[key] = value
-                    }
-                }
-            }
-            setLineDis(() => [newData])
-        } else {
-            setLineDis(() => data)
-        }
-    }
-
-    const disabledTime = () => {
-        let h = moment().format('H')
-        let H: number[] = []
-        for (let index = Number(h) + 1; index < 23; index++) {
-            H.push(index)
-        }
-        return {
-            disabledHours: () => H
-        }
-    }
-
-    return <Space direction='vertical' style={{ width: '100%' }} className="monitor">
-        <Card hoverable bodyStyle={{ padding: '12px 16px' }}>
-            <div style={{ display: 'flex', justifyContent: 'space-between' }}>
-                <Space>
-                    <Select
-                        showSearch
-                        value={queryForm.sysUserId}
-                        style={{ minWidth: 180, maxWidth: 250 }}
-                        mode='multiple'
-                        maxTagCount={1}
-                        allowClear 
-                        placeholder="请选择投手"
-                        disabled={queryForm?.adgroup || queryForm?.accountId?.length > 0}
-                        onChange={(value: number[]) => {
-                            setQueryForm({ ...queryForm, sysUserId: value, pageNum: 1 })
-                            sessionStorage.setItem('SYSUSERID', value ? JSON.stringify(value) : '')
-                            if (mode === 'detail') {
-                                getDetailList(queryForm.adgroup, queryForm?.accountId)
-                            } else if (mode === 'minute') {
-                                getMinuList(queryForm.adgroup, queryForm?.accountId)
-                            }
-                        }}
-                        filterOption={(input, option) =>
-                            (option?.children as any).toLowerCase().indexOf(input.toLowerCase()) >= 0
-                        }
-                    >
-                        {getPicherList?.data?.map((item: { nickname: string, userId: number }, index: number) =>
-                            <Select.Option
-                                value={item.userId}
-                                key={item.userId + '' + index}
-                            >
-                                {item.nickname}
-                            </Select.Option>
-                        )}
-                    </Select>
-                    <Select
-                        showSearch
-                        mode='multiple'
-                        maxTagCount={1}
-                        value={queryForm.accountId}
-                        style={{ minWidth: 220, maxWidth: 250 }}
-                        allowClear
-                        placeholder="请选择广告账号"
-                        onChange={(value: number[]) => {
-                            setQueryForm({ ...queryForm, accountId: value, pageNum: 1 })
-                        }}
-                    >
-                        {getAdqAccountList?.data?.data?.map((item: { id: number, accountId: number }) => <Select.Option
-                            value={item.accountId}
-                            key={item.id}
-                        >
-                            {item.accountId}
-                        </Select.Option>)}
-                    </Select>
-                    <Input
-                        value={queryForm.campaign}
-                        placeholder="计划ID"
-                        onChange={(e) => {
-                            let value = e.target.value
-                            if (!isNaN(Number(value))) {
-                                setQueryForm({ ...queryForm, campaign: value })
-                            }
-                            if (mode === 'detail') {
-                                getDetailList(queryForm?.adgroup, queryForm?.accountId)
-                            } else if (mode === 'minute') {
-                                getMinuList(queryForm?.adgroup, queryForm?.accountId)
-                            }
-                        }}
-                        allowClear
-                    />
-                    <Input
-                        value={queryForm.adgroup}
-                        placeholder="广告ID"
-                        onChange={(e) => {
-                            let value = e.target.value
-                            if (!isNaN(Number(value))) {
-                                setQueryForm({ ...queryForm, adgroup: e.target.value })
-                            }
-                            if (!value) {
-                                setClickAccountId([])
-                            }
-                            if (mode === 'detail') {
-                                getDetailList(e.target.value, queryForm?.accountId)
-                            } else if (mode === 'minute') {
-                                getMinuList(e.target.value, queryForm?.accountId)
-                            }
-                        }}
-                        allowClear
-                    />
-                </Space>
-                <Space>
-                    <Tag onClick={() => setShowEacharts(!showEacharts)}>{showEacharts ? <><EyeInvisibleOutlined /> 隐藏</> : <><EyeOutlined /> 显示</>}</Tag>
-                    <Tag color="#2db7f5" onClick={refresh}><RedoOutlined />   刷新</Tag>
-                </Space>
-            </div>
-        </Card>
-        {showEacharts && <Card hoverable bodyStyle={{ padding: '12px 16px' }}>
-            <span style={{ position: 'absolute', top: 10, zIndex: 10 }}>
-                {px ?
-                    <Tooltip title='左右排列'><Tag color='#f50' onClick={() => { set(false) }}><ColumnWidthOutlined /><span style={{ fontSize: 10 }}>左右排列</span></Tag></Tooltip>
-                    :
-                    <Tooltip title='上下排列'><Tag color='#f50' onClick={() => { set(true) }} ><ColumnHeightOutlined /><span style={{ fontSize: 10 }}>上下排列</span></Tag></Tooltip>
-                }
-            </span>
-            <div className={!px ? 'charts' : 'charts charts100'}>
-                <div>
-                    <div className="selectTime">
-                        <Space>
-                            <span style={{ fontSize: 10, color: '#999' }}>刷新时间:{getPlanCost?.data?.reqTime}</span>
-                            <Radio.Group value={queryForm.totalTimeUnit} buttonStyle="solid" size='small' onChange={(e) => { setQueryForm({ ...queryForm, totalTimeUnit: e.target.value }) }}>
-                                <Radio.Button value="day">天</Radio.Button>
-                                <Radio.Button value="hour">小时</Radio.Button>
-                                <Radio.Button value="minute">5min</Radio.Button>
-                            </Radio.Group>
-                        </Space>
-                    </div>
-                    {getPlanCost?.loading ? <Spin /> : <BarMonitor style={{ width: '100%', height: '100%' }} data={barDis} xName="今日消耗" yName="广告名称" onChange={(value: string, accountId) => { getDetailList(value, accountId) }} planID={queryForm?.adgroup} />}
-                </div>
-                <div>
-                    <div className="selectTime">
-                        <Space>
-                            <span style={{ fontSize: 10, color: '#999' }}>刷新时间:{getTotalCost?.data?.reqTime}</span>
-                            <Radio.Group value={queryForm.planTimeUnit} buttonStyle="solid" size='small' onChange={(e) => { setQueryForm({ ...queryForm, planTimeUnit: e.target.value }) }}>
-                                <Radio.Button value="hour">小时</Radio.Button>
-                                <Radio.Button value="minute">5min</Radio.Button>
-                            </Radio.Group>
-                            {queryForm.planTimeUnit === 'minute' && <TimePicker.RangePicker
-                                size="small"
-                                format='HH:mm'
-                                minuteStep={5}
-                                onChange={timePickerHandle}
-                            />}
-                        </Space>
-                    </div>
-                    {getTotalCost?.loading ? <Spin /> : <LineMonitor style={{ width: '100%', height: '100%' }} series smooth data={lineDis} title={lineTitle} />}
-                </div>
-            </div>
-        </Card>}
-        <div className={'MYtable'}>
-            <TableData
-                bodyStyle={{ padding: '12px 16px' }}
-                gutter={[0, 12]}
-                columns={columnsMonitor(planDetail, getDetailList, details, getMinuList, mode)}
-                dataSource={mode === 'total' ? getPlanList?.data?.data?.records?.map((item: any, index: number) => ({ ...item, id: item.id + '_' + index })) : mode === 'detail' ? getPlanDetailList?.data?.data : getMinuteList?.data?.data}
-                loading={mode === 'total' ? getPlanList?.loading : mode === 'detail' ? getPlanDetailList?.loading : getMinuteList?.loading}
-                ajax={mode === 'total' ? getPlanList : mode === 'detail' ? getPlanDetailList : getMinuteList}
-                leftChild={
-                    <Space>
-                        <Radio.Group onChange={(e: RadioChangeEvent) => {
-                            let value = e.target.value
-                            switch (value) {
-                                case 'total':
-                                    setMode(value)
-                                    break;
-                                case 'detail':
-                                    getDetailList(queryForm.adgroup, clickAccountId)
-                                    break
-                                case 'minute':
-                                    getMinuList(queryForm.adgroup, clickAccountId)
-                                    break
-                            }
-                        }} value={mode} size='small'>
-                            <Radio.Button value="total">总</Radio.Button>
-                            <Radio.Button value="detail" disabled={!adgroup}>小时</Radio.Button>
-                            <Radio.Button value="minute" disabled={!adgroup}>5min</Radio.Button>
-                        </Radio.Group>
-                        {mode === 'total' && <TimePicker disabledTime={disabledTime} size='small' style={{ width: 110 }} onChange={(e) => { setHour(e ? moment(e).format('H') : null) }} format="HH" />}
-                        <Button size="small" icon={<CloudDownloadOutlined />} loading={downLoadLoading} onClick={downLoadExcel}>{mode === 'total' ? '总表' : mode === 'detail' ? '小时表' : '5min表'}下载</Button>
-                    </Space>
-                }
-                fixed={{ left: 0, right: 2 }}
-                total={mode === 'total' ? getPlanList?.data?.data?.total : mode === 'detail' ? getPlanDetailList?.data?.data?.length : getMinuteList?.data?.data?.length}
-                onChange={mode === 'total' ? (props: any) => {
-                    let { sortData, pagination } = props
-                    let { current, pageSize } = pagination
-                    let newQueryForm = JSON.parse(JSON.stringify(queryForm))
-                    newQueryForm.pageNum = current
-                    newQueryForm.pageSize = pageSize
-                    if (sortData && JSON.stringify('sortData') !== '{}') {
-                        let { field, order } = sortData   // descend 降序 大到小  ascend 升序 小到大
-                        if (order) {
-                            newQueryForm.sortField = field
-                            newQueryForm.sort = order === 'ascend' ? 'ASC' : 'DESC'
-                        } else {
-                            Object.keys(newQueryForm).forEach(key => {
-                                if (key === 'sortField' || key === 'sort') {
-                                    delete newQueryForm[key]
-                                }
-                            })
-                        }
-                    } else {
-                        Object.keys(newQueryForm).forEach(key => {
-                            if (key === 'sortField' || key === 'sort') {
-                                delete newQueryForm[key]
-                            }
-                        })
-                    }
-                    setQueryForm({ ...newQueryForm })
-                } : (props: any) => {
-                    let { sortData } = props
-                    if (sortData && JSON.stringify(sortData) !== '{}') {
-                        let { field, order } = sortData   // descend 降序 大到小  ascend 升序 小到大 planDetailList
-                        if (mode === 'detail') {
-                            getPlanDetailList?.mutate({ data: order ? getPlanDetailList?.data?.data?.sort(compare(field, order)) : [...planDetailList] })
-                        } else if (mode === 'minute') {
-                            getMinuteList?.mutate({ data: order ? getMinuteList?.data?.data?.sort(compare(field, order)) : [...minuteList] })
-                        }
-                    }
-                }}
-                page={mode === 'total' ? queryForm.pageNum : undefined}
-                pageSize={mode === 'total' ? queryForm.pageSize : undefined}
-                scroll={{ y: 750 }}
-                config={mode === 'minute' ? qiliangpaihangminute : mode === 'detail' ? qiliangpaihanghour : qiliangpaihang}
-                configName={mode === 'total' ? '起量广告排行' : mode === 'detail' ? '起量广告排行明细' : '起量广告5min'}
-            />
-        </div>
-
-        {visible && <PlanDetail visible={visible} onClose={() => { setVisible(false) }} data={aId} />}
-    </Space>
-}
-
-
-export default React.memo(Monitor)

+ 1 - 1
src/pages/adMonitor/adMonitorList/monitor1.tsx

@@ -59,7 +59,7 @@ function Monitor(props: { onChange: () => void }) {
     }, [queryForHour, filterQuery, queryForm?.sysUserId, queryForm?.accountId, queryForm?.adgroup, queryForm?.groupAccountIds])
 
     const getList = () => {
-        let message = localStorage.getItem(`myAdMonitorConfig1.0.0_${configName}`)
+        let message = localStorage.getItem(`myAdMonitorConfig1.0.1_${configName}`)
         if (message) {
             message = JSON.parse(message)
         }

+ 3 - 3
src/pages/adMonitor/adMonitorList/tableMonitorConfig1.tsx

@@ -72,11 +72,11 @@ function columnsMonitor(planDetail: (id: number) => void, details: (id: number)
             },
             {
                 title: '创意预览',
-                dataIndex: 'creative_preivew',
-                key: 'creative_preivew',
+                dataIndex: 'creative_preview',
+                key: 'creative_preview',
                 width: 110,
                 align: 'center',
-                render: (a: any, b: any) => {
+                render: (a: any) => {
                     return <Box b={a} />
                 }
             },

+ 226 - 0
src/pages/launchSystemNew/adMonitorListV3/Details.tsx

@@ -0,0 +1,226 @@
+import useEcharts from "@/Hook/useEcharts"
+import { GetColumnTrendProps, GetListForAdProps } from "@/services/adMonitor/adMonitor"
+import { Card, DatePicker, Drawer, Select, Space, Spin, Tabs } from "antd"
+import React, { useEffect, useState } from "react"
+import { useModel } from "umi"
+import '@/pages/adMonitor/adMonitorList/components/index.less'
+import TableData from "@/pages/launchSystemNew/components/TableData"
+import { detailsConfig } from "@/pages/adMonitor/adMonitorList/config1"
+import moment from "moment"
+import { columnsList } from "@/pages/adMonitor/adMonitorList/tableMonitorConfig1"
+import { LineField } from "@/pages/adMonitor/adMonitorList/config"
+import TabAd from "./TabAd"
+
+interface Props {
+    data: any,
+    onClose?: () => void,
+    visible?: boolean
+}
+const Details: React.FC<Props> = ({ data, onClose, visible }) => {
+
+    /****************************************/
+    const { adgroup_id, account_id } = data
+    const [activeKey, setActiveKey] = useState<string>('1')
+    const [queryColumnTrend, setQueryColumnTrend] = useState<GetColumnTrendProps>({ accountId: account_id, adgroupId: adgroup_id, trendColumns: ['cost'], timeUnit: 'hour', dataTimeMin: moment().format('YYYY-MM-DD'), dataTimeMax: moment().format('YYYY-MM-DD') })
+    const [queryList, setQueryList] = useState<GetListForAdProps>({ accountId: account_id, adgroupId: adgroup_id, columns: [], pageNum: 1, pageSize: 20, timeUnit: 'hour', dataTimeMin: moment().format('YYYY-MM-DD'), dataTimeMax: moment().format('YYYY-MM-DD') })
+    const [lineDis, setLineDis] = useState<any[]>([])
+    const { LineMonitor } = useEcharts()
+    const configName = '数据报表3.0'
+    const [totalData, setTotalData] = useState<any[]>([])
+
+    const { getColumnTrendV3, getAdTotalDataV3, getListForAdV3 } = useModel('useAdMonitor.useMonitor')
+    /****************************************/
+
+    useEffect(() => {
+        getCTList()
+    }, [queryColumnTrend])
+
+    const getCTList = () => {
+        getColumnTrendV3.run(queryColumnTrend).then(res => {
+            if (res?.data) {
+                let trendColumns = queryColumnTrend.trendColumns
+                let data = trendColumns.map((field) => {
+                    let value: any = {}
+                    res?.data?.forEach((item: any, index: number) => {
+                        if (index === 0) value.legendName = LineField[field];
+                        value[item?.trend_unit] = item?.[field]
+                    });
+                    return value
+                })
+                setLineDis(() => data)
+            }
+        })
+    }
+
+    useEffect(() => {
+        getListList()
+    }, [queryList])
+
+    const getListList = () => {
+        let columns: string[] = []
+        let message = localStorage.getItem(`myAdMonitorConfig1.0.1_${configName}`)
+        if (message) {
+            message = JSON.parse(message)
+        }
+        if (message && Array.isArray(message)) {
+            message.forEach((item: { serverIndex: any; dataIndex: string; }) => {
+                columns.push('adgroup_data.' + item.dataIndex)
+            })
+        } else {
+            detailsConfig.forEach((item: any) => {
+                item?.data?.forEach((d: { default: any, serverIndex: string, dataIndex: string }) => {
+                    if (d.default) {
+                        columns.push('adgroup_data.' + d.dataIndex)
+                    }
+                })
+            })
+        }
+        getListForAdV3.run({ ...queryList, columns })
+        let queryTotal = JSON.parse(JSON.stringify(queryList))
+        delete queryTotal.pageNum
+        delete queryTotal.pageSize
+        delete queryTotal.timeUnit
+        getAdTotalDataV3.run({ ...queryTotal, columns }).then(res => {
+            console.log(res)
+            if (res?.data) {
+                let data = res.data
+                data.id = 1
+                data.time = '总计'
+                setTotalData([data])
+            }
+        })
+    }
+
+    return <Drawer
+        title={data?.adgroup_name}
+        placement="right"
+        onClose={onClose}
+        visible={visible}
+        width={'70%'}
+        bodyStyle={{ padding: '0 10px', background: '#efefef' }}
+        className="detail_drawer"
+    >
+        <Tabs activeKey={activeKey} onChange={(e) => setActiveKey(e)} tabBarExtraContent={activeKey === '1' ? <Space>
+            <Select
+                size="small"
+                value={queryColumnTrend.timeUnit}
+                style={{ width: 78 }}
+                onChange={(e) => {
+                    setQueryColumnTrend({ ...queryColumnTrend, timeUnit: e })
+                    setQueryList({ ...queryList, timeUnit: e })
+                }}
+            >
+                <Select.Option value="day">天</Select.Option>
+                <Select.Option value="hour">小时</Select.Option>
+                <Select.Option value="minute">5min</Select.Option>
+            </Select>
+            <DatePicker.RangePicker
+                size="small"
+                placeholder={['数据时间筛选(起始)', '数据时间筛选(结束)']}
+                value={(queryColumnTrend?.dataTimeMin && queryColumnTrend?.dataTimeMax) ? [moment(queryColumnTrend.dataTimeMin), moment(queryColumnTrend.dataTimeMax)] : undefined as any}
+                style={{ width: 320 }}
+                onChange={(_, o) => {
+                    setQueryColumnTrend({ ...queryColumnTrend, dataTimeMin: o[0], dataTimeMax: o[1] })
+                    setQueryList({ ...queryList, dataTimeMin: o[0], dataTimeMax: o[1], pageNum: 1 })
+                }}
+            />
+        </Space> : null}
+        >
+            <Tabs.TabPane tab="效果数据" key="1">
+                <Space direction="vertical" style={{ width: '100%' }}>
+                    <Card
+                        className="detail_card"
+                        hoverable
+                    >
+                        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '12px 16px 0' }}>
+                            <Space>
+                                <strong>趋势</strong>
+                                <Select
+                                    showSearch
+                                    mode='multiple'
+                                    maxTagCount={2}
+                                    value={queryColumnTrend.trendColumns}
+                                    style={{ minWidth: 160 }}
+                                    allowClear
+                                    placeholder="请选择图表字段"
+                                    onChange={(value: string[]) => {
+                                        setQueryColumnTrend({ ...queryColumnTrend, trendColumns: value?.length ? value : ['cost'] })
+                                    }}
+                                    size="small"
+                                >
+                                    {Object.keys(LineField).map((key) => <Select.Option value={key} key={key}>
+                                        {LineField[key]}
+                                    </Select.Option>)}
+                                </Select>
+                            </Space>
+                            <Space align="center">
+                                <span style={{ fontSize: 10, color: '#999' }}>刷新时间:{getColumnTrendV3?.data?.reqTime}</span>
+                                <a onClick={() => getCTList()} style={{ fontSize: 14 }}>刷新</a>
+                            </Space>
+                        </div>
+                        <div style={{ width: '100%', height: 260, textAlign: 'center' }}>
+                            {getColumnTrendV3?.loading ? <Spin /> : <LineMonitor style={{ width: '100%', height: 260 }} series smooth data={lineDis} />}
+                        </div>
+                    </Card>
+                    <div className={'MYtable'}>
+                        <TableData
+                            refreshData={getListList}
+                            isZj
+                            totalData={totalData}
+                            bodyStyle={{ padding: '12px 16px' }}
+                            gutter={[0, 12]}
+                            columns={columnsList()}
+                            dataSource={getListForAdV3?.data?.data?.records?.map((item: any, index: number) => ({ ...item, id: Number(queryList.pageNum.toString() + index.toString()) }))}
+                            loading={getListForAdV3?.loading}
+                            ajax={getListForAdV3}
+                            leftChild={
+                                <Space>
+                                    <strong>数据报表</strong>
+                                </Space>
+                            }
+                            fixed={{ left: 0, right: 0 }}
+                            total={getListForAdV3?.data?.data?.total}
+                            onChange={(props: any) => {
+                                let { sortData, pagination } = props
+                                let { current, pageSize } = pagination
+                                let newQueryForm = JSON.parse(JSON.stringify(queryList))
+                                newQueryForm.pageNum = current
+                                newQueryForm.pageSize = pageSize
+                                if (sortData && JSON.stringify('sortData') !== '{}') {
+                                    let { order } = sortData   // descend 降序 大到小  ascend 升序 小到大
+                                    if (order) {
+                                        // newQueryForm.sortColumn = field
+                                        newQueryForm.sortAsc = order === 'ascend'
+                                    } else {
+                                        Object.keys(newQueryForm).forEach(key => {
+                                            if (key === 'sortColumn' || key === 'sortAsc') {
+                                                delete newQueryForm[key]
+                                            }
+                                        })
+                                    }
+                                } else {
+                                    Object.keys(newQueryForm).forEach(key => {
+                                        if (key === 'sortField' || key === 'sort') {
+                                            delete newQueryForm[key]
+                                        }
+                                    })
+                                }
+                                setQueryList({ ...newQueryForm })
+                            }}
+                            page={queryList.pageNum}
+                            pageSize={queryList.pageSize}
+                            scroll={{ y: 500 }}
+                            config={detailsConfig}
+                            configName={configName}
+                        />
+                    </div>
+                </Space>
+            </Tabs.TabPane>
+            <Tabs.TabPane tab="广告详情" key="2">
+                <TabAd accountId={account_id} adgroupId={adgroup_id} />
+            </Tabs.TabPane>
+        </Tabs>
+    </Drawer>
+}
+
+export default React.memo(Details)

+ 548 - 0
src/pages/launchSystemNew/adMonitorListV3/FilterQuery.tsx

@@ -0,0 +1,548 @@
+import { CloseOutlined, DownOutlined, FilterOutlined, SearchOutlined, UpOutlined } from "@ant-design/icons"
+import { Button, DatePicker, Form, Input, InputNumber, Popover, Select, Space } from "antd"
+import React, { useEffect, useState } from "react"
+import '../adq/ad/index.less'
+import moment from "moment"
+import { useLocalStorageState, useTimeout } from "ahooks"
+import { AdStatusEnum, OptimizationGoalEnum, PromotedObjectType } from "@/services/launchAdq/enum"
+import { AdListProps } from "@/services/adMonitor/adMonitor"
+import { useModel } from "umi"
+import { ADGROUP_STATUS } from "../adqv3/const"
+// import { useModel } from "umi"
+
+type TypeProps = 'DatePicker' | 'Input' | 'InputNumber' | 'Select'
+interface QueryProps {
+    lable: string,
+    name: string,
+    type: TypeProps,
+    value: (data?: { value?: any, onChange?: (value?: any) => void }) => JSX.Element,
+    isValue?: boolean
+}
+
+interface Props {
+    queryForm: AdListProps,
+    setQueryForm: React.Dispatch<React.SetStateAction<AdListProps>>
+    onChange?: (data: any) => void
+    initialValues?: any
+}
+/**
+ * 请求体 AD 合集
+ * @returns 
+ */
+const FilterQuery: React.FC<Props> = ({ onChange, initialValues, queryForm, setQueryForm }) => {
+
+    /********************************/
+    const [form] = Form.useForm();
+    const [visible, setVisible] = useState<boolean>(true)
+    const [filterTrueList, setFilterTrueList] = useState<QueryProps[]>([])
+    const [message, setMessage] = useLocalStorageState('filterQueryContentAdMessage3.0', '');
+    const { getGroupList } = useModel('useLaunchAdq.useAdAuthorize')
+    const { getPicherList } = useModel('useOperating.useWxGroupList')
+    /********************************/
+
+    useTimeout(() => {
+        setVisible(false);
+    }, 1000);
+
+    useEffect(() => {
+        getPicherList.run()
+    }, [])
+
+    const queryList: QueryProps[] = [
+        {
+            lable: '广告创建时间',
+            name: 'adCreateTime',
+            type: 'DatePicker',
+            value: (params) => <DatePicker.RangePicker {...params} />
+        },
+        {
+            lable: '投放时间',
+            name: 'putDate',
+            type: 'DatePicker',
+            value: (params) => <DatePicker.RangePicker {...params} />
+        },
+        // {
+        //     lable: '投手',
+        //     name: 'sysUserIds',
+        //     type: 'Select',
+        //     value: (params) => <Select
+        //         showSearch
+        //         style={{ width: '100%', minWidth: 130 }}
+        //         allowClear
+        //         mode="multiple"
+        //         placeholder="请选择投手"
+        //         filterOption={(input, option) =>
+        //             (option?.children as any).toLowerCase().indexOf(input.toLowerCase()) >= 0
+        //         }
+        //         {...params}
+        //     >
+        //         {getPicherList?.data?.map((item: { nickname: string, userId: number }, index: number) =>
+        //             <Select.Option
+        //                 value={item.userId}
+        //                 key={item.userId + '' + index}
+        //             >
+        //                 {item.nickname}
+        //             </Select.Option>
+        //         )}
+        //     </Select>
+        // },
+        {
+            lable: '广告账号',
+            name: 'accountIdStr',
+            type: 'Input',
+            value: (params) => <Input placeholder="请输入广告账号(多个,隔开)" style={{ width: '100%' }} {...params} />
+        },
+        // {
+        //     lable: '广告名称',
+        //     name: 'adgroupName',
+        //     type: 'Input',
+        //     value: (params) => <Input placeholder="请输入广告名称" style={{ width: '100%' }} {...params} />
+        // },
+        // {
+        //     lable: '广告ID',
+        //     name: 'adgroupIdStr',
+        //     type: 'Input',
+        //     value: (params) => <Input placeholder="请输入广告ID(多个,隔开)" style={{ width: '100%' }} {...params} />
+        // },
+        {
+            lable: '推广目标名称',
+            name: 'promotedObjectName',
+            type: 'Input',
+            value: (params) => <Input placeholder="请输入推广目标名称" style={{ width: '100%' }} {...params} />
+        },
+        {
+            lable: '腾讯账号备注',
+            name: 'accountMemo',
+            type: 'Input',
+            value: (params) => <Input placeholder="请输入腾讯账号备注" style={{ width: '100%' }} {...params} />
+        },
+        {
+            lable: '本地账号备注',
+            name: 'accountRemark',
+            type: 'Input',
+            value: (params) => <Input placeholder="请输入本地账号备注" style={{ width: '100%' }} {...params} />
+        },
+        // {
+        //     lable: '推广目标',
+        //     name: 'promotedObjectType',
+        //     type: 'Select',
+        //     value: (params) => <Select
+        //         placeholder='推广目标'
+        //         style={{ width: '100%', minWidth: 130 }}
+        //         showSearch
+        //         filterOption={(input: any, option: any) =>
+        //             (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
+        //         }
+        //         allowClear
+        //         {...params}
+        //     >
+        //         {Object.keys(PromotedObjectType).map(key => {
+        //             return <Select.Option value={key} key={key}>{PromotedObjectType[key]}</Select.Option>
+        //         })}
+        //     </Select>
+        // },
+        {
+            lable: '优化目标',
+            name: 'optimizationGoal',
+            type: 'Select',
+            value: (params) => <Select
+                placeholder='优化目标'
+                style={{ width: '100%', minWidth: 130 }}
+                showSearch
+                allowClear
+                filterOption={(input: any, option: any) =>
+                    (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
+                }
+                {...params}
+            >
+                {Object.keys(OptimizationGoalEnum).map(key => <Select.Option value={key} key={key}>{OptimizationGoalEnum[key]}</Select.Option>)}
+            </Select>
+        },
+        {
+            lable: '是否深度优化',
+            name: 'isDeepConversionSpec',
+            type: 'Select',
+            value: (params) => <Select
+                placeholder='是否深度优化?'
+                style={{ width: '100%', minWidth: 130 }}
+                showSearch
+                allowClear
+                filterOption={(input: any, option: any) =>
+                    (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
+                }
+                {...params}
+            >
+                <Select.Option value={true}>是</Select.Option>
+                <Select.Option value={false}>否</Select.Option>
+            </Select>
+        },
+        // {
+        //     lable: '已删除?',
+        //     name: 'isDeleted',
+        //     type: 'Select',
+        //     value: (params) => <Select
+        //         placeholder='已删除?'
+        //         style={{ width: '100%', minWidth: 130 }}
+        //         showSearch
+        //         allowClear
+        //         filterOption={(input: any, option: any) =>
+        //             (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
+        //         }
+        //         {...params}
+        //     >
+        //         <Select.Option value={true}>是</Select.Option>
+        //         <Select.Option value={false}>否</Select.Option>
+        //     </Select>
+        // },
+        // {
+        //     lable: '广告状态',
+        //     name: 'status',
+        //     type: 'Select',
+        //     value: (params) => <Select
+        //         placeholder='广告状态'
+        //         mode="multiple"
+        //         style={{ width: '100%', minWidth: 130 }}
+        //         showSearch
+        //         filterOption={(input: any, option: any) =>
+        //             (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
+        //         }
+        //         allowClear
+        //         {...params}
+        //     >
+        //         {Object.keys(AdStatusEnum).map(key => {
+        //             return <Select.Option value={key} key={key}>{AdStatusEnum[key]}</Select.Option>
+        //         })}
+        //     </Select>
+        // },
+        // {
+        //     lable: '最低今日转化数',
+        //     name: 'conversionsCountDayMin',
+        //     type: 'InputNumber',
+        //     value: (params) => <InputNumber placeholder="请输入最低今日转化数" style={{ width: '100%' }} {...params} />
+        // },
+        {
+            lable: '最低总转化数',
+            name: 'conversionsCountTotalMin',
+            type: 'InputNumber',
+            value: (params) => <InputNumber placeholder="请输入最低总转化数" style={{ width: '100%' }} {...params} />
+        },
+        // {
+        //     lable: '最低今日消耗',
+        //     name: 'costDayMin',
+        //     type: 'InputNumber',
+        //     value: (params) => <InputNumber placeholder="请输入最低今日消耗" style={{ width: '100%' }} {...params} />
+        // },
+        {
+            lable: '最小总消耗',
+            name: 'costTotalMin',
+            type: 'InputNumber',
+            value: (params) => <InputNumber placeholder="请输入最小总消耗" style={{ width: '100%' }} {...params} />
+        },
+        // {
+        //     lable: '最低今日千次曝光成本',
+        //     name: 'thousandDisplayPriceDayMin',
+        //     type: 'InputNumber',
+        //     value: (params) => <InputNumber placeholder="请输入最低今日千次曝光成本" style={{ width: '100%' }} {...params} />
+        // },
+        {
+            lable: '最低总千次曝光成本',
+            name: 'thousandDisplayPriceTotalMin',
+            type: 'InputNumber',
+            value: (params) => <InputNumber placeholder="请输入最低总千次曝光成本" style={{ width: '100%' }} {...params} />
+        },
+    ]
+
+    useEffect(() => {
+        setFiled(message)
+    }, [message])
+
+    const setFiled = (message?: string) => {
+        if (message) {
+            let newFiter: any = []
+            message.split(',').forEach(key => {
+                let data = queryList.find(item => item.name === key)
+                if (data) {
+                    newFiter.push(data)
+                }
+            })
+            console.log('==========>', newFiter)
+            setFilterTrueList(newFiter)
+        } else {
+            setFilterTrueList([])
+        }
+    }
+
+    const onFinish = async () => {
+        let newAllValue = await form.validateFields()
+        if (newAllValue?.adCreateTime && newAllValue?.adCreateTime?.length > 0) {
+            newAllValue.adCreateTimeMin = moment(newAllValue?.adCreateTime[0]).format('YYYY-MM-DD')
+            newAllValue.adCreateTimeMax = moment(newAllValue?.adCreateTime[1]).format('YYYY-MM-DD')
+            delete newAllValue?.adCreateTime
+        }
+        if (newAllValue?.putDate && newAllValue?.putDate?.length > 0) {
+            newAllValue.putDateBegin = moment(newAllValue?.putDate[0]).format('YYYY-MM-DD')
+            newAllValue.putDateEnd = moment(newAllValue?.putDate[1]).format('YYYY-MM-DD')
+            delete newAllValue?.putDate
+        }
+        setQueryForm({ ...queryForm, pageNum: 1 })
+        onChange?.(newAllValue)
+    }
+
+    return <div id="filterQueryContentAd">
+        <div style={{ width: '100%', display: 'flex', gap: 8, flexWrap: 'wrap' }}>
+            <Select
+                showSearch
+                maxTagCount={1}
+                style={{ minWidth: 100 }}
+                allowClear
+                mode="multiple"
+                placeholder="请选择投手"
+                filterOption={(input, option) =>
+                    (option?.children as any).toLowerCase().indexOf(input.toLowerCase()) >= 0
+                }
+                value={queryForm.sysUserIds}
+                onChange={(value) => setQueryForm({ ...queryForm, sysUserIds: value, pageNum: 1 })}
+            >
+                {getPicherList?.data?.map((item: { nickname: string, userId: number }, index: number) =>
+                    <Select.Option
+                        value={item.userId}
+                        key={item.userId + '' + index}
+                    >
+                        {item.nickname}
+                    </Select.Option>
+                )}
+            </Select>
+            <Input
+                placeholder="请输入广告名称"
+                allowClear
+                style={{ width: 140 }}
+                value={queryForm?.adgroupName}
+                onChange={(e) => setQueryForm({ ...queryForm, adgroupName: e.target.value })}
+            />
+            <Select
+                placeholder='广告状态'
+                mode="multiple"
+                maxTagCount={1}
+                style={{ minWidth: 130 }}
+                showSearch
+                filterOption={(input: any, option: any) =>
+                    (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
+                }
+                allowClear
+                value={queryForm.status}
+                onChange={(e) => {
+                    setQueryForm({ ...queryForm, status: e, pageNum: 1 })
+                }}
+            >
+                {Object.keys(ADGROUP_STATUS).map(key => {
+                    return <Select.Option value={key} key={key}>{ADGROUP_STATUS[key]}</Select.Option>
+                })}
+            </Select>
+            <Select
+                showSearch
+                mode='multiple'
+                maxTagCount={1}
+                value={queryForm.groupAccountIds}
+                style={{ minWidth: 130 }}
+                allowClear
+                placeholder="请选择账号分组"
+                onChange={(value: number[]) => {
+                    setQueryForm({ ...queryForm, groupAccountIds: value, pageNum: 1 })
+                }}
+            >
+                {getGroupList?.data?.map((item: { groupId: number, groupName: number }) => <Select.Option
+                    value={item.groupId}
+                    key={item.groupId}
+                >
+                    {item.groupName}
+                </Select.Option>)}
+            </Select>
+            <Input placeholder="请输入广告ID(多个,隔开)" style={{ width: 140 }} value={queryForm.adgroupIdStr} onChange={(e) => setQueryForm({ ...queryForm, adgroupIdStr: e.target.value, pageNum: 1 })} />
+            <Select
+                placeholder='已删除?'
+                style={{ width: 90 }}
+                showSearch
+                allowClear
+                filterOption={(input: any, option: any) =>
+                    (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
+                }
+                value={queryForm.isDeleted}
+                onChange={(e) => {
+                    setQueryForm({ ...queryForm, isDeleted: e, pageNum: 1 })
+                }}
+            >
+                <Select.Option value={true}>是</Select.Option>
+                <Select.Option value={false}>否</Select.Option>
+            </Select>
+            <Select
+                placeholder='推广目标'
+                style={{ minWidth: 130 }}
+                showSearch
+                filterOption={(input: any, option: any) =>
+                    (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
+                }
+                allowClear
+                value={queryForm.promotedObjectType}
+                onChange={(e) => {
+                    setQueryForm({ ...queryForm, promotedObjectType: e, pageNum: 1 })
+                }}
+            >
+                {Object.keys(PromotedObjectType).map(key => {
+                    return <Select.Option value={key} key={key}>{PromotedObjectType[key]}</Select.Option>
+                })}
+            </Select>
+
+            <DatePicker.RangePicker
+                placeholder={['数据时间筛选(起始)', '数据时间筛选(结束)']}
+                value={(queryForm?.dataTimeMin && queryForm?.dataTimeMax) ? [moment(queryForm.dataTimeMin), moment(queryForm.dataTimeMax)] : undefined as any}
+                style={{ width: 320 }}
+                onChange={(_, o) => {
+                    setQueryForm({ ...queryForm, dataTimeMin: o[0], dataTimeMax: o[1], pageNum: 1 })
+                }}
+            />
+
+            <Popover
+                title={<Space direction="vertical" size={0}>
+                    <h3 style={{ fontWeight: 'bold', marginBottom: 0 }}>你可能找这类广告</h3>
+                </Space>}
+                visible={visible}
+                trigger={'click'}
+                placement="bottomLeft"
+                onVisibleChange={(e) => setVisible(e)}
+                getPopupContainer={() => document.getElementById('filterQueryContentAd') as any}
+                content={<div style={{ width: 500, height: 400, overflowY: 'auto', padding: '10px 16px' }}>
+                    <Form
+                        name="FilterQueryAd3.0"
+                        form={form}
+                        colon={false}
+                        layout="vertical"
+                        initialValues={initialValues}
+                        onValuesChange={(changedValues) => {
+                            console.log(changedValues)
+                            let messageArr = message ? message?.split(',') : []
+                            Object.keys(changedValues).forEach(key => {
+                                if (!(messageArr?.includes(key))) {
+                                    messageArr.push(key)
+                                    setMessage(messageArr.join(','))
+                                    // setFiled(messageArr.join(','))
+                                }
+                            })
+                        }}
+                        onFinish={onFinish}
+                    >
+                        {queryList.map(item => <Form.Item label={<strong>{item.lable}</strong>} name={item.name} key={item.name}>
+                            {item.value()}
+                        </Form.Item>)}
+                    </Form>
+                </div>}
+            >
+                <Button className="bt"><FilterOutlined />筛选 {visible ? <UpOutlined /> : <DownOutlined />}</Button>
+            </Popover>
+            <Button type="primary" onClick={onFinish} icon={<SearchOutlined />}>搜索</Button>
+            <Button onClick={() => {
+                form.resetFields();
+                setQueryForm({ pageNum: 1, pageSize: 20, columns: [], isDeleted: false })
+                onFinish()
+            }}>重置</Button>
+        </div>
+        <div className="selectedFilterAd">
+            {filterTrueList.map(item => {
+                let value = form.getFieldValue([item.name])
+                // if (item.type === 'DatePicker' && value && value?.length > 0) {
+                //     console.log(value, Array.isArray(value))
+                //     let start = new Date()
+                //     // let end = moment(value(1)).format('YYYY-MM-DD')
+                //     // value = start + '-' + end
+                // }
+                switch (item.name) {
+                    case 'status':
+                        value = AdStatusEnum[value]
+                        break
+                    case 'isDeleted':
+                        value = typeof value === 'boolean' ? value ? '是' : '否' : '请选择'
+                        break
+                    case 'isDeepConversionSpec':
+                        value = typeof value === 'boolean' ? value ? '是' : '否' : '请选择'
+                        break
+                    case 'optimizationGoal':
+                        value = OptimizationGoalEnum[value] || '请选择'
+                        break
+                    case 'promotedObjectType':
+                        value = PromotedObjectType[value] || '请选择'
+                        break
+                    case 'adCreateTime': case 'putDate':
+                        value = value ? '已经选择时间' : '请选择时间'
+                        break
+                    default:
+                        value = value || '请输入'
+                }
+                return <Popover
+                    placement="bottom"
+                    trigger={'click'}
+                    destroyTooltipOnHide={true}
+                    content={<PopoverSetValue
+                        type={item.type}
+                        dataEntry={item.value}
+                        value={form.getFieldValue([item.name])}
+                        onChange={(value) => {
+                            form.setFieldsValue({ [item.name]: value })
+                            onFinish()
+                        }}
+                    />}
+                    title={item.lable}
+                    key={item.name}
+                >
+                    <div>
+                        <div className="text">
+                            {item.lable}:{value || '请选择'}
+                        </div>
+                        <DownOutlined style={{ fontSize: 10, marginRight: 6 }} />
+                        <div className="close" onClick={() => {
+                            form.setFieldsValue({ [item.name]: undefined })
+                            let messageArr = message?.split(',') || []
+                            messageArr = messageArr.filter(key => key !== item.name)
+                            let messageStr = messageArr.length > 0 ? messageArr.join(',') : undefined
+                            setMessage(messageStr)
+                            // setFiled(messageStr)
+                        }}><CloseOutlined /></div>
+                    </div>
+                </Popover>
+            })}
+        </div>
+    </div>
+}
+
+export default React.memo(FilterQuery)
+
+interface PopoverSetValueProps {
+    type: TypeProps,
+    dataEntry: (data?: { value?: any, onChange?: (value?: any) => void }) => JSX.Element,
+    value?: any
+    onChange?: (value?: any) => void
+}
+const PopoverSetValue: React.FC<PopoverSetValueProps> = ({ type, dataEntry, value, onChange }) => {
+
+    /*************************/
+    const [data, setData] = useState<any>()
+    /*************************/
+
+    useEffect(() => {
+        setData(value)
+    }, [value])
+
+    return <Space>
+        {dataEntry({
+            value: data,
+            onChange(value) {
+                if (['Input'].includes(type)) {
+                    setData(value.target.value)
+                } else {
+                    setData(value)
+                }
+            },
+        })}
+        <Button type="primary" onClick={() => {
+            onChange?.(data)
+        }}>确定</Button>
+    </Space>
+}

+ 219 - 0
src/pages/launchSystemNew/adMonitorListV3/TabAd.tsx

@@ -0,0 +1,219 @@
+import { useAjax } from "@/Hook/useAjax"
+import TimeSeriesLook from "@/pages/launchSystemNew/adq/ad/timeSeriesLook"
+import { addAdToRuleBlackListApi, delAdToRuleBlackListApi, delAdWarningRuleApi, getAdgroupDetailsApi } from "@/services/adMonitor/adMonitor"
+import { AdStatusEnum, BidModeEnum, BidStrategyEnum, OptimizationGoalEnum, PromotedObjectType } from "@/services/launchAdq/enum"
+import { Card, Descriptions, Empty, Popover, Space, Spin, Typography, message } from "antd"
+import React, { useEffect, useMemo, useState } from "react"
+import TableData from "@/pages/launchSystemNew/components/TableData"
+import SetEarlyWarnings from "@/components/EarlyWarning/setEarlyWarnings"
+import RuleLog from "@/components/EarlyWarning/ruleLog"
+import tableConfigEw from "@/pages/adMonitor/adMonitorList/components/tableConfigEw"
+import tableConfig from "../adqv3/creative/tableConfig"
+
+export enum EWTypeEnum {
+    DEFAULT = '默认告警规则',
+    ACCOUNT = '广告账号告警规则',
+    ADGROUP = '广告告警规则',
+    ADGROUP_BLACK_LIST = '告警黑名单'
+}
+
+interface Props {
+    accountId: any
+    adgroupId: any
+}
+const TabAd: React.FC<Props> = ({ accountId, adgroupId }) => {
+
+    /*******************************/
+    const [logVisible, setLogVisible] = useState<boolean>(false)
+    const [ruleId, setrRleId] = useState<number>(0)
+    const [ruleName, setrRuleName] = useState<string>('')
+
+    const getAdgroupDetails = useAjax((params) => getAdgroupDetailsApi(params), { formatResult: true })
+    const delAdWarningRule = useAjax((params) => delAdWarningRuleApi(params), { formatResult: true })
+    const addAdToRuleBlackList = useAjax((params) => addAdToRuleBlackListApi(params), { formatResult: true })
+    const delAdToRuleBlackList = useAjax((params) => delAdToRuleBlackListApi(params), { formatResult: true })
+    /*******************************/
+
+    useEffect(() => {
+        getAdgroupDetails.run({ adgroupId, accountId })
+    }, [adgroupId, accountId])
+
+    const addBlack = (value: any) => {
+        console.log(value)
+        addAdToRuleBlackList.run({ adgroupId, accountId, ruleId: value.id }).then(res => {
+            if (res?.data) {
+                message.success('添加成功')
+                getAdgroupDetails.refresh()
+            }
+        })
+    }
+
+    const remove = (value: any) => {
+        delAdWarningRule.run({ adgroupId, accountId, ruleId: value.id }).then(res => {
+            if (res?.data) {
+                message.success('移除成功')
+                getAdgroupDetails.refresh()
+            }
+        })
+    }
+
+    const removeBlack = (value: any) => {
+        delAdToRuleBlackList.run({ adgroupId, accountId, ruleId: value.id }).then(res => {
+            if (res?.data) {
+                message.success('移出成功')
+                getAdgroupDetails.refresh()
+            }
+        })
+    }
+
+    const log = (value: any) => {
+        setrRleId(value.id)
+        setrRuleName(value.ruleName)
+        setLogVisible(true)
+    }
+
+    const AdContent = useMemo(() => {
+        if (getAdgroupDetails?.data?.data) {
+            const { adgroupName, bidAmount, bidMode, optimizationGoal, adgroupId, rejectMessageList, status, smartBidType, beginDate,
+                endDate, targetingTranslation, timeSeries, firstDayBeginTime, dailyBudget, bidStrategy, promotedObjectType, accountId,
+                creativeName
+            } = getAdgroupDetails?.data?.data
+            return <Spin spinning={getAdgroupDetails.loading}>
+                <Descriptions column={2} size="small" colon={false}>
+                    <Descriptions.Item labelStyle={{ width: 100, flex: '0 0 auto' }} label="广告名称">
+                        <div style={{ width: '90%' }}>
+                            <Typography.Text ellipsis={{ tooltip: true }}>{adgroupName}</Typography.Text>
+                        </div>
+                    </Descriptions.Item>
+                    <Descriptions.Item labelStyle={{ width: 100 }} label="出价">{`${BidModeEnum[bidMode]} ${bidAmount}元/${bidMode === 'BID_MODE_CPM' ? '千次曝光' : bidMode === 'BID_MODE_CPC' ? '点击' : OptimizationGoalEnum[optimizationGoal]}`}</Descriptions.Item>
+                    <Descriptions.Item labelStyle={{ width: 100 }} label="广告ID">{adgroupId}</Descriptions.Item>
+                    <Descriptions.Item labelStyle={{ width: 100 }} label="状态">
+                        {rejectMessageList?.filter((str: any) => str)?.length > 0 ? <Popover
+                            style={{ width: 500 }}
+                            overlayStyle={{ width: 500, fontSize: 12 }}
+                            placement="left"
+                            content={rejectMessageList?.map((str: string, eq: number) => {
+                                return str ? <><strong style={{ fontSize: 13 }}>{eq + 1}:</strong>{str}<br /></> : ""
+                            })}>
+                            {AdStatusEnum[status]}
+                        </Popover> :
+                            AdStatusEnum[status]}
+                    </Descriptions.Item>
+                    <Descriptions.Item label="广告账号" labelStyle={{ width: 100 }}>{accountId}</Descriptions.Item>
+                    <Descriptions.Item label="出价类型" labelStyle={{ width: 100 }}>{smartBidType === 'SMART_BID_TYPE_CUSTOM' ? '手动出价' : '自动出价'}</Descriptions.Item>
+                    <Descriptions.Item label="推广目标类型" labelStyle={{ width: 100 }}>{PromotedObjectType[promotedObjectType]}</Descriptions.Item>
+                    <Descriptions.Item label="投放日期" labelStyle={{ width: 100 }}>{endDate ? beginDate + '~' + endDate : beginDate + '~' + '长期投放'}</Descriptions.Item>
+                    <Descriptions.Item label="定向" labelStyle={{ width: 100 }} span={2}>{targetingTranslation}</Descriptions.Item>
+                    <Descriptions.Item label="投放时间" labelStyle={{ width: 100 }}>{<TimeSeriesLook timeSeries={timeSeries} />}</Descriptions.Item>
+                    <Descriptions.Item label="首日开始时间" labelStyle={{ width: 100 }}>{firstDayBeginTime}</Descriptions.Item>
+                    <Descriptions.Item label="日预算" labelStyle={{ width: 100 }}>{dailyBudget}</Descriptions.Item>
+                    <Descriptions.Item label="创意名称" labelStyle={{ width: 100 }}>{creativeName}</Descriptions.Item>
+                    <Descriptions.Item label="出价策略" labelStyle={{ width: 100 }}>{BidStrategyEnum[bidStrategy]}</Descriptions.Item>
+                </Descriptions>
+            </Spin>
+        }
+        return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
+    }, [getAdgroupDetails?.data?.data, getAdgroupDetails.loading])
+
+    console.log(getAdgroupDetails?.data?.data?.dynamicCreativeList)
+
+    const CreativePreview = useMemo(() => {
+
+        return <Card
+            hoverable
+            style={{ width: '100%' }}
+            bodyStyle={{ padding: 16 }}
+        >
+            <TableData
+                size="small"
+                isCard={false}
+                columns={() => tableConfig()}
+                ajax={getAdgroupDetails}
+                dataSource={getAdgroupDetails?.data?.data?.dynamicCreativeList || []}
+                leftChild={<Space>
+                    <strong>创意预览</strong>
+                </Space>}
+                myKey={'dynamicCreativeId'}
+                loading={getAdgroupDetails?.loading}
+                total={getAdgroupDetails?.data?.data?.dynamicCreativeList?.length || 0}
+                gutter={[0, 10]}
+            />
+        </Card>
+    }, [getAdgroupDetails, getAdgroupDetails?.data?.data, getAdgroupDetails.loading])
+
+    const EWContent = useMemo(() => {
+        if (getAdgroupDetails?.data?.data?.warningRuleMap && Object.keys(getAdgroupDetails?.data?.data?.warningRuleMap).length > 0) {
+            let data = getAdgroupDetails?.data?.data?.warningRuleMap
+            let dataArr = Object.keys(getAdgroupDetails?.data?.data?.warningRuleMap)
+            if (!dataArr.includes('ADGROUP')) {
+                dataArr.push('ADGROUP')
+            }
+            return dataArr.map(key => <Card
+                hoverable
+                style={{ width: '100%' }}
+                key={key}
+                bodyStyle={{ padding: 16 }}
+            >
+                <TableData
+                    size="small"
+                    isCard={false}
+                    columns={() => tableConfigEw(key as any, addBlack, remove, removeBlack, log)}
+                    ajax={getAdgroupDetails}
+                    dataSource={data[key]}
+                    leftChild={<Space>
+                        <strong>{EWTypeEnum[key]}</strong>
+                        {key === 'ADGROUP' && <SetEarlyWarnings accountId={accountId} adgroupId={adgroupId} onChange={() => getAdgroupDetails.refresh()} />}
+                    </Space>}
+                    loading={getAdgroupDetails?.loading}
+                    total={getAdgroupDetails?.data?.data?.length}
+                    gutter={[0, 10]}
+                />
+            </Card>)
+        }
+        return <Card
+            hoverable
+            style={{ width: '100%' }}
+            bodyStyle={{ padding: 16 }}
+        >
+            <TableData
+                size="small"
+                isCard={false}
+                columns={() => tableConfigEw('ADGROUP', addBlack, remove, removeBlack, log)}
+                ajax={getAdgroupDetails}
+                dataSource={[]}
+                leftChild={<Space>
+                    <strong>{EWTypeEnum['ADGROUP']}</strong>
+                    <SetEarlyWarnings accountId={accountId} adgroupId={adgroupId} onChange={() => getAdgroupDetails.refresh()} />
+                </Space>}
+                loading={getAdgroupDetails?.loading}
+                total={getAdgroupDetails?.data?.data?.length}
+                gutter={[0, 10]}
+            />
+        </Card>
+    }, [getAdgroupDetails?.data?.data, getAdgroupDetails.loading])
+
+    return <Space style={{ width: '100%' }} direction="vertical">
+        <Card
+            hoverable
+            className="detail_card"
+            style={{ width: '100%' }}
+        >
+            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '12px 16px 0' }}>
+                <strong>广告</strong>
+                <Space align="center">
+                    <span style={{ fontSize: 10, color: '#999' }}>刷新时间:{getAdgroupDetails?.data?.reqTime}</span>
+                    <a onClick={() => getAdgroupDetails.refresh()} style={{ fontSize: 14 }}>刷新</a>
+                </Space>
+            </div>
+            <div style={{ padding: 16 }}>
+                {AdContent}
+            </div>
+        </Card>
+        {CreativePreview}
+        {EWContent}
+
+        {logVisible && <RuleLog ruleName={ruleName} ruleId={ruleId} visible={logVisible} onClose={() => setLogVisible(false)} />}
+    </Space>
+}
+
+export default React.memo(TabAd)

+ 127 - 0
src/pages/launchSystemNew/adMonitorListV3/adExpandedRowRender.tsx

@@ -0,0 +1,127 @@
+import useEcharts from "@/Hook/useEcharts"
+import { GetColumnTrendProps, getAdgroupDetailsApi } from "@/services/adMonitor/adMonitor"
+import { Card, Descriptions, Empty, Popover, Select, Space, Spin, Typography } from "antd"
+import React, { useEffect, useMemo, useState } from "react"
+import { useModel } from "umi"
+import '../adq/ad/index.less'
+import { useAjax } from "@/Hook/useAjax"
+import { AdStatusEnum, BidModeEnum, BidStrategyEnum, OptimizationGoalEnum } from "@/services/launchAdq/enum"
+import TimeSeriesLook from "../adq/ad/timeSeriesLook"
+
+
+const AdExpandedRowRender: React.FC<{ data: any, scrollLeft: number, width?: number }> = ({ data, scrollLeft, width = 0 }) => {
+
+    const { getColumnTrendV3 } = useModel('useAdMonitor.useMonitor')
+    const getAdgroupDetails = useAjax((params) => getAdgroupDetailsApi(params), { formatResult: true })
+    const [queryColumnTrend, setQueryColumnTrend] = useState<GetColumnTrendProps>({ accountId: data.account_id, adgroupId: data.adgroup_id, trendColumns: ['view', 'cost'], timeUnit: 'hour' })
+    const { LineMonitor } = useEcharts()
+    const [lineDis, setLineDis] = useState<any[]>([])
+
+    useEffect(() => {
+        getAdgroupDetails.run({ accountId: data.account_id, adgroupId: data.adgroup_id })
+    }, [data])
+
+    useEffect(() => {
+        getCTList()
+    }, [queryColumnTrend])
+
+    const getCTList = () => {
+        getColumnTrendV3.run(queryColumnTrend).then(res => {
+            if (res?.data) {
+                let spendData: any = { legendName: '花费' }
+                let exposureData: any = { legendName: '曝光次数' }
+                res?.data?.forEach((item: { day: string, trend_unit: string, view: string, cost: string }) => {
+                    spendData[item?.trend_unit] = item?.cost
+                    exposureData[item?.trend_unit] = item?.view
+                });
+                setLineDis(() => [spendData, exposureData])
+            }
+        })
+    }
+
+    const AdContent = useMemo(() => {
+        if (getAdgroupDetails?.data?.data) {
+            const { adgroupName, bidAmount, bidMode, optimizationGoal, rejectMessageList, status, targetingTranslation, timeSeries, firstDayBeginTime, dailyBudget, bidStrategy,
+            } = getAdgroupDetails?.data?.data
+            return <Spin spinning={getAdgroupDetails.loading}>
+                <Descriptions column={2} size="small" colon={false}>
+                    <Descriptions.Item labelStyle={{ width: 100, flex: '0 0 auto' }} label="广告名称">
+                        <div style={{ width: (width - 20) / 4 }}>
+                            <Typography.Text ellipsis={{ tooltip: true }}>{adgroupName}</Typography.Text>
+                        </div>
+                    </Descriptions.Item>
+                    <Descriptions.Item labelStyle={{ width: 100 }} label="状态">
+                        {rejectMessageList?.filter((str: any) => str)?.length > 0 ? <Popover
+                            style={{ width: 500 }}
+                            overlayStyle={{ width: 500, fontSize: 12 }}
+                            placement="left"
+                            content={rejectMessageList?.map((str: string, eq: number) => {
+                                return str ? <><strong style={{ fontSize: 13 }}>{eq + 1}:</strong>{str}<br /></> : ""
+                            })}>
+                            {AdStatusEnum[status]}
+                        </Popover> :
+                            AdStatusEnum[status]}
+                    </Descriptions.Item>
+                    <Descriptions.Item labelStyle={{ width: 100 }} label="出价">{`${BidModeEnum[bidMode]} ${bidAmount}元/${bidMode === 'BID_MODE_CPM' ? '千次曝光' : bidMode === 'BID_MODE_CPC' ? '点击' : OptimizationGoalEnum[optimizationGoal]}`}</Descriptions.Item>
+                    <Descriptions.Item label="投放时间" labelStyle={{ width: 100 }}>{<TimeSeriesLook timeSeries={timeSeries} />}</Descriptions.Item>
+                    <Descriptions.Item label="定向" labelStyle={{ width: 100, flex: '0 0 auto' }} span={2}><div style={{ width: '98%' }}>
+                        <Typography.Paragraph ellipsis={{ tooltip: true, rows: 2 }}>{targetingTranslation}</Typography.Paragraph>
+                    </div></Descriptions.Item>
+                    <Descriptions.Item label="首日开始时间" labelStyle={{ width: 100 }}>{firstDayBeginTime}</Descriptions.Item>
+                    <Descriptions.Item label="日预算" labelStyle={{ width: 100 }}>{dailyBudget}</Descriptions.Item>
+                    <Descriptions.Item label="出价策略" labelStyle={{ width: 100 }}>{BidStrategyEnum[bidStrategy]}</Descriptions.Item>
+                </Descriptions>
+            </Spin>
+        }
+        return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
+    }, [getAdgroupDetails?.data?.data, getAdgroupDetails.loading])
+
+    return <div style={{ width: width - 20, left: scrollLeft }} className="expandContent">
+        <Card
+            className="detail_card"
+            hoverable
+            style={{ width: (width - 20) / 2 }}
+        >
+            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '12px 16px 0' }}>
+                <strong>广告</strong>
+                <Space align="center">
+                    <span style={{ fontSize: 10, color: '#999' }}>刷新时间:{getAdgroupDetails?.data?.reqTime}</span>
+                    <a onClick={() => getAdgroupDetails.refresh()} style={{ fontSize: 14 }}>刷新</a>
+                </Space>
+            </div>
+            <div style={{ padding: 16 }}>
+                {AdContent}
+            </div>
+        </Card>
+        <Card
+            className="detail_card"
+            hoverable
+            style={{ width: (width - 20) / 2 }}
+        >
+            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '12px 16px 0' }}>
+                <strong>趋势</strong>
+                <Space align="center">
+                    <Select
+                        size="small"
+                        value={queryColumnTrend.timeUnit}
+                        style={{ width: 78 }}
+                        onChange={(e) => {
+                            setQueryColumnTrend({ ...queryColumnTrend, timeUnit: e })
+                        }}
+                    >
+                        <Select.Option value="day">天</Select.Option>
+                        <Select.Option value="hour">小时</Select.Option>
+                        <Select.Option value="minute">5min</Select.Option>
+                    </Select>
+                    <span style={{ fontSize: 10, color: '#999' }}>刷新时间:{getColumnTrendV3?.data?.reqTime}</span>
+                    <a onClick={() => getCTList()} style={{ fontSize: 14 }}>刷新</a>
+                </Space>
+            </div>
+            <div style={{ width: '100%', height: 260, textAlign: 'center' }}>
+                {getColumnTrendV3?.loading ? <Spin /> : <LineMonitor style={{ width: '100%', height: 260 }} series smooth data={lineDis} />}
+            </div>
+        </Card>
+    </div>
+}
+
+export default React.memo(AdExpandedRowRender)

+ 403 - 0
src/pages/launchSystemNew/adMonitorListV3/adPlanList.tsx

@@ -0,0 +1,403 @@
+
+import { useAjax } from '@/Hook/useAjax'
+import { Col, Row, message, Space, Button, Table, Statistic } from 'antd'
+import React, { useEffect, useCallback, useState, useRef } from 'react'
+import { getPutUserApi, delUserTagApi } from '@/services/launchAdq/adq'
+import { LineChartOutlined, PauseCircleOutlined, PlayCircleOutlined, TransactionOutlined } from '@ant-design/icons'
+import { planAdConfig } from './config'
+import '../adq/ad/index.less'
+import tablePlanConfig from './tablePlanListConfig'
+import { AdListProps, getAdV3ListApi } from '@/services/adMonitor/adMonitor'
+import FilterQuery from './FilterQuery'
+import AdExpandedRowRender from './adExpandedRowRender'
+import { useSize, useUpdateEffect } from 'ahooks'
+import PlanTag from '../adq/ad/planTag'
+import moment from 'moment'
+import ColumnTrend from '@/pages/adMonitor/adMonitorList/columnTrend'
+import TableData from '../components/TableData'
+import RuleAccountLog from '@/components/EarlyWarning/ruleAccountLog'
+import Details from './Details'
+import { modifyStatusBatchApi, syncBatchApi } from '@/services/launchAdq/adqv3'
+import UpdateAd from '../adqv3/ad/updateAd'
+
+const AdPlanList: React.FC<{ userId: string }> = (props) => {
+
+    /***********************/
+    const { userId } = props
+    const [selectedRows, setSelectedRows] = useState<any[]>([])
+    const [detailShow, setDetailShow] = useState<boolean>(false)
+    const [detailData, setDetailData] = useState<any>({})
+    const [update, setUpdate] = useState<{ visible: boolean }>({ visible: false })
+    const [queryForm, setQueryForm] = useState<AdListProps>({ pageNum: 1, pageSize: 20, columns: [], isDeleted: false, dataTimeMin: moment().subtract(7, 'days').format('YYYY-MM-DD'), dataTimeMax: moment().format('YYYY-MM-DD') })
+    const [filterForm, setFilterForm] = useState<AdListProps>()
+    const getAdList = useAjax((params) => getAdV3ListApi(params), { formatResult: true })
+    const getPutUser = useAjax((params) => getPutUserApi(params))
+    const delUserTag = useAjax((params) => delUserTagApi(params))
+
+    const [logVisible, setLogVisible] = useState<boolean>(false)
+    const [adgroupId, setAdgroupId] = useState<string>('')
+    const [adgroupName, setAdgroupName] = useState<string>('')
+    const [accountIdRule, setAccountIdRule] = useState<string>('')
+
+    const [tagVisible, setTagVisible] = useState<boolean>(false)
+    const [tagData, setTagData] = useState<any>({})
+
+    const configName = '广告列表3.0'
+    const ref = useRef(null)
+    const size = useSize(ref)
+    const [scrollLeft, setScrollLeft] = useState<number>(0)
+    const [totalData, setTotalData] = useState<any>({})
+    const [tableField, setTableField] = useState<{ title: string, dataIndex: string }[]>([])
+
+    const [trendVisible, setTrendVisible] = useState<boolean>(false)
+    const [trendData, setTrendData] = useState<any>({})
+    const syncBatch = useAjax((params) => syncBatchApi(params))
+    const modifyStatusBatch = useAjax((params) => modifyStatusBatchApi(params))
+    /************************/
+
+    useEffect(() => {
+        getList()
+    }, [filterForm, queryForm])
+
+    const getList = () => {
+        let message = localStorage.getItem(`myAdMonitorConfig1.0.1_${configName}`)
+        if (message) {
+            message = JSON.parse(message)
+        }
+        let columns: string[] = []
+        if (message && Array.isArray(message)) {
+            message.forEach((item: { serverIndex: any; dataIndex: string; }) => {
+                if (!['cz', 'cost_speed'].includes(item.dataIndex)) {
+                    columns.push(item?.serverIndex || 'adgroup_data.' + item.dataIndex)
+                }
+            })
+        } else {
+            planAdConfig.forEach((item: any) => {
+                item?.data?.forEach((d: { default: any, serverIndex: string, dataIndex: string }) => {
+                    if (d.default && !['cz', 'cost_speed'].includes(d.dataIndex)) {
+                        columns.push(d?.serverIndex || 'adgroup_data.' + d.dataIndex)
+                    }
+                })
+            })
+        }
+        getAdList.run({ ...queryForm, ...filterForm, columns })
+    }
+
+    useUpdateEffect(() => {
+        let data: any = {}
+        if (getAdList.data?.data?.sumRecord) {
+            data = getAdList.data?.data?.sumRecord
+        }
+        setTotalData(data)
+    }, [getAdList.data])
+
+    useEffect(() => {
+        let localData = localStorage.getItem('myAdMonitorConfig1.0.1_广告列表New')
+        let data: any[] = []
+        if (localData) {
+            data = JSON.parse(localData)
+        } else {
+            let newSelectData: any[] = [];
+            (planAdConfig as any).forEach((item: { data: { default: any }[] }) => {
+                item?.data?.forEach((d: { default: any }) => {
+                    if (d.default) {
+                        newSelectData[d.default - 1] = d
+                    }
+                })
+            })
+            data = newSelectData
+        }
+        data.unshift({ title: '选择框', dataIndex: 'xzk' })
+        data.unshift({ title: '总计', dataIndex: 'zj' })
+        setTableField(data)
+    }, [localStorage.getItem('myAdMonitorConfig1.0.1_广告列表New')])
+
+    useEffect(() => {
+        getPutUser.run({ userId })
+    }, [userId])
+
+    // 同步 
+    const sync = useCallback(() => {
+        if (selectedRows?.length > 0) {
+            let accountAdgroupMaps = [...new Set(selectedRows?.map(item => item.account_id + ',' + item.adgroup_id))]
+            syncBatch.run({ accountAdgroupMaps }).then(res => {
+                res && getAdList.refresh()
+                res ? message.success('同步成功!') : message.error('同步失败!')
+            })
+        } else {
+            message.error('请勾选')
+        }
+    }, [getAdList, selectedRows])
+
+    // 单个启停
+    const onChange = () => {
+        getAdList.refresh()
+        setSelectedRows([])
+    }
+
+    const details = (data: any) => {
+        setDetailData(data)
+        setDetailShow(true)
+    }
+
+
+    useEffect(() => {
+        const headerBodyScroll = (e: any) => {
+            let el = document.querySelector(`.expandClassname .expendTable .ant-table-body`);
+            if (el) {
+                setScrollLeft(e.target.scrollLeft)
+            }
+        }
+        document.querySelector(`.expandClassname .expendTable .ant-table-body`)?.addEventListener('scroll', headerBodyScroll);
+        () => {
+            document.querySelector(`.expandClassname .expendTable .ant-table-body`)?.removeEventListener('scroll', headerBodyScroll);
+        }
+    }, [])
+
+    const log = (value: any) => {
+        setAccountIdRule(value.account_id)
+        setAdgroupId(value.adgroup_id)
+        setAdgroupName(value.adgroup_name)
+        setLogVisible(true)
+    }
+
+    const handleTag = (value: any) => {
+        setTagData(value)
+        setTagVisible(true)
+    }
+
+    const delTag = (value: any) => {
+        delUserTag.run({ accountId: value.account_id, adgroupId: value.adgroup_id }).then(res => {
+            if (res) {
+                message.success('删除成功')
+                getAdList.refresh()
+            }
+        })
+    }
+
+    const handleColumnTrend = (value: string) => {
+        let message = localStorage.getItem(`myAdMonitorConfig1.0.1_${configName}`)
+        if (message) {
+            message = JSON.parse(message)
+        }
+        let columns: string[] = []
+        if (message && Array.isArray(message)) {
+            message.forEach((item: { serverIndex: any; dataIndex: string; }) => {
+                if (!['cz', 'cost_speed'].includes(item.dataIndex)) {
+                    columns.push(item?.serverIndex || 'adgroup_data.' + item.dataIndex)
+                }
+            })
+        } else {
+            planAdConfig.forEach((item: any) => {
+                item?.data?.forEach((d: { default: any, serverIndex: string, dataIndex: string }) => {
+                    if (d.default && !['cz', 'cost_speed'].includes(d.dataIndex)) {
+                        columns.push(d?.serverIndex || 'adgroup_data.' + d.dataIndex)
+                    }
+                })
+            })
+        }
+        setTrendData({ ...queryForm, ...filterForm, columns, field: value.replace('_total', '') })
+        setTrendVisible(true)
+    }
+
+    // 批量启停
+    const adStatus = (type: boolean) => {
+        let newSelectedRows = []
+        if (type) {
+            newSelectedRows = selectedRows.filter((item: { configuredStatus: string, adgroupId: number }) => item.configuredStatus === 'AD_STATUS_SUSPEND')
+        } else {
+            newSelectedRows = selectedRows.filter((item: { configuredStatus: string, adgroupId: number }) => item.configuredStatus === 'AD_STATUS_NORMAL')
+        }
+        if (newSelectedRows.length === 0) {
+            message.warn(`所有广告都是${type ? '启动' : '暂停'}状态,无需${type ? '启动' : '暂停'}操作`)
+            return
+        }
+        let accountAdgroupMaps = [...new Set(newSelectedRows?.map(item => item.accountId + ',' + item.adgroupId))]
+        modifyStatusBatch.run({ accountAdgroupMaps, suspend: type }).then(res => {
+            message.success(`${type ? '启动' : '暂停'}成功`)
+            getAdList.refresh()
+            setSelectedRows([])
+        })
+    }
+
+
+    const countDecimals = (num: number) => {
+        // 匹配数字的小数部分
+        const match = String(num).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);
+        // 返回小数位数
+        return match ? Math.max(0, (match[1] ? match[1].length : 0) - (match[2] ? +match[2] : 0)) : 0;
+    }
+
+    return <div>
+        {/* 列表指标趋势 */}
+        {trendVisible && <ColumnTrend visible={trendVisible} data={trendData} onClose={() => { setTrendVisible(false) }} />}
+        {/* 打标签 */}
+        {tagVisible && <PlanTag
+            visible={tagVisible}
+            data={tagData}
+            onClose={() => setTagVisible(false)}
+            onChange={() => {
+                getAdList.refresh()
+                setTagVisible(false)
+            }}
+        />}
+        <Row gutter={[6, 6]} align='middle' style={{ marginBottom: 10 }}>
+            <FilterQuery
+                queryForm={queryForm}
+                setQueryForm={setQueryForm}
+                onChange={(value) => {
+                    setFilterForm({ ...value })
+                }}
+            />
+        </Row>
+        <div ref={ref} className='expandClassname'>
+            <TableData
+                refreshData={getList}
+                isCard={false}
+                className='expendTable'
+                columns={() => tablePlanConfig(onChange, details, log, handleTag, delTag)}
+                ajax={getAdList}
+                syncAjax={sync}
+                fixed={{ left: 2, right: 4 }}
+                dataSource={getAdList?.data?.data?.records}
+                loading={getAdList?.loading || syncBatch?.loading}
+                scroll={{ y: 560 }}
+                total={getAdList?.data?.data?.total}
+                page={getAdList?.data?.data?.current}
+                pageSize={getAdList?.data?.data?.size}
+                myKey={'adgroup_id'}
+                gutter={[0, 10]}
+                config={planAdConfig}
+                configName={configName}
+                rowClassName={(record) => {
+                    if (record?.tag_value === 100) {
+                        return 'row_error'
+                    } else if (record?.tag_value === 90) {
+                        return 'row_warning'
+                    } else {
+                        return ''
+                    }
+                }}
+                leftChild={<Space direction='vertical'>
+                    <Row gutter={[10, 10]} align='middle'>
+                        <Col><Button type='primary' style={{ background: '#67c23a', borderColor: '#67c23a' }} loading={modifyStatusBatch.loading} icon={<PlayCircleOutlined />} disabled={selectedRows.length === 0} onClick={() => adStatus(true)}>启动</Button></Col>
+                        <Col><Button type='primary' style={{ background: '#e6a23c', borderColor: '#e6a23c' }} loading={modifyStatusBatch.loading} icon={<PauseCircleOutlined />} disabled={selectedRows.length === 0} onClick={() => adStatus(false)}>暂停</Button></Col>
+                        <Col><Button type='primary' icon={<TransactionOutlined />} disabled={selectedRows.length === 0} onClick={() => setUpdate({ visible: true })}>修改出价</Button></Col>
+                    </Row>
+                </Space>}
+                rowSelection={{
+                    selectedRowKeys: selectedRows.map(item => item.adgroup_id.toString()),
+                    getCheckboxProps: (record: any) => ({
+                        disabled: record.status === 'STATUS_DELETED'
+                    }),
+                    onSelect: (record: { adgroup_id: number, mpName: string }, selected: boolean) => {
+                        if (selected) {
+                            selectedRows.push({ ...record })
+                            setSelectedRows([...selectedRows])
+                        } else {
+                            let newSelectAccData = selectedRows.filter((item: { adgroup_id: number }) => item.adgroup_id !== record.adgroup_id)
+                            setSelectedRows([...newSelectAccData])
+                        }
+                    },
+                    onSelectAll: (selected: boolean, selectedRowss: { adgroup_id: number }[], changeRows: { adgroup_id: number }[]) => {
+                        if (selected) {
+                            let newSelectAccData = [...selectedRows]
+                            changeRows.forEach((item: { adgroup_id: number }) => {
+                                let index = newSelectAccData.findIndex((ite: { adgroup_id: number }) => ite.adgroup_id === item.adgroup_id)
+                                if (index === -1) {
+                                    newSelectAccData.push({ ...item })
+                                }
+                            })
+                            setSelectedRows([...newSelectAccData])
+                        } else {
+                            let newSelectAccData = selectedRows.filter((item: { adgroup_id: number }) => {
+                                let index = changeRows.findIndex((ite: { adgroup_id: number }) => ite.adgroup_id === item.adgroup_id)
+                                if (index !== -1) {
+                                    return false
+                                } else {
+                                    return true
+                                }
+                            })
+                            setSelectedRows([...newSelectAccData])
+                        }
+                    }
+                }}
+                onChange={(props: any) => {
+                    let { sortData, pagination } = props
+                    let { current, pageSize } = pagination
+                    let newQueryForm = JSON.parse(JSON.stringify(queryForm))
+                    newQueryForm.pageNum = current
+                    newQueryForm.pageSize = pageSize
+                    if (sortData && JSON.stringify('sortData') !== '{}') {
+                        let { field, order } = sortData   // descend 降序 大到小  ascend 升序 小到大
+                        if (order) {
+                            newQueryForm.sortColumn = field
+                            newQueryForm.sortAsc = order === 'ascend'
+                        } else {
+                            Object.keys(newQueryForm).forEach(key => {
+                                if (key === 'sortColumn' || key === 'sortAsc') {
+                                    delete newQueryForm[key]
+                                }
+                            })
+                        }
+                    } else {
+                        Object.keys(newQueryForm).forEach(key => {
+                            if (key === 'sortField' || key === 'sort') {
+                                delete newQueryForm[key]
+                            }
+                        })
+                    }
+                    setQueryForm({ ...newQueryForm })
+                }}
+                expandedRowRender={(data) => <AdExpandedRowRender data={data} scrollLeft={scrollLeft} width={size?.width} />}
+                summary={() => (
+                    <Table.Summary fixed>
+                        <Table.Summary.Row className='s_summary'>
+                            {tableField.map((item, index) => {
+                                let data = totalData[item.dataIndex]
+                                let value = (data === 0 || data) ? countDecimals(data) > 2 ? data.toFixed(2) : data : '--'
+                                if (item.dataIndex === 'zj') {
+                                    return <Table.Summary.Cell index={index} key={item.dataIndex} align="center"><strong>总计</strong></Table.Summary.Cell>
+                                } else if (['ctr_total', 'mp_follow_rate_total',
+                                    'add_quick_app_rate_total', 'scan_follow_rate_total',
+                                    'first_day_order_roi_total', 'order_rate_total',
+                                    'order_roi_total', 'conversions_rate_total'
+                                ].includes(item.dataIndex)) {
+                                    return <Table.Summary.Cell index={index} key={item.dataIndex} align="center">
+                                        <Space size={4}>
+                                            <strong>
+                                                {value !== '--' ? <Statistic value={value} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" /> : '--'}
+                                            </strong>
+                                            {value !== '--' && <a onClick={() => handleColumnTrend(item.dataIndex)}><LineChartOutlined /></a>}
+                                        </Space>
+                                    </Table.Summary.Cell>
+                                } else {
+                                    return <Table.Summary.Cell index={index} key={item.dataIndex} align="center">
+                                        <Space size={4}>
+                                            <strong><Statistic value={value} /></strong>
+                                            {value !== '--' && <a onClick={() => handleColumnTrend(item.dataIndex)}><LineChartOutlined /></a>}
+                                        </Space>
+                                    </Table.Summary.Cell>
+                                }
+                            })}
+                        </Table.Summary.Row>
+                    </Table.Summary>
+                )}
+            />
+        </div>
+        {detailShow && <Details visible={detailShow} onClose={() => { setDetailShow(false) }} data={detailData} />}
+        {logVisible && <RuleAccountLog accountId={accountIdRule} adgroupName={adgroupName} adgroupId={adgroupId} visible={logVisible} onClose={() => setLogVisible(false)} />}
+        {/* 修改广告 */}
+        {update.visible && <UpdateAd
+            {...update}
+            selectedRows={selectedRows.map(item => ({ ...item, accountId: item.account_id, adgroupId: item.adgroup_id }))}
+            onChange={() => {
+                setUpdate({ visible: false })
+                getAdList.refresh()
+                setSelectedRows([])
+            }}
+            onClose={() => { setUpdate({ visible: false }) }}
+        />}
+    </div>
+}
+export default AdPlanList

+ 173 - 0
src/pages/launchSystemNew/adMonitorListV3/config.ts

@@ -0,0 +1,173 @@
+const planAdConfig = [
+    {
+        label: '广告详情',
+        data: [
+            { title: '启停', dataIndex: 'configured_status', serverIndex: 'adgroup.configured_status', label: '广告详情', default: 1, width: 40 },
+            { title: '所属账号', dataIndex: 'account_id', serverIndex: 'adgroup.account_id', label: '广告详情', default: 2, width: 75 },
+            { title: '腾讯备注', dataIndex: 'memo', serverIndex: 'ad_account.memo', label: '广告详情', default: 3, width: 80 },
+            { title: '本地备注', dataIndex: 'remark', serverIndex: 'ad_account.remark', label: '广告详情', default: 4, width: 80 },
+            { title: '广告ID', dataIndex: 'adgroup_id', serverIndex: 'adgroup.adgroup_id', label: '广告详情', default: 5, width: 90 },
+            { title: '投手', dataIndex: 'put_user_name', serverIndex: 'sys_user.put_user_name', label: '广告详情', default: 6, width: 70 },
+            { title: '广告名称', dataIndex: 'adgroup_name', serverIndex: 'adgroup.adgroup_name', label: '广告详情', default: 7, width: 280 },
+            { title: '投放日期', dataIndex: 'begin_date', serverIndex: 'adgroup.begin_date, adgroup.end_date', label: '广告详情', default: 8, width: 150 },
+            { title: '投放时间', dataIndex: 'time_series', serverIndex: 'adgroup.time_series', label: '广告详情', default: 9, width: 55 },
+            { title: '首日开始投放时间', dataIndex: 'first_day_begin_time', serverIndex: 'adgroup.first_day_begin_time', label: '广告详情', default: 10, width: 70 },
+            { title: '出价', dataIndex: 'bid_amount', serverIndex: 'adgroup.bid_amount,adgroup.bid_mode,adgroup.optimization_goal', label: '广告详情', default: 11, width: 140 },
+            { title: '深度优化行为出价', dataIndex: 'deep_conversion_behavior_bid', serverIndex: 'adgroup.deep_conversion_behavior_bid', label: '广告详情', default: 12, width: 70 },
+            { title: '出价类型', dataIndex: 'smart_bid_type', serverIndex: 'adgroup.smart_bid_type', label: '广告详情', default: 13, width: 80 },
+            { title: '出价策略', dataIndex: 'bid_strategy', serverIndex: 'adgroup.bid_strategy', label: '广告详情', default: 14, width: 80 },
+            { title: '广告组日预算(元)', dataIndex: 'daily_budget', serverIndex: 'adgroup.daily_budget', label: '广告详情', default: 15, width: 80 },
+            { title: '是否开启自动版位功能', dataIndex: 'automatic_site_enabled', serverIndex: 'adgroup.automatic_site_enabled', label: '广告详情', default: 16, width: 80 },
+            { title: '定向条件描述', dataIndex: 'targeting_translation', serverIndex: 'adgroup.targeting_translation', label: '广告详情', default: 17, width: 120 },
+            { title: '创建时间', dataIndex: 'created_time', serverIndex: 'adgroup.created_time', label: '广告详情', default: 18, width: 140 },
+            { title: '是否已删除', dataIndex: 'is_deleted', serverIndex: 'adgroup.is_deleted', label: '广告详情', default: 19, width: 60 },
+            { title: '广告状态', dataIndex: 'system_status', serverIndex: 'adgroup.system_status', label: '广告详情', default: 20, width: 70 },
+            { title: '广告详情', dataIndex: 'cost_speed', label: '广告详情', default: 21, width: 80 },
+            { title: '标记备注', dataIndex: 'tag_remark', label: '广告详情', width: 80, serverIndex: 'adgroup_user_tag.tag_remark' },
+            { title: '操作', dataIndex: 'cz', label: '广告详情', default: 22, width: 116 },
+        ]
+    },
+    {
+        label: '广告消耗信息',
+        data: [
+            { title: '消耗', dataIndex: 'cost_total', label: '广告消耗信息', width: 110 },
+            { title: '曝光量', dataIndex: 'view_total', label: '广告消耗信息', width: 100 },
+            { title: '千次曝光成本', dataIndex: 'thousand_display_price_total', label: '广告消耗信息', width: 110 },
+            { title: '点击量', dataIndex: 'click_total', label: '广告消耗信息', width: 100 },
+            { title: '点击率', dataIndex: 'ctr_total', label: '广告消耗信息', width: 100 },
+            { title: '点击均价', dataIndex: 'cpc_total', label: '广告消耗信息', width: 100 },
+            { title: '不感兴趣点击次数', dataIndex: 'no_interest_count_total', label: '广告消耗信息', width: 100 },
+            { title: '朋友圈视频播放次数', dataIndex: 'video_play_count_total', label: '广告消耗信息', width: 100 },
+        ]
+    },
+    {
+        label: '广告转化信息',
+        data: [
+            { title: '下载次数', dataIndex: 'download_count_total', label: '广告转化信息', width: 95 },
+            { title: '安装次数', dataIndex: 'install_count_total', label: '广告转化信息', width: 95 },
+            { title: '激活次数', dataIndex: 'activated_count_total', label: '广告转化信息', width: 95 },
+            { title: '公众号关注人数', dataIndex: 'mp_follow_uv_total', label: '广告转化信息', width: 95 },
+            { title: '公众号关注成本', dataIndex: 'mp_follow_cost_total', label: '广告转化信息', width: 100 },
+            { title: '公众号关注率', dataIndex: 'mp_follow_rate_total', label: '广告转化信息', width: 95 },
+            { title: '公众号关注次数', dataIndex: 'mp_follow_pv_total', label: '广告转化信息', width: 95 },
+            { title: '公众号关注次数成本', dataIndex: 'mp_follow_pv_cost_total', label: '广告转化信息', width: 110 },
+            { title: '快应用添加次数', dataIndex: 'add_quick_app_pv_total', label: '广告转化信息', width: 95 },
+            { title: '快应用添加成本', dataIndex: 'add_quick_app_cost_total', label: '广告转化信息', width: 110 },
+            { title: '快应用添加率', dataIndex: 'add_quick_app_rate_total', label: '广告转化信息', width: 95 },
+            { title: '加企业微信客服人数', dataIndex: 'scan_follow_uv_total', label: '广告转化信息', width: 105 },
+            { title: '加企业微信客服成本', dataIndex: 'scan_follow_cost_total', label: '广告转化信息', width: 110 },
+            { title: '加企业微信客服率', dataIndex: 'scan_follow_rate_total', label: '广告转化信息', width: 105 },
+        ]
+    },
+    {
+        label: '商品转化',
+        data: [
+            { title: '首日新增下单量', dataIndex: 'first_day_order_count_total', label: '商品转化', width: 95 },
+            { title: '首日新增下单金额', dataIndex: 'first_day_order_amount_total', label: '商品转化', width: 105 },
+            { title: '首日新增下单ROI', dataIndex: 'first_day_order_roi_total', label: '商品转化', width: 95 },
+            { title: '订单量', dataIndex: 'order_count_total', label: '商品转化', width: 95},
+            { title: '订单金额', dataIndex: 'order_amount_total', label: '商品转化', width: 105 },
+            { title: '下单成本', dataIndex: 'order_cost_total', label: '商品转化', width: 100 },
+            { title: '下单率', dataIndex: 'order_rate_total', label: '商品转化', width: 95 },
+            { title: '下单ROI', dataIndex: 'order_roi_total', label: '商品转化', width: 95 },
+            { title: '客单价', dataIndex: 'atv_total', label: '商品转化', width: 95 },
+            { title: '转化量', dataIndex: 'conversions_count_total', label: '商品转化', width: 95 },
+            { title: '转化成本', dataIndex: 'conversions_cost_total', label: '商品转化', width: 100 },
+            { title: '深度转化', dataIndex: 'deep_conversions_count_total', label: '商品转化', width: 95 },
+            { title: '转化率', dataIndex: 'conversions_rate_total', label: '商品转化', width: 95 },
+            { title: '加粉数', dataIndex: 'add_fans_count_total', label: '商品转化', width: 95 },
+            { title: '加粉成本', dataIndex: 'add_fans_cost_total', label: '商品转化', width: 100 },
+        ]
+    },
+]
+
+const qiliangpaihanghour = [
+    {
+        label: '设置信息',
+        data: [
+            { title: '时间', dataIndex: 'day', label: '设置信息', default: 1, width: 110 },
+            { title: '广告名称', dataIndex: 'adgroup_name', label: '设置信息', default: 2, width: 170 },
+            { title: '广告ID', dataIndex: 'adgroup_id', label: '设置信息', default: 9, width: 90 },
+            { title: '广告账户', dataIndex: 'account_id', label: '设置信息', default: 10, width: 70 },
+            { title: '投手', dataIndex: 'put_user_name', serverIndex: 'sys_user.put_user_name', label: '设置信息', default: 11, width: 65 },
+            { title: '投放时间', dataIndex: 'begin_date', serverIndex: 'adgroup.begin_date, adgroup.end_date', label: '设置信息', default: 12, width: 135 },
+            { title: '广告状态', dataIndex: 'system_status', serverIndex: 'adgroup.system_status', label: '设置信息', width: 85 },
+            { title: '操作', dataIndex: 'event', label: '设置信息', default: 13, width: 170 },
+        ]
+    },
+    {
+        label: '费用',
+        data: [
+            { title: '广告预算', dataIndex: 'daily_budget', serverIndex: 'adgroup.daily_budget', label: '费用', width: 80 },
+            { title: '出价方式', dataIndex: 'bid_mode', serverIndex: 'adgroup.bid_mode',label: '费用', width: 65 },
+            { title: '当前出价', dataIndex: 'bid_amount', serverIndex: 'adgroup.bid_amount', label: '费用', width: 80 },
+            // { title: '计划预算', dataIndex: 'planBudget', label: '费用' },
+            { title: '广告总消耗', dataIndex: 'cost_total', label: '费用', default: 24, width: 100 },
+            { title: '今日消耗', dataIndex: 'cost_day', label: '费用', default: 15, width: 90 },
+            { title: '当前小时消耗', dataIndex: 'cost_hour', label: '费用', default: 16, width: 80 },
+            { title: '前第1小时消耗', dataIndex: 'cost_last_hour', label: '费用', default: 17, width: 80 },
+            { title: '前第2小时消耗', dataIndex: 'cost_last_two_hour', label: '费用', default: 18, width: 80 },
+            { title: '前第3小时消耗', dataIndex: 'cost_last_three_hour', label: '费用', default: 19, width: 80 },
+            { title: '当前小时消耗差额', dataIndex: 'cost_diff_before_hour', label: '费用', default: 20, width: 80 },
+            { title: '前第1小时消耗差额', dataIndex: 'cost_diff_before_two_hour', label: '费用', default: 21, width: 90 },
+            { title: '前第2小时消耗差额', dataIndex: 'cost_diff_before_three_hour', label: '费用', default: 22, width: 90 },
+            { title: '前三小时消耗趋势', dataIndex: 'cost_trend_last_three_hour', label: '费用', default: 23, width: 70 },
+            { title: '当前5min消耗流速', dataIndex: 'cost_speed', label: '费用', default: 14, width: 70 },
+        ]
+    },
+    {
+        label: '曝光',
+        data: [
+            { title: '曝光量(曝光次数)', dataIndex: 'view_day', label: '曝光', default: 3, width: 70 },
+            { title: '千次曝光成本', dataIndex: 'thousand_display_price_day', label: '曝光', default: 4, width: 80 },
+        ]
+    },
+    {
+        label: '点击',
+        data: [
+            { title: '点击量(点击次数)', dataIndex: 'click_day', label: '点击', width: 75 },
+            { title: '点击均价', dataIndex: 'cpc_day', label: '点击', width: 75 },
+            { title: '点击率', dataIndex: 'ctr_day', label: '点击', width: 75 },
+        ]
+    },
+    {
+        label: '转化指标组',
+        data: [
+            { title: '优化目标(转化目标)', dataIndex: 'optimization_goal', serverIndex: 'adgroup.optimization_goal', label: '转化指标组', width: 120 },
+            { title: '转化目标量', dataIndex: 'conversions_count_day', label: '转化指标组', default: 7, width: 75 },
+            { title: '转化目标成本', dataIndex: 'conversions_cost_day', label: '转化指标组', default: 8, width: 75 },
+            { title: '目标转化率', dataIndex: 'conversions_rate_day', label: '转化指标组', width: 75 },
+        ]
+    },
+    {
+        label: '商品转化',
+        data: [
+            { title: '下单量', dataIndex: 'order_count_day', label: '商品转化', width: 70 },
+            // { title: '首日新增下单量', dataIndex: 'first_dayOrder_count_day', label: '商品转化', width: 80 },
+            { title: '下单成本', dataIndex: 'order_cost_day', label: '商品转化', width: 75 },
+            { title: '下单率', dataIndex: 'order_rate_day', label: '商品转化', width: 70 },
+            { title: '下单金额', dataIndex: 'order_amount_day', label: '商品转化', width: 70 },
+            // { title: '首日新增下单金额', dataIndex: 'first_day_order_amount_day', label: '商品转化', width: 75 },
+            { title: '下单客单价', dataIndex: 'atv_day', label: '商品转化', width: 70 },
+            { title: '下单ROI', dataIndex: 'order_roi_day', label: '商品转化', width: 75, default: 5 },
+            // { title: '首日新增下单ROI', dataIndex: 'firstDayOrderRoiDay', label: '商品转化', width: 90 },
+        ]
+    },
+    {
+        label: '转化',
+        data: [
+            { title: '公众号关注人数', dataIndex: 'mp_follow_uv_day', label: '转化', default: 6, width: 85 },
+            { title: '公众号关注率', dataIndex: 'mp_follow_rate_day', label: '转化', width: 70 },
+            { title: '公众号关注成本', dataIndex: 'mp_follow_cost_day', label: '转化', width: 85 },
+            { title: '加粉成本', dataIndex: 'add_fans_cost_day', label: '转化' },
+            { title: '公众号关注次数成本', dataIndex: 'mp_follow_pv_cost_day', label: '转化' },
+            { title: '加粉数', dataIndex: 'add_fans_count_day', label: '转化' },
+            { title: '公众号关注次数', dataIndex: 'mp_follow_pv_day', label: '转化' },
+            { title: '加企业微信客服人数', dataIndex: 'scan_follow_uv_day', label: '转化' },
+            { title: '加企业微信客服成本', dataIndex: 'scan_follow_cost_day', label: '转化' },
+            { title: '加企业微信客服率', dataIndex: 'scan_follow_rate_day', label: '转化' },
+        ]
+    },
+]
+
+export { planAdConfig, qiliangpaihanghour }

+ 36 - 0
src/pages/launchSystemNew/adMonitorListV3/index.tsx

@@ -0,0 +1,36 @@
+import { Card, Tabs } from "antd"
+import React, { useEffect, useState } from "react"
+import { useModel } from "umi"
+import Monitor from "./monitor"
+import AdPlanList from "./adPlanList"
+
+const AdMonitorListV3: React.FC = () => {
+
+    // 变量开始
+    const [tab, setTab] = useState<string>('monitor')  // tab切换
+    const { getPlanList, getPlanDetailList, getAllPlanList } = useModel('useAdMonitor.useMonitor')
+    const [userId] = useState(localStorage.getItem("userId") as string)
+    const { getPicherList } = useModel('useOperating.useWxGroupList')
+    // 变量结束
+    // 获取投手
+    useEffect(() => {
+        !getPicherList.data && getPicherList.run()
+    }, [])
+    return <div className="adMonitorList">
+        <Tabs activeKey={tab} className="adMonitorListTab" size="small" type="card" onChange={(activeKey: string) => {
+            if (activeKey === 'monitor') {
+                getAllPlanList.data && getAllPlanList.mutate([])
+            } else {
+                getPlanList.data && getPlanList.mutate([])
+                getPlanDetailList.data && getPlanDetailList.mutate([])
+            }
+            setTab(activeKey)
+        }}>
+            <Tabs.TabPane tab="今日起量广告监控" key="monitor" />
+            <Tabs.TabPane tab="广告列表" key="list" />
+        </Tabs>
+        {tab === 'monitor' ? <Monitor onChange={() => { setTab('list') }} /> : <Card bodyStyle={{ padding: '12px 16px' }}><AdPlanList userId={userId} /></Card>}
+    </div>
+}
+
+export default AdMonitorListV3

+ 469 - 0
src/pages/launchSystemNew/adMonitorListV3/monitor.tsx

@@ -0,0 +1,469 @@
+import { Card, Input, Radio, Select, Space, Spin, Tag, TimePicker, Tooltip } from "antd";
+import React, { useCallback, useEffect, useState } from "react";
+import { ColumnHeightOutlined, ColumnWidthOutlined, EyeInvisibleOutlined, EyeOutlined, RedoOutlined } from "@ant-design/icons";
+import useEcharts from '@/Hook/useEcharts'
+import { useModel } from 'umi'
+import { ListHourProps, ListType } from '@/services/adMonitor/adMonitor'
+import '../../adMonitor/adMonitorList/table.less'
+import moment from "moment";
+import TableData from "@/pages/launchSystemNew/components/TableData";
+import RuleAccountLog from "@/components/EarlyWarning/ruleAccountLog";
+import { LineField } from "@/pages/adMonitor/adMonitorList/config";
+import FilterQuery from "@/pages/adMonitor/adMonitorList/components/FilterQuery";
+import Details from "./Details";
+import { columnsMonitor } from "./tableMonitorConfig";
+import { qiliangpaihanghour } from "./config";
+interface newListType extends ListType {
+    totalTimeUnit: 'total' | 'minute' | 'hour' | 'day',
+    planTimeUnit: 'minute' | 'hour' | 'day'
+}
+
+/**
+ * 今日起量监控
+ * @param props 
+ * @returns 
+ */
+function Monitor(props: { onChange: () => void }) {
+    const { onChange } = props
+    const { getCostTrendV3List, getCostTopV3List, getListForHourV3, getAdqAccountList } = useModel('useAdMonitor.useMonitor')
+    const { getPicherList } = useModel('useOperating.useWxGroupList')
+    // 变量开始
+    const [queryForm, setQueryForm] = useState<newListType>({ totalTimeUnit: 'day', planTimeUnit: 'hour', sysUserId: JSON.parse(sessionStorage.getItem('SYSUSERID') || '[]'), pageNum: 1, pageSize: 20 }) // 搜索变量//startTime: moment().format('YYYY-MM-DD'), endTime: moment().format('YYYY-MM-DD'),
+    const { BarMonitor, LineMonitor } = useEcharts()
+    const [barDis, setBarDis] = useState<any[]>([])
+    const [lineDis, setLineDis] = useState<any[]>([])
+    const [lineTitle, setLineTitle] = useState<string>('广告总消耗趋势')
+    const [px, setPx] = useState<boolean>(false)//设置顶部图形的排列方式
+    const [visible, setVisible] = useState<boolean>(false) // 详情弹窗控制
+    const [aId, setAId] = useState<any>()
+    const [showEacharts, setShowEacharts] = useState<boolean>(true)
+    const [queryForHour, setQueryForHour] = useState<ListHourProps>({ pageNum: 1, pageSize: 20 })
+    const [filterQuery, setFilterQuery] = useState<any>({})
+
+    const [logVisible, setLogVisible] = useState<boolean>(false)
+    const [adgroupId, setAdgroupId] = useState<string>('')
+    const [adgroupName, setAdgroupName] = useState<string>('')
+    const [accountIdRule, setAccountIdRule] = useState<string>('')
+    const [trendColumns, setTrendColumns] = useState<string[]>(['cost'])
+
+    const { totalTimeUnit, planTimeUnit, adgroup, accountId, sysUserId, groupAccountIds } = queryForm
+    const configName = '起量广告排行明细3.0'
+    const { getGroupList, groupListInit } = useModel('useLaunchAdq.useAdAuthorize')
+
+    useEffect(() => {
+        groupListInit()
+    }, [])
+
+    useEffect(() => {
+        getList()
+    }, [queryForHour, filterQuery, queryForm?.sysUserId, queryForm?.accountId, queryForm?.adgroup, queryForm?.groupAccountIds])
+
+    const getList = () => {
+        let message = localStorage.getItem(`myAdMonitorConfig1.0.1_${configName}`)
+        if (message) {
+            message = JSON.parse(message)
+        }
+        let isAccountId = true
+        let isAdgroupId = true
+        let isAdgroupName = true
+        let columns: string[] = []
+        if (message && Array.isArray(message)) {
+            message.forEach((item: { serverIndex: any; dataIndex: string; }) => {
+                if (!['event'].includes(item.dataIndex)) {
+                    if (item.dataIndex === 'account_id') {
+                        isAccountId = false
+                    } else if (item.dataIndex === 'adgroup_id') {
+                        isAdgroupId = false
+                    } else if (item.dataIndex === 'adgroup_name') {
+                        isAdgroupName = false
+                    }
+                    columns.push(item?.serverIndex || 'adgroup_data.' + item.dataIndex)
+                }
+            })
+        } else {
+            qiliangpaihanghour.forEach((item: any) => {
+                item?.data?.forEach((d: { default: any, serverIndex: string, dataIndex: string }) => {
+                    if (d.default && !['event'].includes(d.dataIndex)) {
+                        if (d.dataIndex === 'account_id') {
+                            isAccountId = false
+                        } else if (d.dataIndex === 'adgroup_id') {
+                            isAdgroupId = false
+                        } else if (item.dataIndex === 'adgroup_name') {
+                            isAdgroupName = false
+                        }
+                        columns.push(d?.serverIndex || 'adgroup_data.' + d.dataIndex)
+                    }
+                })
+            })
+        }
+        if (isAccountId) {
+            columns.push('adgroup_data.account_id')
+        }
+        if (isAdgroupId) {
+            columns.push('adgroup_data.adgroup_id')
+        }
+        if (isAdgroupName) {
+            columns.push('adgroup_data.adgroup_name')
+        }
+
+        let params = { ...queryForHour, ...filterQuery }
+        if (queryForm?.sysUserId) {
+            params.sysUserIds = queryForm?.sysUserId
+        }
+        if (queryForm?.accountId && queryForm?.accountId?.length > 0) {
+            params.accountIdStr = queryForm?.accountId.toString()
+        }
+        if (queryForm?.adgroup) {
+            params.adgroupIdStr = queryForm?.adgroup
+        }
+        if (queryForm?.groupAccountIds) {
+            params.groupAccountIds = queryForm?.groupAccountIds
+        }
+        params.columns = columns
+        getListForHourV3.run(params)
+    }
+
+    // 获取投手
+    useEffect(() => {
+        !getPicherList.data && getPicherList.run()
+    }, [])
+    // 获取广告账号
+    useEffect(() => {
+        !getAdqAccountList.data && getAdqAccountList.run()
+    }, [])
+    // // 获取排行数据,柱图
+    useEffect(() => {
+        getPlanCostList()
+    }, [totalTimeUnit, accountId, sysUserId, groupAccountIds])
+    // 获取今日计划总消耗图谱,折线
+    useEffect(() => {
+        getTootalCostList()
+    }, [planTimeUnit, adgroup, accountId, sysUserId, groupAccountIds, trendColumns])
+
+    /** 获取折线图 */
+    const getTootalCostList = useCallback(async () => {
+        let { totalTimeUnit, planTimeUnit, pageNum, pageSize, adgroup, sysUserId, accountId, ...newQueryForm } = queryForm
+        let params = adgroup ? { ...newQueryForm, adgroupIdStr: adgroup } : newQueryForm
+        let res = await getCostTrendV3List.run({ ...params, timeUnit: planTimeUnit, sysUserIds: sysUserId, accountIdStr: accountId?.join(), trendColumns })
+        let data = trendColumns.map((field) => {
+            let value: any = {}
+            res?.data?.forEach((item: any, index: number) => {
+                if (index === 0) value.legendName = LineField[field];
+                value[item?.trend_unit] = item?.[field]
+            });
+            return value
+        })
+        setLineDis(() => data)
+        setLineTitle(() => `广告总${LineField[trendColumns[0]]}趋势`)
+    }, [queryForm, lineDis, trendColumns])
+
+    /** 获取柱状图 */
+    const getPlanCostList = useCallback(async () => {
+        let { totalTimeUnit, planTimeUnit, pageNum, pageSize, sysUserId, accountId, ...newQueryForm } = queryForm
+        let { adgroup, ...planQueryFrom } = newQueryForm
+        let res = await getCostTopV3List.run({ ...planQueryFrom, timeUnit: totalTimeUnit, sysUserIds: sysUserId, accountId: accountId?.join(), topN: 10 })
+        let data = res?.data?.map((item: { adgroupId: number, cost: number, adgroupName: string, accountId: number }) => {
+            return { name: item.adgroupId.toString(), value: item.cost, adName: item.adgroupName, accountId: item.accountId }
+        })
+        data = data?.sort((a: any, b: any) => {
+            var value1 = a['value'];
+            var value2 = b['value'];
+            return value2 - value1;
+        })
+        setBarDis(() => data)
+    }, [queryForm, barDis])
+
+    // 详情
+    const details = (data: any) => {
+        setAId(data)
+        setVisible(true)
+    }
+
+    // 计划详情
+    const planDetail = (data: any) => {
+        sessionStorage.setItem('ADIDORNAME', JSON.stringify(data))
+        onChange && onChange()
+    }
+
+    //全部接口刷新
+    const refresh = () => {
+        getCostTrendV3List.refresh()
+        getCostTopV3List.refresh()
+        getListForHourV3.refresh()
+    }
+
+    // 接口自动刷新10分钟一次
+    useEffect(() => {
+        let time = setInterval(() => {
+            refresh()
+        }, 1000 * 60 * 10)
+        return () => {
+            clearInterval(time)
+        }
+    }, [])
+
+    //图形排列样式改变重新获取数据刷新图形
+    const set = useCallback((b) => {
+        setPx(b)
+        getCostTrendV3List.refresh()
+        getCostTopV3List.refresh()
+    }, [getCostTopV3List, getCostTrendV3List, trendColumns])
+
+    // 处理折线图数据
+    const timePickerHandle = async (values: any, formatString: [string, string]) => {
+        let res = await getCostTrendV3List.data
+        let data = trendColumns.map((field) => {
+            let value: any = {}
+            res?.data?.forEach((item: any, index: number) => {
+                if (index === 0) value.legendName = LineField[field];
+                value[item?.trend_unit] = item?.[field]
+            });
+            return value
+        })
+
+        if (values) {
+            let date = moment().format('YYYY-MM-DD')
+            let strTime = formatString[0]
+            let endTime = formatString[1]
+            let dateTimeStr = `${date} ${strTime}:00`
+            let dateTimeEnd = `${date} ${endTime}:00`
+
+            let newData: any[] = data.map(item => {
+                let { legendName, ...otherData } = item
+                let newItem: any = { legendName }
+                for (const key in otherData) {
+                    if (Object.prototype.hasOwnProperty.call(otherData, key)) {
+                        const value = otherData[key];
+                        if (moment(dateTimeStr) <= moment(key) && moment(key) <= moment(dateTimeEnd)) {
+                            newItem[key] = value
+                        }
+                    }
+                }
+                return newItem
+            })
+            setLineDis(() => newData)
+        } else {
+            setLineDis(() => data)
+        }
+    }
+
+    const log = (value: any) => {
+        setAccountIdRule(value.account_id)
+        setAdgroupId(value.adgroup_id)
+        setAdgroupName(value.adgroup_name)
+        setLogVisible(true)
+    }
+
+    return <Space direction='vertical' style={{ width: '100%' }} className="monitor">
+        <Card hoverable bodyStyle={{ padding: '12px 16px' }}>
+            <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+                <Space>
+                    <Select
+                        showSearch
+                        value={queryForm.sysUserId}
+                        style={{ minWidth: 180, maxWidth: 250 }}
+                        mode='multiple'
+                        maxTagCount={1}
+                        allowClear
+                        placeholder="请选择投手"
+                        disabled={queryForm?.adgroup || queryForm?.accountId?.length > 0}
+                        onChange={(value: number[]) => {
+                            setQueryForm({ ...queryForm, sysUserId: value, pageNum: 1 })
+                            sessionStorage.setItem('SYSUSERID', value ? JSON.stringify(value) : '')
+                        }}
+                        filterOption={(input, option) =>
+                            (option?.children as any).toLowerCase().indexOf(input.toLowerCase()) >= 0
+                        }
+                    >
+                        {getPicherList?.data?.map((item: { nickname: string, userId: number }, index: number) =>
+                            <Select.Option
+                                value={item.userId}
+                                key={item.userId + '' + index}
+                            >
+                                {item.nickname}
+                            </Select.Option>
+                        )}
+                    </Select>
+                    <Select
+                        showSearch
+                        mode='multiple'
+                        maxTagCount={1}
+                        value={queryForm.accountId}
+                        style={{ minWidth: 220, maxWidth: 250 }}
+                        allowClear
+                        placeholder="请选择广告账号"
+                        onChange={(value: number[]) => {
+                            setQueryForm({ ...queryForm, accountId: value, pageNum: 1 })
+                        }}
+                    >
+                        {getAdqAccountList?.data?.data?.map((item: { id: number, accountId: number }) => <Select.Option
+                            value={item.accountId}
+                            key={item.id}
+                        >
+                            {item.accountId}
+                        </Select.Option>)}
+                    </Select>
+                    <Input
+                        value={queryForm?.accountId?.toString()}
+                        placeholder="输入广告账号"
+                        onChange={(e) => {
+                            setQueryForm({ ...queryForm, accountId: e.target.value ? e.target.value?.split(/[,,\s\n]+/) : undefined })
+                        }}
+                        allowClear
+                    />
+                    <Input
+                        value={queryForm.adgroup}
+                        placeholder="广告ID"
+                        onChange={(e) => {
+                            setQueryForm({ ...queryForm, adgroup: e.target.value })
+                        }}
+                        allowClear
+                    />
+                    <Select
+                        showSearch
+                        mode='multiple'
+                        maxTagCount={1}
+                        value={queryForm.groupAccountIds}
+                        style={{ minWidth: 150 }}
+                        allowClear
+                        placeholder="请选择账号分组"
+                        onChange={(value: number[]) => {
+                            setQueryForm({ ...queryForm, groupAccountIds: value, pageNum: 1 })
+                        }}
+                    >
+                        {getGroupList?.data?.map((item: { groupId: number, groupName: number }) => <Select.Option
+                            value={item.groupId}
+                            key={item.groupId}
+                        >
+                            {item.groupName}
+                        </Select.Option>)}
+                    </Select>
+                </Space>
+                <Space>
+                    <Tag onClick={() => setShowEacharts(!showEacharts)}>{showEacharts ? <><EyeInvisibleOutlined /> 隐藏</> : <><EyeOutlined /> 显示</>}</Tag>
+                    <Tag color="#2db7f5" onClick={refresh}><RedoOutlined />   刷新</Tag>
+                </Space>
+            </div>
+        </Card>
+        {showEacharts && <Card hoverable bodyStyle={{ padding: '12px 16px' }}>
+            <span style={{ position: 'absolute', top: 10, zIndex: 10 }}>
+                {px ?
+                    <Tooltip title='左右排列'><Tag color='#f50' onClick={() => { set(false) }}><ColumnWidthOutlined /><span style={{ fontSize: 10 }}>左右排列</span></Tag></Tooltip>
+                    :
+                    <Tooltip title='上下排列'><Tag color='#f50' onClick={() => { set(true) }} ><ColumnHeightOutlined /><span style={{ fontSize: 10 }}>上下排列</span></Tag></Tooltip>
+                }
+            </span>
+            <div className={!px ? 'charts' : 'charts charts100'}>
+                <div>
+                    <div className="selectTime">
+                        <Space>
+                            <span style={{ fontSize: 10, color: '#999' }}>刷新时间:{getCostTopV3List?.data?.reqTime}</span>
+                            <Radio.Group value={queryForm.totalTimeUnit} buttonStyle="solid" size='small' onChange={(e) => { setQueryForm({ ...queryForm, totalTimeUnit: e.target.value }) }}>
+                                <Radio.Button value="total">总</Radio.Button>
+                                <Radio.Button value="day">天</Radio.Button>
+                                <Radio.Button value="hour">小时</Radio.Button>
+                                <Radio.Button value="minute">5min</Radio.Button>
+                            </Radio.Group>
+                        </Space>
+                    </div>
+                    {getCostTopV3List?.loading ? <Spin /> : <BarMonitor style={{ width: '100%', height: '100%' }} data={barDis} xName="今日消耗" yName="广告名称" onChange={(value: string, accountId) => { console.log(value, accountId); setQueryForm({ ...queryForm, adgroup: value }) }} planID={queryForm?.adgroup} />}
+                </div>
+                <div>
+                    <div className="selectTime">
+                        <Space>
+                            <span style={{ fontSize: 10, color: '#999' }}>刷新时间:{getCostTrendV3List?.data?.reqTime}</span>
+                            <Radio.Group value={queryForm.planTimeUnit} buttonStyle="solid" size='small' onChange={(e) => { setQueryForm({ ...queryForm, planTimeUnit: e.target.value }) }}>
+                                <Radio.Button value="hour">小时</Radio.Button>
+                                <Radio.Button value="minute">5min</Radio.Button>
+                            </Radio.Group>
+                            {queryForm.planTimeUnit === 'minute' && <TimePicker.RangePicker
+                                size="small"
+                                format='HH:mm'
+                                minuteStep={5}
+                                onChange={timePickerHandle}
+                            />}
+                            <Select
+                                showSearch
+                                mode='multiple'
+                                maxTagCount={1}
+                                value={trendColumns}
+                                style={{ minWidth: 130 }}
+                                allowClear
+                                placeholder="请选择图表字段"
+                                onChange={(value: string[]) => {
+                                    setTrendColumns(value?.length ? value : ['cost'])
+                                }}
+                                size="small"
+                            >
+                                {Object.keys(LineField).map((key) => <Select.Option value={key} key={key}>
+                                    {LineField[key]}
+                                </Select.Option>)}
+                            </Select>
+                        </Space>
+                    </div>
+                    {getCostTrendV3List?.loading ? <Spin /> : <LineMonitor style={{ width: '100%', height: '100%' }} series smooth data={lineDis} title={trendColumns?.length > 1 ? undefined : lineTitle} />}
+                </div>
+            </div>
+        </Card>}
+        <div className={'MYtable'}>
+            <TableData
+                refreshData={getList}
+                bodyStyle={{ padding: '12px 16px' }}
+                gutter={[0, 12]}
+                columns={columnsMonitor(planDetail, details, (value) => { setQueryForm({ ...queryForm, adgroup: value }); }, log)}
+                dataSource={getListForHourV3?.data?.data?.records}
+                loading={getListForHourV3?.loading}
+                ajax={getListForHourV3}
+                leftChild={
+                    <Space>
+                        <FilterQuery onChange={(data) => {
+                            setFilterQuery(data)
+                        }} />
+                    </Space>
+                }
+                myKey={'adgroup_id'}
+                fixed={{ left: 0, right: 2 }}
+                total={getListForHourV3?.data?.data?.total}
+                onChange={(props: any) => {
+                    let { sortData, pagination } = props
+                    let { current, pageSize } = pagination
+                    let newQueryForm = JSON.parse(JSON.stringify(queryForHour))
+                    newQueryForm.pageNum = current
+                    newQueryForm.pageSize = pageSize
+                    if (sortData && JSON.stringify('sortData') !== '{}') {
+                        let { field, order } = sortData   // descend 降序 大到小  ascend 升序 小到大
+                        if (order) {
+                            newQueryForm.sortColumn = field
+                            newQueryForm.sortAsc = order === 'ascend'
+                        } else {
+                            Object.keys(newQueryForm).forEach(key => {
+                                if (key === 'sortColumn' || key === 'sortAsc') {
+                                    delete newQueryForm[key]
+                                }
+                            })
+                        }
+                    } else {
+                        Object.keys(newQueryForm).forEach(key => {
+                            if (key === 'sortField' || key === 'sort') {
+                                delete newQueryForm[key]
+                            }
+                        })
+                    }
+                    setQueryForHour({ ...newQueryForm })
+                }}
+                page={queryForHour.pageNum}
+                pageSize={queryForHour.pageSize}
+                scroll={{ y: 750 }}
+                config={qiliangpaihanghour}
+                configName={configName}
+            />
+        </div>
+
+        {visible && <Details visible={visible} onClose={() => { setVisible(false) }} data={aId} />}
+
+        {logVisible && <RuleAccountLog accountId={accountIdRule} adgroupName={adgroupName} adgroupId={adgroupId} visible={logVisible} onClose={() => setLogVisible(false)} />}
+    </Space>
+}
+
+
+export default React.memo(Monitor)

+ 1035 - 0
src/pages/launchSystemNew/adMonitorListV3/tableMonitorConfig.tsx

@@ -0,0 +1,1035 @@
+import useCopy from '@/Hook/useCopy'
+import { RiseOutlined } from '@ant-design/icons'
+import {  Progress, Space, Statistic } from 'antd'
+import { ColumnsType } from 'antd/lib/table'
+import React from 'react'
+import { ReactComponent as RocketSvg } from '@/assets/rocket.svg'
+import '@/pages/adMonitor/adMonitorList/index.less'
+import { CHUJIAFANGSHI, YOUHUAMUBIAO } from '@/pages/adMonitor/adMonitorList/enum'
+import { GGStateData } from '@/pages/adMonitor/adMonitorList/data'
+import { copy } from '@/utils/utils'
+import { ADGROUP_STATUS } from '../adqv3/const'
+function columnsMonitor(planDetail: (id: number) => void, details: (id: number) => void, onChange: (value: any) => void, log: (value: any) => void) {
+    const { copy } = useCopy()
+    return function columns() {
+        let newArr: ColumnsType<any> = [
+            {
+                title: '时间',
+                dataIndex: 'day',
+                key: 'day',
+                align: 'center',
+                width: 120,
+                ellipsis: true
+            },
+            {
+                title: '广告名称',
+                dataIndex: 'adgroup_name',
+                key: 'adgroup_name',
+                align: 'center',
+                width: 170,
+                ellipsis: true,
+                render: (str, b) => {
+                    return <a style={{ color: '#1890ff', fontSize: 12 }} onClick={() => { onChange(b.adgroup_id) }}>{str}</a>
+                }
+            },
+            {
+                title: '广告ID',
+                dataIndex: 'adgroup_id',
+                key: 'adgroup_id',
+                align: 'center',
+                width: 100,
+                ellipsis: true,
+                render: (a: any) => {
+                    return <a onClick={() => { copy(a) }} style={{ color: '#3946c3' }}>{a}</a>
+                }
+            },
+            {
+                title: '广告账户',
+                dataIndex: 'account_id',
+                key: 'account_id',
+                width: 70,
+                ellipsis: true,
+                align: 'center'
+            },
+            {
+                title: '投手',
+                dataIndex: 'put_user_name',
+                key: 'put_user_name',
+                align: 'center',
+                ellipsis: true,
+                width: 65
+            },
+            {
+                title: '投放时间',
+                dataIndex: 'begin_date',
+                key: 'begin_date',
+                width: 110,
+                align: 'center',
+                ellipsis: true,
+                render(value, record) {
+                    return value + '~' + (record?.end_date === '1970-01-01' ? '长期投放' : record?.end_date)
+                },
+            },
+            {
+                title: '广告状态',
+                dataIndex: 'system_status',
+                key: 'system_status',
+                align: 'center',
+                width: 105,
+                ellipsis: true,
+                render: (a: any) => {
+                    return ADGROUP_STATUS[a] || '--'
+                }
+            },
+            {
+                title: '广告预算',
+                dataIndex: 'daily_budget',
+                key: 'daily_budget',
+                width: 110,
+                align: 'center',
+                ellipsis: true,
+                render: (a: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '出价方式',
+                dataIndex: 'bid_mode',
+                key: 'bid_mode',
+                align: 'center',
+                width: 80,
+                render: (a: any) => {
+                    return CHUJIAFANGSHI[a] || '--'
+                }
+            },
+            {
+                title: '当前出价',
+                dataIndex: 'bid_amount',
+                key: 'bid_amount',
+                width: 110,
+                align: 'center',
+                ellipsis: true,
+                render: (a: any) => {
+                    return <div style={a >= 500 ? { backgroundColor: 'rgba(255, 80, 82, .72)', height: 26, color: '#fff', display: 'flex', justifyContent: 'center', alignItems: 'center', fontWeight: 600 } : {}}><Statistic value={a || 0} valueStyle={a >= 500 ? { fontSize: 14 } : {}} /></div>
+                }
+            },
+            {
+                title: '广告总消耗',
+                dataIndex: 'cost_total',
+                key: 'cost_total',
+                align: 'center',
+                width: 100,
+                ellipsis: true,
+                sorter: true,
+                render: (a: any) => {
+                    return <div style={{ height: 26, position: 'relative' }}>
+                        <Progress
+                            strokeColor={{
+                                from: '#10c1e9',
+                                to: '#6892d0',
+                            }}
+                            status="active"
+                            showInfo={false}
+                            percent={a ? a / 30000 * 100 : 0}
+                        />
+                        <span style={{ position: 'absolute', left: 0, top: 2, bottom: 0, right: 0 }}><Statistic value={a || 0} valueStyle={a >= 30000 ? { color: '#000', fontWeight: 500 } : { fontWeight: 500 }} /></span>
+                    </div>
+                }
+            },
+            {
+                title: '今日消耗',
+                dataIndex: 'cost_day',
+                key: 'cost_day',
+                align: 'center',
+                width: 105,
+                sorter: true,
+                render: (a: any) => {
+                    return <div style={{ height: 26, position: 'relative' }}>
+                        <Progress
+                            strokeColor={{
+                                from: '#ff5900',
+                                to: '#ffd380',
+                            }}
+                            status="active"
+                            showInfo={false}
+                            percent={a ? a / 2000 * 100 : 0}
+                        />
+                        <span style={{ position: 'absolute', left: 0, top: 2, bottom: 0, right: 0 }}><Statistic value={a || 0} valueStyle={a >= 2000 ? { color: '#000', fontWeight: 500 } : { fontWeight: 500 }} /></span>
+                    </div>
+                }
+            },
+            {
+                title: '当前小时消耗',
+                dataIndex: 'cost_hour',
+                key: 'cost_hour',
+                align: 'center',
+                width: 110,
+                sorter: true,
+                render: (a: any) => {
+                    return <div style={{ height: 26, position: 'relative' }}>
+                        <Progress
+                            strokeColor={{
+                                from: '#e7a0f5',
+                                to: '#d161f7',
+                            }}
+                            status="active"
+                            showInfo={false}
+                            percent={a ? a / 300 * 100 : 0}
+                        />
+                        <span style={{ position: 'absolute', left: 0, top: 2, bottom: 0, right: 0 }}><Statistic value={a || 0} valueStyle={a >= 300 ? { color: '#000', fontWeight: 500, fontSize: 15 } : { fontWeight: 500, fontSize: 15 }} /></span>
+                    </div>
+                }
+            },
+            {
+                title: '前第1小时消耗',
+                dataIndex: 'cost_last_hour',
+                key: 'cost_last_hour',
+                align: 'center',
+                width: 110,
+                sorter: true,
+                render: (a: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '前第2小时消耗',
+                dataIndex: 'cost_last_two_hour',
+                key: 'cost_last_two_hour',
+                align: 'center',
+                width: 80,
+                sorter: true,
+                render: (a: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '前第3小时消耗',
+                dataIndex: 'cost_last_three_hour',
+                key: 'cost_last_three_hour',
+                align: 'center',
+                width: 110,
+                sorter: true,
+                render: (a: number) => {
+                    return <Statistic value={a || 0} />
+                },
+            },
+            {
+                title: '当前小时消耗差额',
+                dataIndex: 'cost_diff_before_hour',
+                key: 'cost_diff_before_hour',
+                align: 'center',
+                width: 125,
+                sorter: true,
+                render: (a: any) => {
+                    return <Statistic value={a || 0} valueStyle={a > 0 ? { color: 'red' } : { color: 'green' }} />
+                }
+            },
+            {
+                title: '前第1小时消耗差额',
+                dataIndex: 'cost_diff_before_two_hour',
+                key: 'cost_diff_before_two_hour',
+                align: 'center',
+                width: 125,
+                sorter: true,
+                render: (a: any) => {
+                    return <Statistic value={a || 0} valueStyle={a > 0 ? { color: 'red' } : { color: 'green' }} />
+                }
+            },
+            {
+                title: "前第2小时消耗差额",
+                dataIndex: 'cost_diff_before_three_hour',
+                key: 'cost_diff_before_three_hour',
+                align: 'center',
+                width: 125,
+                sorter: true,
+                render: (a: number) => {
+                    return <Statistic value={a || 0} valueStyle={a > 0 ? { color: 'red' } : { color: 'green' }} />
+                },
+            },
+            {
+                title: "前三小时消耗趋势",
+                dataIndex: 'cost_trend_last_three_hour',
+                key: 'cost_trend_last_three_hour',
+                align: 'center',
+                width: 75,
+                render: (a: number) => {
+                    return a > 0 ? <RiseOutlined style={{ color: 'red', fontWeight: 900, fontSize: 22 }} /> : '--'
+                },
+            },
+            {
+                title: '当前5min消耗流速',
+                dataIndex: 'cost_speed',
+                key: 'cost_speed',
+                align: 'center',
+                width: 115,
+                render: (a: any) => {
+                    a = a ? parseFloat(a.toFixed(2)) : 0
+                    return <div style={{ height: 26, position: 'relative' }}>
+                        <Progress
+                            strokeColor={{
+                                from: '#00DDFF',
+                                to: '#37A2FF',
+                            }}
+                            status="active"
+                            showInfo={false}
+                            percent={a ? a / 100 * 100 : 0}
+                        />
+                        <span style={{ position: 'absolute', left: 0, top: 2, bottom: 0, right: 0 }}><Statistic value={a || 0} valueStyle={a >= 100 ? { color: '#000', fontWeight: 500 } : { fontWeight: 500 }} /></span>
+                    </div>
+                }
+            },
+            {
+                title: '曝光量',
+                dataIndex: 'view_day',
+                key: 'view_day',
+                align: 'center',
+                width: 70,
+                sorter: true,
+                render: (a: number) => {
+                    return <Statistic value={a || 0} />
+                },
+            },
+            {
+                title: '千次曝光成本',
+                dataIndex: 'thousand_display_price_day',
+                key: 'thousand_display_price_day',
+                align: 'center',
+                width: 115,
+                sorter: true,
+                render: (a: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '点击量',
+                dataIndex: 'click_day',
+                key: 'click_day',
+                align: 'center',
+                width: 70,
+                sorter: true,
+                render: (a: number) => {
+                    return <Statistic value={a || 0} />
+                },
+            },
+            {
+                title: '点击均价',
+                dataIndex: 'cpc_day',
+                key: 'cpc_day',
+                align: 'center',
+                width: 115,
+                sorter: true,
+                render: (a: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '点击率',
+                dataIndex: 'ctr_day',
+                key: 'ctr_day',
+                align: 'center',
+                width: 115,
+                sorter: true,
+                render: (a: any) => {
+                    a = a ? parseFloat((a * 100).toFixed(2)) : 0
+                    return a + '%'
+                }
+            },
+            {
+                title: '优化目标',
+                dataIndex: 'optimization_goal',
+                key: 'optimization_goal',
+                align: 'center',
+                width: 115,
+                render: (a: any) => {
+                    return YOUHUAMUBIAO[a] || '--'
+                }
+            },
+            {
+                title: '转化目标量',
+                dataIndex: 'conversions_count_day',
+                key: 'conversions_count_day',
+                align: 'center',
+                width: 115,
+                sorter: true,
+                render: (a: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '转化目标成本',
+                dataIndex: 'conversions_cost_day',
+                key: 'conversions_cost_day',
+                align: 'center',
+                width: 115,
+                sorter: true,
+                render: (a: any) => {
+                    return <div style={a > 500 ? { backgroundColor: '#efea5b', height: 26, color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 600 } : {}}><Statistic value={a || 0} /></div>
+                }
+            },
+            {
+                title: '目标转化率',
+                dataIndex: 'conversions_rate_day',
+                key: 'conversions_rate_day',
+                align: 'center',
+                width: 115,
+                sorter: true,
+                render: (a: any) => {
+                    a = a ? parseFloat((a * 100).toFixed(2)) : 0
+                    return a + '%'
+                }
+            },
+            {
+                title: '下单量',
+                dataIndex: 'order_count_day',
+                key: 'order_count_day',
+                align: 'center',
+                width: 115,
+                sorter: true,
+                render: (a: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            // {
+            //     title: '首日新增下单量',
+            //     dataIndex: 'first_dayOrder_count_day',
+            //     key: 'first_dayOrder_count_day',
+            //     align: 'center',
+            //     width: 115,
+            //     sorter: true,
+            //     render: (a: any) => {
+            //         return <Statistic value={a || 0} />
+            //     }
+            // },
+            {
+                title: '下单成本',
+                dataIndex: 'order_cost_day',
+                key: 'order_cost_day',
+                align: 'center',
+                width: 115,
+                sorter: true,
+                render: (a: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '下单率',
+                dataIndex: 'order_rate_day',
+                key: 'order_rate_day',
+                align: 'center',
+                width: 115,
+                sorter: true,
+                render: (a: any) => {
+                    a = a ? parseFloat((a * 100).toFixed(2)) : 0
+                    return a + '%'
+                }
+            },
+            {
+                title: '下单金额',
+                dataIndex: 'order_amount_day',
+                key: 'order_amount_day',
+                align: 'center',
+                width: 115,
+                sorter: true,
+                render: (a: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            // {
+            //     title: '首日新增下单金额',
+            //     dataIndex: 'first_day_order_amount_day',
+            //     key: 'first_day_order_amount_day',
+            //     align: 'center',
+            //     width: 115,
+            //     sorter: true,
+            //     render: (a: any) => {
+            //         return <Statistic value={a || 0} />
+            //     }
+            // },
+            {
+                title: '下单客单价',
+                dataIndex: 'atv_day',
+                key: 'atv_day',
+                align: 'center',
+                width: 115,
+                sorter: true,
+                render: (a: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '下单ROI',
+                dataIndex: 'order_roi_day',
+                key: 'order_roi_day',
+                align: 'center',
+                width: 115,
+                sorter: true,
+                render: (a: any) => {
+                    a = a ? parseFloat((a * 100).toFixed(2)) : 0
+                    return a + '%'
+                }
+            },
+            // {
+            //     title: '首日新增下单ROI',
+            //     dataIndex: 'first_day_order_roi_day',
+            //     key: 'first_day_order_roi_day',
+            //     align: 'center',
+            //     width: 115,
+            //     sorter: true,
+            //     render: (a: any) => {
+            //         a = a ? parseFloat((a * 100).toFixed(2)) : 0
+            //         return a + '%'
+            //     }
+            // },
+            {
+                title: '公众号关注人数',
+                dataIndex: 'mp_follow_uv_day',
+                key: 'mp_follow_uv_day',
+                align: 'center',
+                width: 115,
+                sorter: true,
+                render: (a: any) => {
+                    return a
+                }
+            },
+            {
+                title: '公众号关注率',
+                dataIndex: 'mp_follow_rate_day',
+                key: 'mp_follow_rate_day',
+                align: 'center',
+                width: 115,
+                sorter: true,
+                render: (a: any) => {
+                    a = a ? parseFloat((a * 100).toFixed(2)) : 0
+                    return a + '%'
+                }
+            },
+            {
+                title: '公众号关注成本',
+                dataIndex: 'mp_follow_cost_day',
+                key: 'mp_follow_cost_day',
+                align: 'center',
+                width: 115,
+                sorter: true,
+                render: (a: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '加粉成本',
+                dataIndex: 'add_fans_cost_day',
+                key: 'add_fans_cost_day',
+                align: 'center',
+                width: 75,
+                // sorter: true,
+                render: (a: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '公众号关注次数成本',
+                dataIndex: 'mp_follow_pv_cost_day',
+                key: 'mp_follow_pv_cost_day',
+                align: 'center',
+                width: 80,
+                // sorter: true,
+                render: (a: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '加粉数',
+                dataIndex: 'add_fans_count_day',
+                key: 'add_fans_count_day',
+                align: 'center',
+                width: 75,
+                // sorter: true,
+                render: (a: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '公众号关注次数',
+                dataIndex: 'mp_follow_pv_day',
+                key: 'mp_follow_pv_day',
+                align: 'center',
+                width: 75,
+                // sorter: true,
+                render: (a: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '加企业微信客服人数',
+                dataIndex: 'scan_follow_uv_day',
+                key: 'scan_follow_uv_day',
+                align: 'center',
+                width: 80,
+                // sorter: true,
+                render: (a: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '加企业微信客服成本',
+                dataIndex: 'scan_follow_cost_day',
+                key: 'scan_follow_cost_day',
+                align: 'center',
+                width: 80,
+                // sorter: true,
+                render: (a: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '加企业微信客服率',
+                dataIndex: 'scan_follow_rate_day',
+                key: 'scan_follow_rate_day',
+                align: 'center',
+                width: 80,
+                // sorter: true,
+                render: (a: any) => {
+                    a = a ? parseFloat((a * 100).toFixed(2)) : 0
+                    return a + '%'
+                }
+            },
+            {
+                title: '操作',
+                dataIndex: 'event',
+                key: 'event',
+                align: 'center',
+                width: 180,
+                render: (a: string, b: any) => (<Space>
+                    <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', fontSize: 14 }} onClick={() => details(b)}>
+                        <span role="img" aria-label="fund-view" className="anticon anticon-fund-view"><RocketSvg /></span><a style={{ marginLeft: 4, color: '#1890ff', fontSize: 12 }}>详情</a>
+                    </div>
+                    <a onClick={() => log(b)}>告警日志</a>
+                    <a style={{ color: '#1890ff', fontSize: 12 }} onClick={() => window.open(`https://ad.qq.com/atlas/${b?.account_id}/admanage/index?tab=adgroup&query={%22operation_status%22:[%22CALCULATE_STATUS_EXCLUDE_DEL%22],%22system_status%22:[],%22search_name%22:%22${b.adgroup_id}%22}`)} target="_blank">腾讯广告</a>
+                </Space>
+                )
+            }
+        ]
+        return newArr
+    }
+}
+
+let columnsList = () => {
+
+    return function columns() {
+        let newArr: ColumnsType<any> = [
+            {
+                title: '时间',
+                dataIndex: 'time',
+                key: 'time',
+                align: 'center',
+                width: 120,
+                sorter: true,
+                ellipsis: true
+            },
+            {
+                title: '数据更新时间',
+                dataIndex: 'create_time',
+                key: 'create_time',
+                align: 'center',
+                width: 120,
+                ellipsis: true
+            },
+            {
+                title: '广告ID',
+                dataIndex: 'adgroup_id',
+                key: 'adgroup_id',
+                align: 'center',
+                width: 100,
+                ellipsis: true,
+                render: (a: any) => {
+                    return <a onClick={() => { copy(a) }} style={{ color: '#3946c3' }}>{a}</a>
+                }
+            },
+            {
+                title: '广告账户',
+                dataIndex: 'account_id',
+                key: 'account_id',
+                width: 70,
+                ellipsis: true,
+                align: 'center'
+            },
+            {
+                title: '消耗',
+                dataIndex: 'cost',
+                key: 'cost',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '曝光量',
+                dataIndex: 'view',
+                key: 'view',
+                align: 'center',
+                width: 80
+            },
+            {
+                title: '千次曝光成本',
+                dataIndex: 'thousand_display_price',
+                key: 'thousand_display_price',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '点击量',
+                dataIndex: 'click',
+                key: 'click',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '点击率',
+                dataIndex: 'ctr',
+                key: 'ctr',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a ? (a * 100).toFixed(2) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                }
+            },
+            {
+                title: '点击均价',
+                dataIndex: 'cpc',
+                key: 'cpc',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '不感兴趣点击次数',
+                dataIndex: 'no_interest_count',
+                key: 'no_interest_count',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '朋友圈视频播放次数',
+                dataIndex: 'video_play_count',
+                key: 'video_play_count',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+
+
+            {
+                title: '下载次数',
+                dataIndex: 'download_count',
+                key: 'download_count',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '安装次数',
+                dataIndex: 'install_count',
+                key: 'install_count',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '激活次数',
+                dataIndex: 'activated_count',
+                key: 'activated_count',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '公众号关注人数',
+                dataIndex: 'mp_follow_uv',
+                key: 'mp_follow_uv',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '公众号关注成本',
+                dataIndex: 'mp_follow_cost',
+                key: 'mp_follow_cost',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '公众号关注率',
+                dataIndex: 'mp_follow_rate',
+                key: 'mp_follow_rate',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a ? (a * 100).toFixed(2) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                }
+            },
+            {
+                title: '公众号关注次数',
+                dataIndex: 'mp_follow_pv',
+                key: 'mp_follow_pv',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '公众号关注次数成本',
+                dataIndex: 'mp_follow_pv_cost',
+                key: 'mp_follow_pv_cost',
+                align: 'center',
+                width: 100,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+
+            {
+                title: '快应用添加次数',
+                dataIndex: 'add_quick_app_pv',
+                key: 'add_quick_app_pv',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '快应用添加成本',
+                dataIndex: 'add_quick_app_cost',
+                key: 'add_quick_app_cost',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '快应用添加率',
+                dataIndex: 'add_quick_app_rate',
+                key: 'add_quick_app_rate',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a ? (a * 100).toFixed(2) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                }
+            },
+            {
+                title: '加企业微信客服人数',
+                dataIndex: 'scan_follow_uv',
+                key: 'scan_follow_uv',
+                align: 'center',
+                width: 100,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '加企业微信客服成本',
+                dataIndex: 'scan_follow_cost',
+                key: 'scan_follow_cost',
+                align: 'center',
+                width: 100,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '加企业微信客服率',
+                dataIndex: 'scan_follow_rate',
+                key: 'scan_follow_rate',
+                align: 'center',
+                width: 100,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a ? (a * 100).toFixed(2) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                }
+            },
+
+
+            {
+                title: '首日新增下单量',
+                dataIndex: 'first_day_order_count',
+                key: 'first_day_order_count',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '首日新增下单金额',
+                dataIndex: 'first_day_order_amount',
+                key: 'first_day_order_amount',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '首日新增下单ROI',
+                dataIndex: 'first_day_order_roi',
+                key: 'first_day_order_roi',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a ? (a * 100).toFixed(2) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                }
+            },
+            {
+                title: '订单量',
+                dataIndex: 'order_count',
+                key: 'order_count',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '订单金额',
+                dataIndex: 'order_amount',
+                key: 'order_amount',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '下单成本',
+                dataIndex: 'order_cost',
+                key: 'order_cost',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '下单率',
+                dataIndex: 'order_rate',
+                key: 'order_rate',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a ? (a * 100).toFixed(2) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                }
+            },
+            {
+                title: '下单ROI',
+                dataIndex: 'order_roi',
+                key: 'order_roi',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a ? (a * 100).toFixed(2) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                }
+            },
+            {
+                title: '客单价',
+                dataIndex: 'atv',
+                key: 'atv',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '转化量',
+                dataIndex: 'conversions_count',
+                key: 'conversions_count',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '转化成本',
+                dataIndex: 'conversions_cost',
+                key: 'conversions_cost',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '深度转化',
+                dataIndex: 'deep_conversions_count',
+                key: 'deep_conversions_count',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '转化率',
+                dataIndex: 'conversions_rate',
+                key: 'conversions_rate',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a ? (a * 100).toFixed(2) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                }
+            },
+            {
+                title: '加粉数',
+                dataIndex: 'add_fans_count',
+                key: 'add_fans_count',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+            {
+                title: '加粉成本',
+                dataIndex: 'add_fans_cost',
+                key: 'add_fans_cost',
+                align: 'center',
+                width: 90,
+                render: (a: any, b: any) => {
+                    return <Statistic value={a || 0} />
+                }
+            },
+        ]
+        return newArr
+    }
+
+}
+export {
+    columnsMonitor,
+    columnsList
+}

+ 768 - 0
src/pages/launchSystemNew/adMonitorListV3/tablePlanListConfig.tsx

@@ -0,0 +1,768 @@
+import { AdStatusEnum, BidModeEnum, BidStrategyEnum, OptimizationGoalEnum, PromotedObjectType } from '@/services/launchAdq/enum'
+import React from 'react'
+import { Badge, Dropdown, Menu, Popover, Space, Statistic } from 'antd'
+import { ReactComponent as RocketSvg } from '@/assets/rocket.svg'
+import '../adq/index.less'
+import { copy } from '@/utils/utils'
+import { ColumnsType } from 'antd/lib/table'
+import StatisticNull from '@/components/StatisticNull'
+import { DownOutlined } from '@ant-design/icons'
+import SwitchStatus from '../adqv3/ad/switchStatus'
+import TimeSeriesLook from '../adq/ad/timeSeriesLook'
+import { ADGROUP_STATUS } from '../adqv3/const'
+function tablePlanConfig(
+    onChange: () => void,
+    details: (data: any) => void,
+    log: (data: any) => void,
+    handleTag: (data: any) => void,
+    delTag: (data: any) => void,
+): ColumnsType<any> {
+
+    let adArr: ColumnsType<any> = [
+        {
+            title: '启停',
+            dataIndex: 'configured_status',
+            key: 'configured_status',
+            align: 'center',
+            width: 40,
+            fixed: 'left',
+            render: (a: string, b: any) => {
+                if (b?.account_id === '总计') {
+                    return '--'
+                }
+                return <SwitchStatus configuredStatus={a} isDeleted={b?.is_deleted} adgroupId={b?.adgroup_id} accountId={b?.account_id} onChange={onChange} />
+            }
+        },
+        {
+            title: '所属账号',
+            dataIndex: 'account_id',
+            key: 'account_id',
+            align: 'center',
+            width: 80,
+            ellipsis: true,
+            render: (a: string) => {
+                return <Space>
+                    <a onClick={() => copy(a)} >{a}</a>
+                </Space>
+            }
+        },
+        {
+            title: '腾讯备注',
+            dataIndex: 'memo',
+            key: 'memo',
+            align: 'center',
+            width: 80,
+            ellipsis: true,
+            render(value, record) {
+                if (record?.account_id === '总计') {
+                    return '--'
+                }
+                return value
+            },
+        },
+        {
+            title: '本地备注',
+            dataIndex: 'remark',
+            key: 'remark',
+            align: 'center',
+            width: 80,
+            ellipsis: true,
+            render(value, record) {
+                if (record?.account_id === '总计') {
+                    return '--'
+                }
+                return value
+            },
+        },
+        {
+            title: '广告ID',
+            dataIndex: 'adgroup_id',
+            key: 'adgroup_id',
+            align: 'center',
+            width: 100,
+            ellipsis: true,
+            render: (a: string, b: any) => {
+                if (b?.account_id === '总计') {
+                    return '--'
+                }
+                return <Space>
+                    <a onClick={() => copy(a)} >{a}</a>
+                </Space>
+            }
+        },
+        {
+            title: '投手',
+            dataIndex: 'put_user_name',
+            key: 'put_user_name',
+            align: 'center',
+            width: 70,
+            ellipsis: true,
+            render(value, record) {
+                if (record?.account_id === '总计') return '--';
+                return value
+            },
+        },
+        {
+            title: '广告名称',
+            dataIndex: 'adgroup_name',
+            key: 'adgroup_name',
+            width: 280,
+            ellipsis: true,
+            render: (a: string, b: any) => {
+                if (b?.account_id === '总计') return '--';
+                return a
+            }
+        },
+        {
+            title: '投放日期',
+            dataIndex: 'begin_date',
+            key: 'begin_date',
+            align: 'center',
+            width: 150,
+            ellipsis: true,
+            sorter: true,
+            render: (a: string, b: { end_date: string, account_id: any }) => {
+                if (b?.account_id === '总计') return '--';
+                return b?.end_date && b?.end_date !== '1970-01-01' ? a + '~' + b.end_date : a + '~' + '长期投放'
+            }
+        },
+        {
+            title: '投放时间',
+            dataIndex: 'time_series',
+            key: 'time_series',
+            align: 'center',
+            width: 55,
+            render: (a: string, b: { endDate: string, account_id: string }) => {
+                if (b?.account_id === '总计') return '--';
+                return <TimeSeriesLook timeSeries={a} />
+            }
+        },
+        {
+            title: '首日开始投放时间',
+            dataIndex: 'first_day_begin_time',
+            key: 'first_day_begin_time',
+            align: 'center',
+            width: 70,
+            render(value, record) {
+                if (record?.account_id === '总计') return '--';
+                return value
+            },
+        },
+        {
+            title: '出价',
+            dataIndex: 'bid_amount',
+            key: 'bid_amount',
+            width: 140,
+            ellipsis: true,
+            sorter: true,
+            render: (a: string, b: { bid_mode: string, optimization_goal: string, account_id: any }) => {
+                if (b?.account_id === '总计') return '--';
+                return `${b?.bid_mode ? BidModeEnum[b?.bid_mode] : ''} ${a}元/${b?.bid_mode === 'BID_MODE_CPM' ? '千次曝光' : b?.bid_mode === 'BID_MODE_CPC' ? '点击' : OptimizationGoalEnum[b?.optimization_goal]}`
+            }
+        },
+        {
+            title: '深度优化行为出价',
+            dataIndex: 'deep_conversion_behavior_bid',
+            key: 'deep_conversion_behavior_bid',
+            width: 70,
+            align: 'center',
+            render: (a: string, b: { deep_conversion_spec_json: any, account_id: any }) => {
+                if (b?.account_id === '总计') return '--';
+                if (b?.deep_conversion_spec_json) {
+                    return a
+                } else {
+                    return '--'
+                }
+
+            }
+        },
+        {
+            title: '出价类型',
+            dataIndex: 'smart_bid_type',
+            key: 'smart_bid_type',
+            align: 'center',
+            width: 80,
+            ellipsis: true,
+            render: (a: string, b) => {
+                if (b?.account_id === '总计') return '--';
+                return a === 'SMART_BID_TYPE_CUSTOM' ? '手动出价' : '自动出价'
+            }
+        },
+        {
+            title: '出价策略',
+            dataIndex: 'bid_strategy',
+            key: 'bid_strategy',
+            align: 'center',
+            width: 70,
+            ellipsis: true,
+            render: (a: string, b) => {
+                if (b?.account_id === '总计') return '--';
+                return BidStrategyEnum[a]
+            }
+        },
+        {
+            title: '广告组日预算(元)',
+            dataIndex: 'daily_budget',
+            key: 'daily_budget',
+            align: 'center',
+            width: 70,
+            sorter: true,
+            render: (a: string, b: any) => {
+                if (b?.account_id === '总计') return '--';
+                return a
+            }
+        },
+        {
+            title: '是否开启自动版位功能',
+            dataIndex: 'automatic_site_enabled',
+            key: 'automatic_site_enabled',
+            align: 'center',
+            width: 80,
+            render: (a: any, b: any) => {
+                if (b?.account_id === '总计') return '--';
+                return a ? '开' : '关'
+            }
+        },
+        {
+            title: '定向条件描述',
+            dataIndex: 'targeting_translation',
+            key: 'targeting_translation',
+            align: 'center',
+            width: 80,
+            ellipsis: true,
+            render: (a: any) => {
+                return a || '--'
+            }
+        },
+        {
+            title: '创建时间',
+            dataIndex: 'created_time',
+            key: 'created_time',
+            align: 'center',
+            width: 140,
+            ellipsis: true,
+            render(value, record) {
+                if (record?.account_id === '总计') return '--';
+                return value
+            },
+        },
+        {
+            title: '是否已删除',
+            dataIndex: 'is_deleted',
+            key: 'is_deleted',
+            align: 'center',
+            width: 60,
+            render: (a: any, b: any) => {
+                if (b?.account_id === '总计') return '--';
+                return <Badge status={!a ? "processing" : "error"} text={a ? '是' : '否'} />
+            }
+        },
+        {
+            title: '广告状态',
+            dataIndex: 'system_status',
+            key: 'system_status',
+            align: 'center',
+            width: 70,
+            ellipsis: true,
+            render: (a: string) => {
+                return ADGROUP_STATUS[a]
+            }
+        },
+        {
+            title: '标记备注',
+            dataIndex: 'tag_remark',
+            key: 'tag_remark',
+            align: 'center',
+            width: 100,
+            ellipsis: true,
+            render(value, b) {
+                if (b?.account_id === '总计') return '--';
+                return value || '--'
+            },
+        },
+        {
+            title: '广告详情',
+            dataIndex: 'cost_speed',
+            key: 'cost_speed',
+            align: 'center',
+            width: 80,
+            className: 'padding2',
+            render: (a: any, b: any) => {
+                if (b?.account_id === '总计') return '--';
+                return <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', lineHeight: 'normal', fontSize: 14 }}>
+                    <RocketSvg /> <a onClick={() => details(b)} style={{ marginLeft: 10 }}>详情</a>
+                </div>
+            }
+        },
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            width: 130,
+            align: 'center',
+            render: (a: any, b: any) => {
+                if (b?.account_id === '总计') return '--';
+                return <Space>
+                    <Dropdown overlay={<Menu>
+                        <Menu.Item><a onClick={() => log(b)}>告警日志</a></Menu.Item>
+                        <Menu.Item><a onClick={() => handleTag(b)}>打标记</a></Menu.Item>
+                        {b?.tag_value ? <Menu.Item><a style={{ color: 'red' }} onClick={() => delTag(b)}>删除标记</a></Menu.Item> : undefined}
+                    </Menu>}>
+                        <a><Space size={2}>更多 <DownOutlined /></Space></a>
+                    </Dropdown>
+                    <a style={{ color: '#1890ff' }} onClick={() => window.open(`https://ad.qq.com/atlas/${b?.account_id}/admanage/index?tab=adgroup&query={%22operation_status%22:[%22CALCULATE_STATUS_EXCLUDE_DEL%22],%22system_status%22:[],%22search_name%22:%22${b.adgroup_id}%22}`)} target="_blank">腾讯广告</a>
+                </Space>
+            }
+        }
+    ]
+
+    let adDataArr: ColumnsType<any> = [
+        {
+            title: '消耗',
+            dataIndex: 'cost_total',
+            key: 'cost_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='cost_total' />
+            }
+        },
+        {
+            title: '曝光量',
+            dataIndex: 'view_total',
+            key: 'view_total',
+            align: 'center',
+            width: 100,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='view_total' />
+            }
+        },
+        {
+            title: '千次曝光成本',
+            dataIndex: 'thousand_display_price_total',
+            key: 'thousand_display_price_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='thousand_display_price_total' precision={2} />
+            }
+        },
+        {
+            title: '点击量',
+            dataIndex: 'click_total',
+            key: 'click_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='click_total' />
+            }
+        },
+        {
+            title: '点击率',
+            dataIndex: 'ctr_total',
+            key: 'ctr_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                if (b?.ctr_total !== undefined && b?.ctr_total !== null) {
+                    return <Statistic value={a ? a.toFixed(2) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                } else {
+                    return '--'
+                }
+            }
+        },
+        {
+            title: '点击均价',
+            dataIndex: 'cpc_total',
+            key: 'cpc_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='cpc_total' />
+            }
+        },
+        {
+            title: '不感兴趣点击次数',
+            dataIndex: 'no_interest_count_total',
+            key: 'no_interest_count_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='no_interest_count_total' />
+            }
+        },
+        {
+            title: '朋友圈视频播放次数',
+            dataIndex: 'video_play_count_total',
+            key: 'video_play_count_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='video_play_count_total' />
+            }
+        },
+        {
+            title: '下载次数',
+            dataIndex: 'download_count_total',
+            key: 'download_count_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='download_count_total' />
+            }
+        },
+        {
+            title: '安装次数',
+            dataIndex: 'install_count_total',
+            key: 'install_count_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='install_count_total' />
+            }
+        },
+        {
+            title: '激活次数',
+            dataIndex: 'activated_count_total',
+            key: 'activated_count_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='activated_count_total' />
+            }
+        },
+        {
+            title: '公众号关注人数',
+            dataIndex: 'mp_follow_uv_total',
+            key: 'mp_follow_uv_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='mp_follow_uv_total' />
+            }
+        },
+        {
+            title: '公众号关注成本',
+            dataIndex: 'mp_follow_cost_total',
+            key: 'mp_follow_cost_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='mp_follow_cost_total' />
+            }
+        },
+        {
+            title: '公众号关注率',
+            dataIndex: 'mp_follow_rate_total',
+            key: 'mp_follow_rate_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                if (b?.mp_follow_rate_total !== undefined && b?.mp_follow_rate_total !== null) {
+                    return <Statistic value={a ? a.toFixed(2) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                } else {
+                    return '--'
+                }
+            }
+        },
+        {
+            title: '公众号关注次数',
+            dataIndex: 'mp_follow_pv_total',
+            key: 'mp_follow_pv_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='mp_follow_pv_total' />
+            }
+        },
+        {
+            title: '公众号关注次数成本',
+            dataIndex: 'mp_follow_pv_cost_total',
+            key: 'mp_follow_pv_cost_total',
+            align: 'center',
+            width: 120,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='mp_follow_pv_cost_total' precision={2}/>
+            }
+        },
+        {
+            title: '快应用添加次数',
+            dataIndex: 'add_quick_app_pv_total',
+            key: 'add_quick_app_pv_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='add_quick_app_pv_total' />
+            }
+        },
+        {
+            title: '快应用添加成本',
+            dataIndex: 'add_quick_app_cost_total',
+            key: 'add_quick_app_cost_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='add_quick_app_cost_total' />
+            }
+        },
+        {
+            title: '快应用添加率',
+            dataIndex: 'add_quick_app_rate_total',
+            key: 'add_quick_app_rate_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                if (b?.add_quick_app_rate_total !== undefined && b?.add_quick_app_rate_total !== null) {
+                    return <Statistic value={a ? a.toFixed(2) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                } else {
+                    return '--'
+                }
+            }
+        },
+        {
+            title: '加企业微信客服人数',
+            dataIndex: 'scan_follow_uv_total',
+            key: 'scan_follow_uv_total',
+            align: 'center',
+            width: 120,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='scan_follow_uv_total' />
+            }
+        },
+        {
+            title: '加企业微信客服成本',
+            dataIndex: 'scan_follow_cost_total',
+            key: 'scan_follow_cost_total',
+            align: 'center',
+            width: 120,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='scan_follow_cost_total' precision={2} />
+            }
+        },
+        {
+            title: '加企业微信客服率',
+            dataIndex: 'scan_follow_rate_total',
+            key: 'scan_follow_rate_total',
+            align: 'center',
+            width: 120,
+            sorter: true,
+            render: (a: any, b: any) => {
+                if (b?.scan_follow_rate_total !== undefined && b?.scan_follow_rate_total !== null) {
+                    return <Statistic value={a ? a.toFixed(2) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                } else {
+                    return '--'
+                }
+            }
+        },
+
+
+        {
+            title: '首日新增下单量',
+            dataIndex: 'first_day_order_count_total',
+            key: 'first_day_order_count_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='first_day_order_count_total' />
+            }
+        },
+        {
+            title: '首日新增下单金额',
+            dataIndex: 'first_day_order_amount_total',
+            key: 'first_day_order_amount_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='first_day_order_amount_total' />
+            }
+        },
+        {
+            title: '首日新增下单ROI',
+            dataIndex: 'first_day_order_roi_total',
+            key: 'first_day_order_roi_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                if (b?.first_day_order_roi_total !== undefined && b?.first_day_order_roi_total !== null) {
+                    return <Statistic value={a ? a.toFixed(2) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                } else {
+                    return '--'
+                }
+            }
+        },
+        {
+            title: '订单量',
+            dataIndex: 'order_count_total',
+            key: 'order_count_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='order_count_total' />
+            }
+        },
+        {
+            title: '订单金额',
+            dataIndex: 'order_amount_total',
+            key: 'order_amount_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='order_amount_total' />
+            }
+        },
+        {
+            title: '下单成本',
+            dataIndex: 'order_cost_total',
+            key: 'order_cost_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='order_cost_total' precision={2} />
+            }
+        },
+        {
+            title: '下单率',
+            dataIndex: 'order_rate_total',
+            key: 'order_rate_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                if (b?.order_rate_total !== undefined && b?.order_rate_total !== null) {
+                    return <Statistic value={a ? a.toFixed(2) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                } else {
+                    return '--'
+                }
+            }
+        },
+        {
+            title: '下单ROI',
+            dataIndex: 'order_roi_total',
+            key: 'order_roi_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                if (b?.order_roi_total !== undefined && b?.order_roi_total !== null) {
+                    return <Statistic value={a ? a.toFixed(2) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                } else {
+                    return '--'
+                }
+            }
+        },
+        {
+            title: '客单价',
+            dataIndex: 'atv_total',
+            key: 'atv_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='atv_total' precision={2} />
+            }
+        },
+        {
+            title: '转化量',
+            dataIndex: 'conversions_count_total',
+            key: 'conversions_count_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='conversions_count_total' />
+            }
+        },
+        {
+            title: '转化成本',
+            dataIndex: 'conversions_cost_total',
+            key: 'conversions_cost_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='conversions_cost_total' precision={2} />
+            }
+        },
+        {
+            title: '深度转化',
+            dataIndex: 'deep_conversions_count_total',
+            key: 'deep_conversions_count_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='deep_conversions_count_total' />
+            }
+        },
+        {
+            title: '转化率',
+            dataIndex: 'conversions_rate_total',
+            key: 'conversions_rate_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                if (b?.conversions_rate_total !== undefined && b?.conversions_rate_total !== null) {
+                    return <Statistic value={a ? a.toFixed(2) : 0} precision={2} valueStyle={{ color: '#3f8600' }} suffix="%" />
+                } else {
+                    return '--'
+                }
+            }
+        },
+        {
+            title: '加粉数',
+            dataIndex: 'add_fans_count_total',
+            key: 'add_fans_count_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='add_fans_count_total' />
+            }
+        },
+        {
+            title: '加粉成本',
+            dataIndex: 'add_fans_cost_total',
+            key: 'add_fans_cost_total',
+            align: 'center',
+            width: 110,
+            sorter: true,
+            render: (a: any, b: any) => {
+                return <StatisticNull data={b} field='add_fans_cost_total' precision={2} />
+            }
+        },
+    ]
+
+    return [
+        ...adArr,
+        ...adDataArr
+    ]
+}
+export default tablePlanConfig

+ 1 - 1
src/pages/launchSystemNew/adq/ad/FilterQuery.tsx

@@ -469,7 +469,7 @@ const FilterQuery: React.FC<Props> = ({ onChange, initialValues, queryForm, setQ
                     case 'promotedObjectType':
                         value = PromotedObjectType[value] || '请选择'
                         break
-                    case 'adCreateTime':
+                    case 'adCreateTime': case 'putDate':
                         value = value ? '已经选择时间' : '请选择时间'
                         break
                     default:

+ 4 - 4
src/pages/launchSystemNew/adq/ad/adPlanList.tsx

@@ -72,7 +72,7 @@ const AdPlanList: React.FC<{ userId: string }> = (props) => {
     }, [filterForm, queryForm])
 
     const getList = () => {
-        let message = localStorage.getItem(`myAdMonitorConfig1.0.0_${configName}`)
+        let message = localStorage.getItem(`myAdMonitorConfig1.0.1_${configName}`)
         if (message) {
             message = JSON.parse(message)
         }
@@ -104,7 +104,7 @@ const AdPlanList: React.FC<{ userId: string }> = (props) => {
     }, [getAdList.data])
 
     useEffect(() => {
-        let localData = localStorage.getItem('myAdMonitorConfig1.0.0_广告列表New')
+        let localData = localStorage.getItem('myAdMonitorConfig1.0.1_广告列表New')
         let data: any[] = []
         if (localData) {
             data = JSON.parse(localData)
@@ -122,7 +122,7 @@ const AdPlanList: React.FC<{ userId: string }> = (props) => {
         data.unshift({ title: '选择框', dataIndex: 'xzk' })
         data.unshift({ title: '总计', dataIndex: 'zj' })
         setTableField(data)
-    }, [localStorage.getItem('myAdMonitorConfig1.0.0_广告列表New')])
+    }, [localStorage.getItem('myAdMonitorConfig1.0.1_广告列表New')])
 
     useEffect(() => {
         getPutUser.run({ userId })
@@ -306,7 +306,7 @@ const AdPlanList: React.FC<{ userId: string }> = (props) => {
     }
 
     const handleColumnTrend = (value: string) => {
-        let message = localStorage.getItem(`myAdMonitorConfig1.0.0_${configName}`)
+        let message = localStorage.getItem(`myAdMonitorConfig1.0.1_${configName}`)
         if (message) {
             message = JSON.parse(message)
         }

+ 5 - 5
src/pages/launchSystemNew/adq/ad/index.tsx

@@ -80,9 +80,9 @@ const Ad: React.FC<Props> = (props) => {
     const getPutUser = useAjax((params) => getPutUserApi(params))
     /************************/
 
-    useEffect(() => {
-        getPutUser.run({ userId })
-    }, [userId])
+    // useEffect(() => {
+    //     getPutUser.run({ userId })
+    // }, [userId])
 
     useEffect(() => {
         // let { accountId, campaignId, adgroupId, ...obj } = queryParmas
@@ -444,7 +444,7 @@ const Ad: React.FC<Props> = (props) => {
                     {Object.keys(OptimizationGoalEnum).map(key => <Select.Option value={key} key={key}>{OptimizationGoalEnum[key]}</Select.Option>)}
                 </Select>
             </Col>
-            <Col>
+            {/* <Col>
                 <Select
                     placeholder='投手'
                     mode='multiple'
@@ -462,7 +462,7 @@ const Ad: React.FC<Props> = (props) => {
                 >
                     {getPutUser?.data ? Object.keys(getPutUser?.data).map(key => <Select.Option value={key} key={key}>{getPutUser?.data[key]}</Select.Option>) : null}
                 </Select>
-            </Col>
+            </Col> */}
             <Col>
                 <Space>
                     <Button

+ 3 - 3
src/pages/launchSystemNew/adq/ad/tablePlanListConfig.tsx

@@ -299,13 +299,13 @@ function tablePlanConfig(
         },
         {
             title: '创意预览',
-            dataIndex: 'creative_id',
-            key: 'creative_id',
+            dataIndex: 'creative_ids',
+            key: 'creative_ids',
             width: 70,
             align: 'center',
             render: (a: any, b: any) => {
                 if (b?.account_id === '总计') return '--';
-                return <Box b={b?.creative_preivew} />
+                return <Box b={b?.creative_preview} />
             }
         },
         {

+ 1 - 1
src/pages/launchSystemNew/adq/config.ts

@@ -55,7 +55,7 @@ const planAdConfig = [
             { title: '创建时间', dataIndex: 'created_time', serverIndex: 'adgroups.created_time', label: '广告详情', default: 19, width: 140 },
             { title: '是否已删除', dataIndex: 'is_deleted', serverIndex: 'adgroups.is_deleted', label: '广告详情', default: 20, width: 60 },
             { title: '广告状态', dataIndex: 'status', serverIndex: 'adgroups.status', label: '广告详情', default: 21, width: 70 },
-            { title: '创意预览', dataIndex: 'creative_id', serverIndex: 'adgroup_data.creative_id', label: '广告详情', default: 22, width: 70 },
+            { title: '创意预览', dataIndex: 'creative_ids', serverIndex: 'adgroup_data.creative_ids', label: '广告详情', default: 22, width: 70 },
             { title: '广告详情', dataIndex: 'cost_speed', label: '广告详情', default: 23, width: 80 },
             { title: '标记备注', dataIndex: 'tag_remark', label: '广告详情', width: 80, serverIndex: 'adgroup_user_tag.tag_remark' },
             { title: '操作', dataIndex: 'cz', label: '广告详情', default: 24, width: 116 },

+ 292 - 0
src/pages/launchSystemNew/adqv3/ad/index.tsx

@@ -0,0 +1,292 @@
+import { useAjax } from "@/Hook/useAjax";
+import { PauseCircleOutlined, PlayCircleOutlined, QuestionCircleOutlined, TransactionOutlined } from "@ant-design/icons";
+import { Button, Checkbox, Col, Input, Row, Select, Space, Tooltip, message } from "antd"
+import React, { useCallback, useEffect, useState } from "react"
+import { ADGROUP_STATUS } from "../const";
+import { getAdqV3AdListApi, modifyStatusBatchApi, syncBatchApi } from "@/services/launchAdq/adqv3";
+import TableData from "../../components/TableData";
+import tableConfig from "./tableConfig";
+import { txAdConfig } from "../config";
+import UpdateAd from "./updateAd";
+
+
+const Ad: React.FC<ADQV3.AdProps> = ({ userId, creativeHandle }) => {
+
+    /*****************************************/
+    const [queryFrom, set_queryFrom] = useState<ADQV3.GetAdListProps>({ pageNum: 1, pageSize: 20, useType: 1 })
+    const [isClearSelect, setIsClearSelect] = useState(true)
+    const [selectedRows, setSelectedRows] = useState<any[]>([])
+    const [update, setUpdate] = useState<{ visible: boolean }>({ visible: false })
+
+    const syncBatch = useAjax((params) => syncBatchApi(params))
+    const modifyStatusBatch = useAjax((params) => modifyStatusBatchApi(params))
+    const getAdqV3AdList = useAjax((params) => getAdqV3AdListApi(params), { formatResult: true })
+    /*****************************************/
+
+    useEffect(() => {
+        getList({ pageNum: 1, pageSize: 20, useType: 1 })
+    }, [userId])
+
+    // 获取列表
+    const getList = useCallback((params: ADQV3.GetAdListProps) => {
+        getAdqV3AdList.run({ ...params, putUserIdList: [userId] })
+    }, [userId, getAdqV3AdList])
+
+    // 同步 
+    const sync = useCallback(() => {
+        if (selectedRows?.length > 0) {
+            let accountAdgroupMaps = [...new Set(selectedRows?.map(item => item.accountId + ',' + item.adgroupId))]
+            syncBatch.run({ accountAdgroupMaps }).then(res => {
+                res && getAdqV3AdList.refresh()
+                res ? message.success('同步成功!') : message.error('同步失败!')
+            })
+        } else {
+            message.error('请勾选')
+        }
+
+    }, [getAdqV3AdList, selectedRows])
+
+    // 批量启停
+    const adStatus = (type: boolean) => {
+        let newSelectedRows = []
+        if (type) {
+            newSelectedRows = selectedRows.filter((item: { configuredStatus: string, adgroupId: number }) => item.configuredStatus === 'AD_STATUS_SUSPEND')
+        } else {
+            newSelectedRows = selectedRows.filter((item: { configuredStatus: string, adgroupId: number }) => item.configuredStatus === 'AD_STATUS_NORMAL')
+        }
+        if (newSelectedRows.length === 0) {
+            message.warn(`所有广告都是${type ? '启动' : '暂停'}状态,无需${type ? '启动' : '暂停'}操作`)
+            return
+        }
+        let accountAdgroupMaps = [...new Set(newSelectedRows?.map(item => item.accountId + ',' + item.adgroupId))]
+        modifyStatusBatch.run({ accountAdgroupMaps, suspend: type }).then(res => {
+            message.success(`${type ? '启动' : '暂停'}成功`)
+            getAdqV3AdList.refresh()
+            setSelectedRows([])
+        })
+    }
+
+
+    return <div>
+        <Row gutter={[6, 6]} align='middle' style={{ marginBottom: 15 }}>
+            <Col>
+                <Select
+                    placeholder='应用类型'
+                    style={{ width: 90 }}
+                    showSearch
+                    filterOption={(input: any, option: any) =>
+                        (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
+                    }
+                    value={queryFrom.useType}
+                    onChange={(value: any) => {
+                        set_queryFrom({ ...queryFrom, useType: value })
+                    }}
+                >
+                    <Select.Option value={1}>小说</Select.Option>
+                    <Select.Option value={2}>游戏</Select.Option>
+                </Select>
+            </Col>
+            <Col>
+                <Input
+                    placeholder='广告账号(多个,分割)'
+                    allowClear
+                    style={{ width: 160 }}
+                    onChange={(e) => {
+                        let value = e.target.value
+                        let arr: any = []
+                        if (value) {
+                            value = value.replace(/[,,\s]/g, ',')
+                            arr = value.split(',').filter((a: any) => a)
+                        }
+                        set_queryFrom({ ...queryFrom, accountIdList: arr })
+                    }}
+                />
+            </Col>
+            <Col>
+                <Input
+                    placeholder='广告ID(多个,分割)'
+                    allowClear
+                    style={{ width: 150 }}
+                    onChange={(e) => {
+                        let value = e.target.value
+                        let arr: any = []
+                        if (value) {
+                            value = value.replace(/[,,\s]/g, ',')
+                            arr = value.split(',').filter((a: any) => a)
+                        }
+                        set_queryFrom({ ...queryFrom, adgroupIdList: arr })
+                    }}
+                />
+            </Col>
+            <Col>
+                <Select
+                    placeholder='广告状态'
+                    mode="multiple"
+                    style={{ minWidth: 120 }}
+                    showSearch
+                    filterOption={(input: any, option: any) =>
+                        (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
+                    }
+                    allowClear
+                    onChange={(value: any) => {
+                        set_queryFrom({ ...queryFrom, systemStatusList: value })
+                    }}
+                >
+                    {Object.keys(ADGROUP_STATUS).map(key => {
+                        return <Select.Option value={key} key={key}>{ADGROUP_STATUS[key]}</Select.Option>
+                    })}
+                </Select>
+            </Col>
+            <Col>
+                <Input
+                    placeholder='广告名称'
+                    allowClear
+                    style={{ width: 120 }}
+                    onChange={(e) => {
+                        let value = e.target.value
+                        set_queryFrom({ ...queryFrom, adgroupName: value })
+                    }}
+                />
+            </Col>
+            <Col>
+                <Input
+                    placeholder='腾讯备注'
+                    allowClear
+                    style={{ width: 120 }}
+                    onChange={(e) => {
+                        let value = e.target.value
+                        let arr: any = []
+                        if (value) {
+                            value = value.replace(/[,,\s]/g, ',')
+                            arr = value.split(',').filter((a: any) => a)
+                        }
+                        set_queryFrom({ ...queryFrom, accountMemo: arr })
+                    }}
+                />
+            </Col>
+            <Col>
+                <Input
+                    placeholder='本地备注'
+                    allowClear
+                    style={{ width: 120 }}
+                    onChange={(e) => {
+                        let value = e.target.value
+                        let arr: any = []
+                        if (value) {
+                            value = value.replace(/[,,\s]/g, ',')
+                            arr = value.split(',').filter((a: any) => a)
+                        }
+                        set_queryFrom({ ...queryFrom, accountRemark: arr })
+                    }}
+                />
+            </Col>
+            <Col>
+                <Space>
+                    <Button
+                        type="primary"
+                        onClick={() => {
+                            if (isClearSelect) {
+                                setSelectedRows([])
+                            }
+                            getList({ ...queryFrom, pageNum: 1 })
+                        }}
+                    >
+                        <Space>
+                            <span>搜索</span>
+                            <Checkbox className='clearCheckbox' onClick={(e) => e.stopPropagation()} checked={isClearSelect} onChange={(e) => setIsClearSelect(e.target.checked)} />
+                            <Tooltip title="勾选搜索清空已选">
+                                <QuestionCircleOutlined />
+                            </Tooltip>
+                        </Space>
+                    </Button>
+
+                    {selectedRows?.length > 0 && <Button type='link' style={{ padding: 0, color: 'red' }} onClick={() => {
+                        setSelectedRows([])
+                    }}>清空已选({selectedRows?.length})</Button>}
+                </Space>
+            </Col>
+        </Row>
+        <TableData
+            isCard={false}
+            columns={() => tableConfig(() => getAdqV3AdList.refresh(), creativeHandle)}
+            ajax={getAdqV3AdList}
+            syncAjax={sync}
+            fixed={{ left: 2, right: 4 }}
+            dataSource={getAdqV3AdList?.data?.data?.records}
+            loading={getAdqV3AdList?.loading || syncBatch?.loading}
+            scroll={{ y: 560 }}
+            total={getAdqV3AdList?.data?.data?.total}
+            page={getAdqV3AdList?.data?.data?.current}
+            pageSize={getAdqV3AdList?.data?.data?.size}
+            myKey={'adgroupId'}
+            gutter={[0, 10]}
+            config={txAdConfig}
+            configName="腾讯广告3.0"
+            leftChild={<Space direction='vertical'>
+                <Row gutter={[10, 10]} align='middle'>
+                    <Col><Button type='primary' style={{ background: '#67c23a', borderColor: '#67c23a' }} loading={modifyStatusBatch.loading} icon={<PlayCircleOutlined />} disabled={selectedRows.length === 0} onClick={() => adStatus(true)}>启动</Button></Col>
+                    <Col><Button type='primary' style={{ background: '#e6a23c', borderColor: '#e6a23c' }} loading={modifyStatusBatch.loading} icon={<PauseCircleOutlined />} disabled={selectedRows.length === 0} onClick={() => adStatus(false)}>暂停</Button></Col>
+                    <Col><Button type='primary' icon={<TransactionOutlined />} disabled={selectedRows.length === 0} onClick={() => setUpdate({ visible: true })}>修改出价</Button></Col>
+                </Row>
+            </Space>}
+            rowSelection={{
+                selectedRowKeys: selectedRows.map(item => item.adgroupId.toString()),
+                getCheckboxProps: (record: any) => ({
+                    disabled: record.isDeleted
+                }),
+                onSelect: (record: { adgroupId: number, mpName: string }, selected: boolean) => {
+                    if (selected) {
+                        selectedRows.push({ ...record })
+                        setSelectedRows([...selectedRows])
+                    } else {
+                        let newSelectAccData = selectedRows.filter((item: { adgroupId: number }) => item.adgroupId !== record.adgroupId)
+                        setSelectedRows([...newSelectAccData])
+                    }
+                },
+                onSelectAll: (selected: boolean, selectedRowss: { adgroupId: number }[], changeRows: { adgroupId: number }[]) => {
+                    if (selected) {
+                        let newSelectAccData = [...selectedRows]
+                        changeRows.forEach((item: { adgroupId: number }) => {
+                            let index = newSelectAccData.findIndex((ite: { adgroupId: number }) => ite.adgroupId === item.adgroupId)
+                            if (index === -1) {
+                                newSelectAccData.push({ ...item })
+                            }
+                        })
+                        setSelectedRows([...newSelectAccData])
+                    } else {
+                        let newSelectAccData = selectedRows.filter((item: { adgroupId: number }) => {
+                            let index = changeRows.findIndex((ite: { adgroupId: number }) => ite.adgroupId === item.adgroupId)
+                            if (index !== -1) {
+                                return false
+                            } else {
+                                return true
+                            }
+                        })
+                        setSelectedRows([...newSelectAccData])
+                    }
+                }
+            }}
+            onChange={(props: any) => {
+                let { pagination } = props
+                let { current, pageSize } = pagination
+                set_queryFrom({ ...queryFrom, pageNum: current, pageSize })
+                getList({ ...queryFrom, pageNum: current, pageSize })
+            }}
+        />
+
+        {/* 修改广告 */}
+        {update.visible && <UpdateAd
+            {...update}
+            selectedRows={selectedRows}
+            onChange={() => {
+                setUpdate({ visible: false })
+                getAdqV3AdList.refresh()
+                setSelectedRows([])
+            }}
+            onClose={() => { setUpdate({ visible: false }) }}
+        />}
+    </div>
+}
+
+
+export default React.memo(Ad)

+ 35 - 0
src/pages/launchSystemNew/adqv3/ad/switchStatus.tsx

@@ -0,0 +1,35 @@
+import { useAjax } from "@/Hook/useAjax"
+import { newEditAdqAdgroupsDataApi } from "@/services/launchAdq/adq"
+import { modifyStatusBatchApi } from "@/services/launchAdq/adqv3"
+import { message, notification, Switch } from "antd"
+import React from "react"
+
+
+
+/**
+ * 修改启停
+ */
+interface Props {
+    configuredStatus: string,
+    isDeleted?: boolean,
+    accountId: number
+    adgroupId: number,
+    onChange?: () => void
+}
+const SwitchStatus: React.FC<Props> = (prosp) => {
+
+    const { configuredStatus, isDeleted, accountId, adgroupId, onChange } = prosp
+    const modifyStatusBatch = useAjax((params) => modifyStatusBatchApi(params))
+
+    const switchHandle = (accountAdgroupMaps: string[], suspend: boolean) => {
+        modifyStatusBatch.run({ accountAdgroupMaps, suspend }).then(res => {
+            message.success(`${suspend ? '启动' : '暂停'}成功`)
+            onChange?.()
+        })
+    }
+
+    return <Switch size="small" checked={configuredStatus === 'AD_STATUS_NORMAL'} loading={modifyStatusBatch.loading} disabled={isDeleted} onChange={(checked) => switchHandle([accountId + ',' + adgroupId], checked)} />
+}
+
+
+export default React.memo(SwitchStatus)

+ 243 - 0
src/pages/launchSystemNew/adqv3/ad/tableConfig.tsx

@@ -0,0 +1,243 @@
+import { BidModeEnum, BidStrategyEnum, OptimizationGoalEnum } from '@/services/launchAdq/enum'
+import React from 'react'
+import { Badge, Space } from 'antd'
+import '../index.less'
+import { copy } from '@/utils/utils'
+import TimeSeriesLook from '../../adq/ad/timeSeriesLook'
+import { ADGROUP_STATUS } from '../const'
+import SwitchStatus from './switchStatus'
+function tableConfig(onChange: () => void, creativeHandle?: (id: number) => void): any {
+    return [
+        {
+            title: '启停',
+            dataIndex: 'configuredStatus',
+            key: 'configuredStatus',
+            align: 'center',
+            width: 40,
+            fixed: 'left',
+            render: (a: string, b: any) => {
+                return <SwitchStatus configuredStatus={a} accountId={b?.accountId} isDeleted={b?.isDeleted} adgroupId={b?.adgroupId} onChange={onChange}/>
+            }
+        },
+        {
+            title: '所属账号',
+            dataIndex: 'accountId',
+            key: 'accountId',
+            align: 'center',
+            width: 80,
+            ellipsis: true,
+            render: (a: string) => {
+                return <Space>
+                    <a onClick={() => copy(a)} >{a}</a>
+                </Space>
+            }
+        },
+        {
+            title: '腾讯备注',
+            dataIndex: 'memo',
+            key: 'memo',
+            align: 'center',
+            width: 80,
+            ellipsis: true,
+            render: (a: string) => {
+                return a || '--'
+            }
+        },
+        {
+            title: '本地备注',
+            dataIndex: 'remark',
+            key: 'remark',
+            align: 'center',
+            width: 80,
+            ellipsis: true,
+            render: (a: string) => {
+                return a || '--'
+            }
+        },
+        {
+            title: '广告ID',
+            dataIndex: 'adgroupId',
+            key: 'adgroupId',
+            align: 'center',
+            width: 100,
+            ellipsis: true,
+            render: (a: string, b: any) => {
+                return <Space>
+                    <a onClick={() => copy(a)} >{a}</a>
+                </Space>
+            }
+        },
+        {
+            title: '投手',
+            dataIndex: 'putUserName',
+            key: 'putUserName',
+            align: 'center',
+            width: 70,
+            ellipsis: true
+        },
+        {
+            title: '广告名称',
+            dataIndex: 'adgroupName',
+            key: 'adgroupName',
+            width: 280,
+            ellipsis: true,
+            render: (a: string, b: any) => {
+                return <a onClick={() => { creativeHandle?.(b?.adgroupId) }}>{a}</a>
+                // return <InputUpdate title={a} dataIndex={'adgroupName'} record={b} handleSave={handleSave} />
+            }
+        },
+        {
+            title: '投放日期',
+            dataIndex: 'beginDate',
+            key: 'beginDate',
+            align: 'center',
+            width: 150,
+            ellipsis: true,
+            render: (a: string, b: { endDate: string }) => {
+                return b?.endDate ? a + '~' + b.endDate : a + '~' + '长期投放'
+            }
+        },
+        {
+            title: '投放时间',
+            dataIndex: 'timeSeries',
+            key: 'timeSeries',
+            align: 'center',
+            width: 55,
+            render: (a: string, b: { endDate: string }) => {
+                return <TimeSeriesLook timeSeries={a} />
+            }
+        },
+        {
+            title: '首日开始投放时间',
+            dataIndex: 'firstDayBeginTime',
+            key: 'firstDayBeginTime',
+            align: 'center',
+            width: 70,
+        },
+        {
+            title: '出价',
+            dataIndex: 'bidAmount',
+            key: 'bidAmount',
+            width: 140,
+            ellipsis: true,
+            render: (a: string, b: { bidMode: string, optimizationGoal: string }) => {
+                return `${BidModeEnum[b?.bidMode]} ${a}元/${b?.bidMode === 'BID_MODE_CPM' ? '千次曝光' : b?.bidMode === 'BID_MODE_CPC' ? '点击' : OptimizationGoalEnum[b?.optimizationGoal]}`
+            }
+        },
+        {
+            title: '深度优化行为出价',
+            dataIndex: 'deepConversionBehaviorBid',
+            key: 'deepConversionBehaviorBid',
+            width: 140,
+            align: 'center',
+            render: (a: string) => {
+                return a || '--'
+            }
+        },
+        {
+            title: '出价类型',
+            dataIndex: 'smartBidType',
+            key: 'smartBidType',
+            align: 'center',
+            width: 80,
+            ellipsis: true,
+            render: (a: string, b: { endDate: string }) => {
+                return a === 'SMART_BID_TYPE_CUSTOM' ? '手动出价' : '自动出价'
+            }
+        },
+        {
+            title: '出价策略',
+            dataIndex: 'bidStrategy',
+            key: 'bidStrategy',
+            align: 'center',
+            width: 70,
+            ellipsis: true,
+            render: (a: string, b: { endDate: string }) => {
+                return BidStrategyEnum[a]
+            }
+        },
+        {
+            title: '广告组日预算(元)',
+            dataIndex: 'dailyBudget',
+            key: 'dailyBudget',
+            align: 'center',
+            width: 70,
+            // render: (a: string, b: any) => {
+            //     return <InputUpdate title={a} isNum={true} dataIndex={'dailyBudget'} record={b} handleSave={handleSaveDaily} />
+            // }
+        },
+        {
+            title: '是否开启自动版位功能',
+            dataIndex: 'automaticSiteEnabled',
+            key: 'automaticSiteEnabled',
+            align: 'center',
+            width: 80,
+            render: (a: any, b: any) => {
+                return a ? '开' : '关'
+            }
+        },
+        {
+            title: '定向条件描述',
+            dataIndex: 'targetingTranslation',
+            key: 'targetingTranslation',
+            align: 'center',
+            width: 80,
+            render: (a: any) => {
+                return a || '--'
+            }
+        },
+        {
+            title: '创建时间',
+            dataIndex: 'createdTime',
+            key: 'createdTime',
+            align: 'center',
+            width: 140,
+            ellipsis: true,
+        },
+        {
+            title: '是否已删除',
+            dataIndex: 'isDeleted',
+            key: 'isDeleted',
+            align: 'center',
+            width: 60,
+            render: (a: any, b: any) => {
+                return <Badge status={!a ? "processing" : "error"} text={a ? '是' : '否'} />
+            }
+        },
+        {
+            title: '广告状态',
+            dataIndex: 'systemStatus',
+            key: 'systemStatus',
+            align: 'center',
+            width: 90,
+            fixed: 'right',
+            ellipsis: true,
+            render: (a: string, b: any) => {
+                return ADGROUP_STATUS[a]
+            }
+        },
+        {
+            title: '创意预览',
+            dataIndex: 'dynamicCreativeList',
+            key: 'dynamicCreativeList',
+            width: 70,
+            align: 'center',
+            fixed: 'right',
+            render: (_: any, b: any) => {
+                return <a onClick={() => { creativeHandle?.(b?.adgroupId) }}>创意预览</a>
+            }
+        },
+        {
+            title: '操作',
+            dataIndex: 'cz',
+            key: 'cz',
+            width: 65,
+            align: 'center',
+            fixed: 'right',
+            render: (a: any, b: any) => {
+                return <a style={{ color: '#1890ff' }} onClick={() => window.open(`https://ad.qq.com/atlas/${b?.accountId}/admanage/index?tab=adgroup&query={%22operation_status%22:[%22CALCULATE_STATUS_EXCLUDE_DEL%22],%22system_status%22:[],%22search_name%22:%22${b.adgroupId}%22}`)} target="_blank">腾讯广告</a>
+            }
+        },
+    ]
+}
+export default tableConfig

+ 59 - 0
src/pages/launchSystemNew/adqv3/ad/updateAd.tsx

@@ -0,0 +1,59 @@
+import { Form, Input, message, Modal } from "antd"
+import React from "react"
+import { useAjax } from "@/Hook/useAjax";
+import { modifyAmountBatchApi } from "@/services/launchAdq/adqv3";
+
+interface Props {
+    visible?: boolean,
+    onChange?: () => void,
+    onClose?: () => void,
+    selectedRows: any[]
+}
+/**
+ * 修改广告
+ * @returns 
+ */
+const UpdateAd: React.FC<Props> = ({ visible, onChange, onClose, selectedRows }) => {
+
+    /***************************/
+    const [form] = Form.useForm();
+
+    const modifyAmountBatch = useAjax((params) => modifyAmountBatchApi(params))
+    /***************************/
+
+    const handleOk = () => {
+        form.validateFields().then(values => {
+            let accountAdgroupMaps = [...new Set(selectedRows?.map(item => item.accountId + ',' + item.adgroupId))]
+            modifyAmountBatch.run({ accountAdgroupMaps, ...values }).then(res => {
+                if (res) {
+                    message.success(`修改操作完成!`)
+                    onChange?.()
+                }
+            })
+        })
+    }
+
+    return <Modal
+        title={'修改出价'}
+        visible={visible}
+        onOk={handleOk}
+        onCancel={() => onClose && onClose()}
+        confirmLoading={modifyAmountBatch.loading}
+    >
+        <Form
+            form={form}
+            labelCol={{ span: 4 }}
+            className='ad_form_style'
+            colon={false}
+            labelAlign="left"
+            initialValues={{}}
+        >
+            <Form.Item label={<strong>出价</strong>} name='bidAmount' rules={[{ required: true, message: '请输入价格' }]}>
+                <Input placeholder={`输入价格 元`} style={{ width: 300 }} />
+            </Form.Item>
+        </Form>
+    </Modal>
+}
+
+
+export default React.memo(UpdateAd)

+ 52 - 0
src/pages/launchSystemNew/adqv3/config.ts

@@ -0,0 +1,52 @@
+/**广告 */
+const txAdConfig = [
+    {
+        label: '广告详情',
+        data: [
+            { title: '启停', dataIndex: 'configuredStatus', label: '广告详情', default: 1, width: 40 },
+            { title: '所属账号', dataIndex: 'accountId', label: '广告详情', default: 2, width: 75 },
+            { title: '腾讯备注', dataIndex: 'memo', label: '广告详情', default: 3, width: 80 },
+            { title: '本地备注', dataIndex: 'remark', label: '广告详情', default: 4, width: 80 },
+            { title: '广告ID', dataIndex: 'adgroupId', label: '广告详情', default: 5, width: 90 },
+            { title: '投手', dataIndex: 'putUserName', label: '广告详情', default: 6, width: 70 },
+            { title: '广告名称', dataIndex: 'adgroupName', label: '广告详情', default: 7, width: 280 },
+            { title: '投放日期', dataIndex: 'beginDate', label: '广告详情', default: 8, width: 150 },
+            { title: '投放时间', dataIndex: 'timeSeries', label: '广告详情', default: 9, width: 55 },
+            { title: '首日开始投放时间', dataIndex: 'firstDayBeginTime', label: '广告详情', default: 10, width: 70 },
+            { title: '出价', dataIndex: 'bidAmount', label: '广告详情', default: 11, width: 140 },
+            { title: '深度优化行为出价', dataIndex: 'deepConversionBehaviorBid', label: '广告详情', default: 12, width: 70 },
+            { title: '出价类型', dataIndex: 'smartBidType', label: '广告详情', default: 13, width: 80 },
+            { title: '出价策略', dataIndex: 'bidStrategy', label: '广告详情', default: 14, width: 80 },
+            { title: '广告组日预算(元)', dataIndex: 'dailyBudget', label: '广告详情', default: 15, width: 70 },
+            { title: '是否开启自动版位功能', dataIndex: 'automaticSiteEnabled', label: '广告详情', default: 16, width: 80 },
+            { title: '定向条件描述', dataIndex: 'targetingTranslation', label: '广告详情', default: 17, width: 80 },
+            { title: '创建时间', dataIndex: 'createdTime', label: '广告详情', default: 18, width: 140 },
+            { title: '是否已删除', dataIndex: 'isDeleted', label: '广告详情', default: 19, width: 60 },
+            { title: '广告状态', dataIndex: 'systemStatus', label: '广告详情', default: 20, width: 80 },
+            { title: '创意预览', dataIndex: 'dynamicCreativeList', label: '广告详情', default: 21, width: 70 },
+            { title: '操作', dataIndex: 'cz', label: '广告详情', default: 22, width: 65 },
+        ]
+    }
+]
+
+const txDynamicConfig = [
+    {
+        label: '创意详情',
+        data: [
+            { title: '启停', dataIndex: 'configuredStatus', label: '广告详情', default: 1, width: 40 },
+            { title: '创意预览', dataIndex: 'creativeComponents', label: '广告详情', default: 2, width: 130 },
+            { title: '所属账号', dataIndex: 'accountId', label: '广告详情', default: 3, width: 75 },
+            { title: '广告ID', dataIndex: 'adgroupId', label: '广告详情', default: 4, width: 90 },
+            { title: '创意名称', dataIndex: 'dynamicCreativeName', label: '广告详情', default: 5, width: 120 },
+            { title: '创意ID', dataIndex: 'dynamicCreativeId', label: '广告详情', default: 6, width: 60 },
+            { title: '投放模式', dataIndex: 'deliveryMode', label: '广告详情', default: 7, width: 100 },
+            { title: '创意形式匹配方式', dataIndex: 'dynamicCreativeType', label: '广告详情', default: 8, width: 100 },
+            { title: '状态', dataIndex: 'systemStatus', label: '广告详情', default: 9, width: 100 },
+        ]
+    }
+]
+
+export {
+    txAdConfig,
+    txDynamicConfig
+}

+ 47 - 0
src/pages/launchSystemNew/adqv3/const.tsx

@@ -0,0 +1,47 @@
+import { Badge } from "antd";
+import React from "react";
+
+/** 广告状态 */
+export enum ADGROUP_STATUS {
+    ADGROUP_STATUS_FROZEN = '已冻结',
+    ADGROUP_STATUS_SUSPEND = '暂停中',
+    ADGROUP_STATUS_NOT_IN_DELIVERY_TIME = '广告未到投放时间',
+    ADGROUP_STATUS_ACTIVE = '投放中',
+    ADGROUP_STATUS_DELETED = '已删除',
+    ADGROUP_STATUS_ACCOUNT_BALANCE_NOT_ENOUGH = '账户余额不足',
+    ADGROUP_STATUS_DAILY_BUDGET_REACHED = '广告达到日预算上限'
+}
+
+/** 投放模式 */
+export enum DELIVERY_MODE {
+    DELIVERY_MODE_COMPONENT = '组件化创意',
+    DELIVERY_MODE_CUSTOMIZE = '自定义创意'
+}
+
+/** 创意形式匹配方式 */
+export enum DYNAMIC_CREATIVE_TYPE {
+    DYNAMIC_CREATIVE_TYPE_COMMON = '手动指定',
+    DYNAMIC_CREATIVE_TYPE_PROGRAM = '自动匹配'
+}
+
+
+/** 创意形式匹配方式 */
+export enum DYNAMIC_CREATIVE_STATUS {
+    DYNAMIC_CREATIVE_STATUS_PENDING = '审核中',
+    DYNAMIC_CREATIVE_STATUS_DENIED = '审核不通过',
+    DYNAMIC_CREATIVE_STATUS_ACTIVE = '投放中',
+    DYNAMIC_CREATIVE_STATUS_SUSPEND = '暂停',
+    DYNAMIC_CREATIVE_STATUS_PREPARE_FAILED = '创意准备失败',
+    DYNAMIC_CREATIVE_STATUS_DELETED = '已删除',
+    DYNAMIC_CREATIVE_STATUS_CREATING = '创意准备中',
+}
+
+export const dynamicCreativeStatus = {
+    DYNAMIC_CREATIVE_STATUS_PENDING: <Badge status="default" text='审核中' />,
+    DYNAMIC_CREATIVE_STATUS_DENIED: <Badge status="error" text='审核不通过' />,
+    DYNAMIC_CREATIVE_STATUS_ACTIVE: <Badge status="success" text='投放中' />,
+    DYNAMIC_CREATIVE_STATUS_SUSPEND: <Badge status="warning" text='暂停' />,
+    DYNAMIC_CREATIVE_STATUS_PREPARE_FAILED: <Badge status="error" text='创意准备失败' />,
+    DYNAMIC_CREATIVE_STATUS_DELETED: <Badge status="error" text='已删除' />,
+    DYNAMIC_CREATIVE_STATUS_CREATING: <Badge status="processing" text='创意准备中' />,
+}

+ 61 - 0
src/pages/launchSystemNew/adqv3/creative/box.tsx

@@ -0,0 +1,61 @@
+import { Popover, Space } from 'antd'
+import React, { useMemo } from 'react'
+
+interface Props {
+    creativeComponents: {
+        brand: {
+            value: {
+                brandName: string
+            }
+        }[],
+        description: {
+            value: {
+                content: string
+            }
+        }[],
+        video: {
+            value: {
+                videoUrl: string
+            }
+        }[]
+    }
+}
+/**
+ * 预览
+ */
+const Box: React.FC<Props> = ({ creativeComponents }) => {
+
+
+    let el = useMemo(() => {
+        let video = creativeComponents?.video?.[0]?.value?.videoUrl
+        let title = creativeComponents?.brand?.[0]?.value?.brandName
+        let description = creativeComponents?.description?.[0]?.value?.content
+        if (video) {
+            return <div>
+                <Popover
+                    placement='right'
+                    content={<div>
+                        <div style={{ maxWidth: 300 }}><strong style={{ fontSize: 15 }}>标题:</strong>{title}</div>
+                        <small style={{ fontSize: 10, maxWidth: 300, display: 'inline-block' }}><strong style={{ fontSize: 13 }}>描述:</strong>{description}</small>
+                        <Space style={{ maxWidth: 300, display: 'flex', flexFlow: 'row wrap', margin: '10px 0' }}>
+                            <video src={video} style={{ width: 250 }} controls />
+                        </Space>
+                    </div>}
+                    destroyTooltipOnHide
+                >
+                    <Space style={{ width: '100%' }}>
+                        {/* <video src={video} height={20} /> */}
+                        <a>{title}</a>
+                    </Space>
+                </Popover>
+            </div>
+        } else {
+            return <span>--</span>
+        }
+
+    }, [creativeComponents])
+
+    return el
+}
+
+export default React.memo(Box)

+ 100 - 0
src/pages/launchSystemNew/adqv3/creative/index.tsx

@@ -0,0 +1,100 @@
+import { useAjax } from "@/Hook/useAjax"
+import { getDynamicCreativeV3ListApi } from "@/services/launchAdq/adqv3"
+import { Button, Col, Form, Input, Row, Select, Space } from "antd"
+import React, { useEffect } from "react"
+import TableData from "../../components/TableData"
+import { txDynamicConfig } from "../config"
+import tableConfig from "./tableConfig"
+
+
+const Creative: React.FC<ADQV3.CreativeProps> = ({ queryForm, setQueryForm, userId }) => {
+
+    /*********************************/
+    const [form] = Form.useForm();
+    const getDynamicCreativeV3List = useAjax((params) => getDynamicCreativeV3ListApi(params), { formatResult: true })
+    /*********************************/
+
+    useEffect(() => {
+        form.setFieldsValue({ adgroupId: queryForm.adgroupId })
+    }, [queryForm.adgroupId])
+
+    useEffect(() => {
+        getDynamicCreativeV3List.run({ ...queryForm, userId })
+    }, [userId, queryForm])
+
+    const onFinish = (values: any) => {
+        console.log(values)
+        setQueryForm({ ...queryForm, ...values })
+    }
+
+    return <div>
+        <Form
+            layout="inline"
+            form={form}
+            name="basignCreative"
+            initialValues={queryForm}
+            onFinish={onFinish}
+        >
+            <Row gutter={[10, 10]}>
+                <Col><Form.Item name='accountId' style={{ marginRight: 0 }}>
+                    <Input placeholder="广告账号" allowClear />
+                </Form.Item></Col>
+                <Col><Form.Item name='adgroupId' style={{ marginRight: 0 }}>
+                    <Input placeholder="广告ID" allowClear />
+                </Form.Item></Col>
+                <Col><Form.Item name='creativeName' style={{ marginRight: 0 }}>
+                    <Input placeholder="创意名称" allowClear />
+                </Form.Item></Col>
+                <Col><Form.Item name='creativeId' style={{ marginRight: 0 }}>
+                    <Input placeholder="创意ID" allowClear />
+                </Form.Item></Col>
+                <Col><Form.Item name='isDeleted'>
+                    <Select
+                        placeholder='是否删除?'
+                        style={{ width: 100 }}
+                        showSearch
+                        filterOption={(input: any, option: any) =>
+                            (option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
+                        }
+                    >
+                        <Select.Option value={true}>是</Select.Option>
+                        <Select.Option value={false}>否</Select.Option>
+                    </Select>
+                </Form.Item></Col>
+                <Col><Form.Item style={{ marginRight: 0 }}>
+                    <Button type="primary" htmlType="submit">搜索</Button>
+                    <Button onClick={() => form.resetFields()}>重置</Button>
+                </Form.Item></Col>
+            </Row>
+        </Form>
+
+        <TableData
+            isCard={false}
+            columns={() => tableConfig()}
+            ajax={getDynamicCreativeV3List}
+            fixed={{ left: 2, right: 4 }}
+            dataSource={getDynamicCreativeV3List?.data?.data?.records}
+            loading={getDynamicCreativeV3List?.loading}
+            scroll={{ y: 560 }}
+            total={getDynamicCreativeV3List?.data?.data?.total}
+            page={getDynamicCreativeV3List?.data?.data?.current}
+            pageSize={getDynamicCreativeV3List?.data?.data?.size}
+            myKey={'dynamicCreativeId'}
+            gutter={[0, 10]}
+            config={txDynamicConfig}
+            configName="创意3.0"
+            leftChild={<Space direction='vertical'>
+                <Row gutter={[10, 10]} align='middle'>
+
+                </Row>
+            </Space>}
+            onChange={(props: any) => {
+                let { pagination } = props
+                let { current, pageSize } = pagination
+                setQueryForm({ ...queryForm, pageNum: current, pageSize })
+            }}
+        />
+    </div>
+}
+
+export default React.memo(Creative)

+ 108 - 0
src/pages/launchSystemNew/adqv3/creative/tableConfig.tsx

@@ -0,0 +1,108 @@
+import React from 'react'
+import { Space, Switch } from 'antd'
+import '../index.less'
+import { copy } from '@/utils/utils'
+import { DELIVERY_MODE, DYNAMIC_CREATIVE_TYPE, dynamicCreativeStatus } from '../const'
+import Box from './box'
+function tableConfig(): any {
+    return [
+        {
+            title: '启停',
+            dataIndex: 'configuredStatus',
+            key: 'configuredStatus',
+            align: 'center',
+            width: 40,
+            fixed: 'left',
+            render: (a: string, b: any) => {
+                return <Switch size="small" checked={a === 'AD_STATUS_NORMAL'} onChange={(checked) => { }} />
+            }
+        },
+        {
+            title: '创意预览',
+            dataIndex: 'creativeComponents',
+            key: 'creativeComponents',
+            width: 130,
+            render: (a: any) => {
+                return <Box creativeComponents={a} />
+            }
+        },
+        {
+            title: '所属账号',
+            dataIndex: 'accountId',
+            key: 'accountId',
+            align: 'center',
+            width: 80,
+            ellipsis: true,
+            render: (a: string) => {
+                return <Space>
+                    <a onClick={() => copy(a)} >{a}</a>
+                </Space>
+            }
+        },
+        {
+            title: '广告ID',
+            dataIndex: 'adgroupId',
+            key: 'adgroupId',
+            align: 'center',
+            width: 100,
+            ellipsis: true,
+            render: (a: string, b: any) => {
+                return <Space>
+                    <a onClick={() => copy(a)} >{a}</a>
+                </Space>
+            }
+        },
+        {
+            title: '创意名称',
+            dataIndex: 'dynamicCreativeName',
+            key: 'dynamicCreativeName',
+            align: 'center',
+            width: 120,
+            ellipsis: true
+        },
+        {
+            title: '创意ID',
+            dataIndex: 'dynamicCreativeId',
+            key: 'dynamicCreativeId',
+            align: 'center',
+            width: 100,
+            ellipsis: true,
+            render: (a: string, b: any) => {
+                return <Space>
+                    <a onClick={() => copy(a)} >{a}</a>
+                </Space>
+            }
+        },
+        {
+            title: '投放模式',
+            dataIndex: 'deliveryMode',
+            key: 'deliveryMode',
+            align: 'center',
+            width: 120,
+            render: (a: string) => {
+                return DELIVERY_MODE[a]
+            }
+        },
+        {
+            title: '创意形式匹配方式',
+            dataIndex: 'dynamicCreativeType',
+            key: 'dynamicCreativeType',
+            align: 'center',
+            width: 120,
+            render: (a: string) => {
+                return DYNAMIC_CREATIVE_TYPE[a]
+            }
+        },
+        {
+            title: '状态',
+            dataIndex: 'systemStatus',
+            key: 'systemStatus',
+            align: 'center',
+            width: 120,
+            render: (a: string) => {
+                return dynamicCreativeStatus[a]
+            }
+        },
+    ]
+}
+export default tableConfig

+ 0 - 0
src/pages/launchSystemNew/adqv3/index.less


+ 117 - 0
src/pages/launchSystemNew/adqv3/index.tsx

@@ -0,0 +1,117 @@
+import { useAjax } from "@/Hook/useAjax"
+import { IdcardFilled, MenuFoldOutlined, MenuUnfoldOutlined } from "@ant-design/icons"
+import { Select, Menu, Spin, Button, Card, Tabs } from "antd"
+import React, { useEffect, useState } from "react"
+import { useModel } from "umi"
+import style from '../adq/index.less'
+import { getPicherListApi } from "@/services/operating/account"
+import Ad from "./ad"
+import Creative from "./creative"
+
+
+const AdqV3: React.FC = () => {
+
+    /****************************/
+    const userInfo = useModel('@@initialState', model => model.initialState?.currentUser)
+    const allOfMember = useAjax(() => getPicherListApi(), { formatResult: true })
+    const [userAll, setUserAll] = useState([])
+    const [activeKey, setActiveKey] = useState('1')
+    const [userId, setUserId] = useState<any>(userInfo?.userId?.toString())
+    const [hide, setHide] = useState<boolean>(false)
+    const [queryForm, setQueryForm] = useState<ADQV3.GetDynamicCreativeProps>({ pageNum: 1, pageSize: 20, userId, isDeleted: false })
+    /****************************/
+
+    // 获取组员
+    useEffect(() => {
+        allOfMember.run().then(res => {
+            if (res?.data) {
+                let useAll: any = []
+                res?.data?.forEach((item: { userId: number; nickname: any; }) => {
+                    let obj = {
+                        key: item.userId,
+                        label: item.nickname,
+                        icon: <IdcardFilled />,
+                    }
+                    useAll.push(obj)
+                })
+                setUserAll(useAll)
+            }
+        })
+    }, [])
+
+    // // 初始跳转到自己的名称
+    useEffect(() => {
+        if (userAll?.length > 0 && userId) {
+            let topEq = userAll?.findIndex((item: any) => item.key == userId)
+            let em: any = document.getElementById('myMenus1')
+            console.log(topEq)
+            if (em) {
+                em.scrollTop = (52 - 4) * topEq
+            }
+        }
+    }, [userId, userAll])
+
+    const creativeHandle = (id: number) => {
+        setQueryForm({ ...queryForm, adgroupId: id })
+        setActiveKey('2')
+    }
+
+    return <div className={style.adq}>
+        {!hide && <div className={style.left}>
+            <Spin spinning={allOfMember.loading}>
+                <div>
+                    <Select
+                        placeholder='名称搜索'
+                        style={{ width: '100%' }}
+                        showSearch
+                        filterOption={(input: any, option: any) => {
+                            return (option!?.children as unknown as string)?.toLowerCase()?.includes(input?.toLowerCase())
+                        }}
+                        allowClear
+                        onChange={(value: any) => {
+                            if (value) {
+                                setUserId(value.toString())
+                            }
+                        }}
+                        loading={allOfMember.loading}
+                    >
+                        {userAll.map((item: any) => {
+                            return <Select.Option value={item.key} key={item.key}>{item.label}</Select.Option>
+                        })}
+                    </Select>
+                    <Menu
+                        id='myMenus1'
+                        theme='light'
+                        onClick={(e: any) => {//点击菜单
+                            setUserId(e.key)
+                        }}
+                        selectedKeys={userId}
+                        mode="inline"
+                        multiple={false}
+                        items={userAll}
+                        style={{
+                            overflowY: 'auto',
+                            height: 'calc(100vh - 150px)',
+                            overflowX: 'hidden'
+                        }}
+                    />
+                </div>
+            </Spin>
+        </div>}
+        <div className={style.right} style={!hide ? { width: 'calc(100% - 150px)' } : { width: '100%' }}>
+            <div className={style.hiddenBtn}><Button size='small' type="primary" onClick={() => setHide(!hide)} icon={!hide ? <MenuFoldOutlined /> : <MenuUnfoldOutlined />} /></div>
+            <Card bodyStyle={{ padding: '12px 16px' }}>
+                <Tabs activeKey={activeKey} size="small" type="card" onChange={(activeKey) => { setActiveKey(activeKey) }}>
+                    <Tabs.TabPane tab={'广告'} key='1' >
+                        <Ad userId={userId} creativeHandle={creativeHandle} />
+                    </Tabs.TabPane>
+                    <Tabs.TabPane tab={'创意'} key='2' >
+                        <Creative queryForm={queryForm} setQueryForm={setQueryForm} userId={userId}/>
+                    </Tabs.TabPane>
+                </Tabs>
+            </Card>
+        </div>
+    </div>
+}
+
+export default AdqV3

+ 42 - 0
src/pages/launchSystemNew/adqv3/typings.d.ts

@@ -0,0 +1,42 @@
+declare namespace ADQV3 {
+    interface AdProps {
+        userId: number
+        creativeHandle?: (id: number) => void
+    };
+    interface CreativeProps {
+        queryForm: GetDynamicCreativeProps,
+        setQueryForm: React.Dispatch<React.SetStateAction<GetDynamicCreativeProps>>,
+        userId: number
+    };
+    interface GetAdListProps {
+        pageNum: number;
+        pageSize: number;
+        useType: 1 | 2,   // 1:小说(默认值)2:游戏
+        accountMemo?: string,  // 腾讯备注
+        accountRemark?: string, // 本地备注
+        accountIdList?: any[], // 广告账号
+        adgroupIdList?: any[], // 广告组
+        adgroupName?: string,  // 广告名称
+        putUserIdList?: number[], // 投手
+        systemStatusList?: string[], // 广告状态
+    };
+    type AccountAdgroupMapsProps = {
+        accountAdgroupMaps: string[]
+    };
+    interface ModifyStatusBatchProps extends AccountAdgroupMapsProps {
+        suspend: boolean
+    };
+    interface ModifyAmountBatchProps extends AccountAdgroupMapsProps {
+        bidAmount: number
+    };
+    interface GetDynamicCreativeProps {
+        pageNum: number;
+        pageSize: number;
+        userId: number;
+        isDeleted?: boolean;
+        creativeName?: string;
+        creativeId?: number;
+        adgroupId?: number;
+        accountId?: number
+    }
+}

+ 1 - 1
src/pages/launchSystemNew/components/TableData/index.tsx

@@ -71,7 +71,7 @@ function TableData(props: Prosp) {
     })
     const ref = useRef(null)
     const oldName = useRef(null)
-    const version = '1.0.0'
+    const version = '1.0.1'
     // // /**重组选中的字段 */
     useEffect(() => {
         let oldConfigName = oldName.current || ''

+ 103 - 0
src/services/adMonitor/adMonitor.ts

@@ -74,6 +74,18 @@ export async function getListForHourApi(params: ListHourProps) {
     })
 }
 
+/**
+ * 3.0
+ * @param params 
+ * @returns 
+ */
+export async function getListForHourV3Api(params: ListHourProps) {
+    return request(`${api}/tencentMonitor/adV3Data/listForHour`, {
+        method: 'POST',
+        data: params
+    })
+}
+
 /** 获取起量广告列表5min */
 export async function getMinuteListApi(params: ListType) {
     Object.keys(params).forEach(key => {
@@ -298,6 +310,23 @@ export async function getCostTrendListApi(data: ListType) {
     })
 }
 
+/**
+ * NEW 3.0 广告消耗趋势 获取今日计划总消耗图谱 折线图
+ * @param data 
+ * @returns 
+ */
+export async function getCostTrendV3ListApi(data: ListType) {
+    Object.keys(data).forEach(key => {
+        if (!data[key]) {
+            delete data[key]
+        }
+    })
+    return request(`${api}/tencentMonitor/adV3Data/costTrend`, {
+        method: 'POST',
+        data
+    })
+}
+
 /**
  * NEW 广告起量排行
  * @param params 
@@ -315,6 +344,23 @@ export async function getCostTopListApi(data: ListType) {
     })
 }
 
+/**
+ * NEW3.0 广告起量排行
+ * @param params 
+ * @returns 
+ */
+export async function getCostTopV3ListApi(data: ListType) {
+    Object.keys(data).forEach(key => {
+        if (!data[key]) {
+            delete data[key]
+        }
+    })
+    return request(`${api}/tencentMonitor/adV3Data/costTop`, {
+        method: 'POST',
+        data
+    })
+}
+
 
 export interface GetColumnTrendProps {
     accountId: number
@@ -337,6 +383,18 @@ export async function getColumnTrendApi(data: GetColumnTrendProps) {
     })
 }
 
+/**
+ * NEW3.0 广告指标趋势
+ * @param data 
+ * @returns 
+ */
+export async function getColumnTrendV3Api(data: GetColumnTrendProps) {
+    return request(`${api}/tencentMonitor/adV3Data/columnTrend`, {
+        method: 'POST',
+        data
+    })
+}
+
 /**
  * NEW 广告数据-指定广告的总数据
  * @param data 
@@ -349,6 +407,17 @@ export async function getAdTotalDataApi(data: { accountId: number, adgroupId: nu
     })
 }
 
+/**
+ * NEW3.0 广告数据-指定广告的总数据
+ * @param data 
+ * @returns 
+ */
+export async function getAdTotalDataV3Api(data: { accountId: number, adgroupId: number, columns: string[] }) {
+    return request(`${api}/tencentMonitor/adV3Data/adTotalData`, {
+        method: 'POST',
+        data
+    })
+}
 
 export interface GetListForAdProps {
     accountId: number,
@@ -373,6 +442,18 @@ export async function getListForAdApi(data: GetListForAdProps) {
     })
 }
 
+/**
+ * NEW3.0 广告数据列表-指定广告的所有数据
+ * @param data 
+ * @returns 
+ */
+export async function getListForAdV3Api(data: GetListForAdProps) {
+    return request(`${api}/tencentMonitor/adV3Data/listForAd`, {
+        method: 'POST',
+        data
+    })
+}
+
 
 /**
  * 广告列表指标趋势
@@ -434,6 +515,18 @@ export async function getAdListApi(data: AdListProps) {
     })
 }
 
+/**
+ * 3.0
+ * @param data 
+ * @returns 
+ */
+export async function getAdV3ListApi(data: AdListProps) {
+    return request(`${api}/tencentMonitor/adV3/list`, {
+        method: 'POST',
+        data
+    })
+}
+
 /**
  * 广告详情
  * @param param0 
@@ -444,6 +537,16 @@ export async function getAdgroupsDetailsApi({ accountId, adgroupId }: { accountI
         method: 'POST'
     })
 }
+/**
+ * 广告详情3.0
+ * @param param0 
+ * @returns 
+ */
+export async function getAdgroupDetailsApi({ accountId, adgroupId }: { accountId: any, adgroupId: any }) {
+    return request(`${api}/adq/adgroup/detail/${accountId}/${adgroupId}`, {
+        method: 'GET'
+    })
+}
 
 /**
  * 移除广告的告警规则---广告告警规则表的操作

+ 62 - 0
src/services/launchAdq/adqv3.ts

@@ -0,0 +1,62 @@
+import { request } from 'umi';
+import { api } from '../api';
+
+/**
+ * 广告列表
+ * @param data 
+ * @returns 
+ */
+export async function getAdqV3AdListApi(data: ADQV3.GetAdListProps) {
+    return request(api + '/adq/adgroup/listOfPage', {
+        method: 'POST',
+        data
+    });
+}
+
+/**
+ * 同步
+ * @param data 
+ * @returns 
+ */
+export async function syncBatchApi(data: ADQV3.AccountAdgroupMapsProps) {
+    return request(api + '/adq/adgroup/syncBatch', {
+        method: 'PUT',
+        data
+    });
+}
+
+/**
+ * 启停
+ * @param data 
+ * @returns 
+ */
+export async function modifyStatusBatchApi(data: ADQV3.ModifyStatusBatchProps) {
+    return request(api + '/adq/adgroup/modifyStatusBatch', {
+        method: 'PUT',
+        data
+    });
+}
+
+/**
+ * 出价
+ * @param data 
+ * @returns 
+ */
+export async function modifyAmountBatchApi(data: ADQV3.ModifyAmountBatchProps) {
+    return request(api + '/adq/adgroup/modifyAmountBatch', {
+        method: 'PUT',
+        data
+    });
+}
+
+/**
+ * 3.0创意
+ * @param data 
+ * @returns 
+ */
+export async function getDynamicCreativeV3ListApi(data: ADQV3.GetDynamicCreativeProps) {
+    return request(api + '/adq/dynamicCreative/listOfPage', {
+        method: 'POST',
+        data
+    });
+}